Compare commits
150 Commits
download-m
...
content-co
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ee2fd2ef68 | ||
![]() |
3f95d2bf1d | ||
![]() |
1390983732 | ||
![]() |
ce660cefe9 | ||
![]() |
72b81eb886 | ||
![]() |
57d593a68a | ||
![]() |
39a81a3376 | ||
![]() |
fd0bf17c24 | ||
![]() |
ac12bff69b | ||
![]() |
feef773bac | ||
![]() |
239d8f12a7 | ||
![]() |
8bbc61a7cb | ||
![]() |
7f31c4477f | ||
![]() |
6bad67c1bf | ||
![]() |
c76e6dc99f | ||
![]() |
acf9db0db3 | ||
![]() |
69b4f0b547 | ||
![]() |
80e165b505 | ||
![]() |
bcce55b873 | ||
![]() |
d5c92560db | ||
![]() |
f827c1c9bf | ||
![]() |
88c5f1a02d | ||
![]() |
d8e44f5f39 | ||
![]() |
72ff47ffe5 | ||
![]() |
066ee2c96d | ||
![]() |
0a8016dea7 | ||
![]() |
db36367b11 | ||
![]() |
b6c9ccb7f6 | ||
![]() |
a9dc636bce | ||
![]() |
3cc0574d11 | ||
![]() |
20fab9b16d | ||
![]() |
4015818323 | ||
![]() |
f569d45c8c | ||
![]() |
3773647869 | ||
![]() |
29cdbf018c | ||
![]() |
94bb7022eb | ||
![]() |
39808302df | ||
![]() |
2d22f9c39e | ||
![]() |
ee8f80bab6 | ||
![]() |
3e6242e583 | ||
![]() |
41181616ee | ||
![]() |
eb2530ca32 | ||
![]() |
b5233780ef | ||
![]() |
78753d7538 | ||
![]() |
4740e8b4f5 | ||
![]() |
ad5b00fc90 | ||
![]() |
d6c6880848 | ||
![]() |
4f948c1b9e | ||
![]() |
2b68c24f9c | ||
![]() |
bcdf0422db | ||
![]() |
f6434b478d | ||
![]() |
e979fdd26f | ||
![]() |
e6bfcaaab9 | ||
![]() |
9780108e8a | ||
![]() |
697c7d2d6d | ||
![]() |
887d10c8bf | ||
![]() |
ef6b8fe458 | ||
![]() |
20ab55d763 | ||
![]() |
eda58c9e0d | ||
![]() |
fb42fc0e35 | ||
![]() |
35cabc47ad | ||
![]() |
5be97d0404 | ||
![]() |
82b0fa253c | ||
![]() |
011a4d5766 | ||
![]() |
5cd1ca88c1 | ||
![]() |
44c880d911 | ||
![]() |
14857cb5ad | ||
![]() |
7daf981f1a | ||
![]() |
b99bc0ea32 | ||
![]() |
1ccf6fbdfa | ||
![]() |
5711979272 | ||
![]() |
9a5e2b1fa3 | ||
![]() |
cafc5f582e | ||
![]() |
a89b423dfc | ||
![]() |
79e8438941 | ||
![]() |
19c2c46491 | ||
![]() |
78f1d54b69 | ||
![]() |
9461649ed4 | ||
![]() |
8573ab2850 | ||
![]() |
8b3d752727 | ||
![]() |
7c54bd8966 | ||
![]() |
5d0fcb7027 | ||
![]() |
3ec9654d3c | ||
![]() |
7c8d64b462 | ||
![]() |
31e30e3d31 | ||
![]() |
8caf6e99b0 | ||
![]() |
624155debd | ||
![]() |
4468a262ae | ||
![]() |
1780901cb0 | ||
![]() |
d830d9261f | ||
![]() |
f5e1833a48 | ||
![]() |
9feb2a3c8f | ||
![]() |
b27665f5dd | ||
![]() |
4465aa4134 | ||
![]() |
ad766ac748 | ||
![]() |
d9e7d67d86 | ||
![]() |
3fefbc94b3 | ||
![]() |
21034209a5 | ||
![]() |
7c04c0f83c | ||
![]() |
f5293d65dd | ||
![]() |
8191bf6066 | ||
![]() |
29b6bfd463 | ||
![]() |
2f3d23bc34 | ||
![]() |
98dd80c4b8 | ||
![]() |
d9edb2e128 | ||
![]() |
de04b40b86 | ||
![]() |
7206a3d926 | ||
![]() |
98b98d8938 | ||
![]() |
294b8fcc2f | ||
![]() |
32f601a1b1 | ||
![]() |
8e3a398080 | ||
![]() |
720b9688b4 | ||
![]() |
e3066161c5 | ||
![]() |
a9aa3a524f | ||
![]() |
92848e818a | ||
![]() |
a7aa3008c0 | ||
![]() |
485325e824 | ||
![]() |
0df2a0e039 | ||
![]() |
fb7b4466c2 | ||
![]() |
53105245f4 | ||
![]() |
b68eab91e0 | ||
![]() |
f72cf91462 | ||
![]() |
a655c4ef50 | ||
![]() |
5d46e9b796 | ||
![]() |
642e6e67b3 | ||
![]() |
2b6b86f903 | ||
![]() |
f2706a4426 | ||
![]() |
1af75413aa | ||
![]() |
adc4077b1a | ||
![]() |
01f4e2453b | ||
![]() |
61267374dd | ||
![]() |
970f814685 | ||
![]() |
4fd9fc1991 | ||
![]() |
26207ffd1b | ||
![]() |
2614cfbe5f | ||
![]() |
f11d461ec0 | ||
![]() |
b2eb2d2755 | ||
![]() |
ea46a54f19 | ||
![]() |
627add45ad | ||
![]() |
d364855459 | ||
![]() |
14ee35e77a | ||
![]() |
8773eb4ee0 | ||
![]() |
51425bbfd9 | ||
![]() |
6a4879bc0b | ||
![]() |
e7fe56439b | ||
![]() |
2886feab4a | ||
![]() |
fb91194026 | ||
![]() |
4527478b0d | ||
![]() |
b0062f146e | ||
![]() |
bf16561170 |
35
README.md
35
README.md
@@ -4,7 +4,7 @@ MuWire is an easy to use file-sharing program which offers anonymity using [I2P
|
|||||||
|
|
||||||
It is inspired by the LimeWire Gnutella client and developped by a former LimeWire developer.
|
It is inspired by the LimeWire Gnutella client and developped by a former LimeWire developer.
|
||||||
|
|
||||||
The current stable release - 0.2.5 is avaiable for download at http://muwire.com. You can find technical documentation in the "doc" folder.
|
The current stable release - 0.4.6 is avaiable for download at https://muwire.com. You can find technical documentation in the "doc" folder.
|
||||||
|
|
||||||
### Building
|
### Building
|
||||||
|
|
||||||
@@ -23,34 +23,13 @@ Some of the UI tests will fail because they haven't been written yet :-/
|
|||||||
|
|
||||||
### Running
|
### Running
|
||||||
|
|
||||||
You need to have an I2P router up and running on the same machine. 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 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.
|
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.
|
||||||
|
|
||||||
The first time you run MuWire it will ask you to select a nickname. This nickname will be displayed with search results, so that others can verify the file was shared by you. It is best to leave MuWire running all the time, just like I2P.
|
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.
|
||||||
|
|
||||||
### Known bugs and limitations
|
### GPG Fingerprint
|
||||||
|
471B 9FD4 5517 A5ED 101F C57D A728 3207 2D52 5E41
|
||||||
|
|
||||||
* Many UI features you would expect are not there yet
|
You can find the full key at https://keybase.io/zlatinb
|
||||||
|
|
||||||
### Quick FAQ
|
|
||||||
|
|
||||||
* why is MuWire slow ?
|
|
||||||
|
|
||||||
- too few sources you're downloading from
|
|
||||||
- you can increase the number of tunnels by using more tunnels via Options->I2P Inbound/Outbound Quantity
|
|
||||||
the default is 4 and you could raise up to as high as 16 ( Caution !!!!)
|
|
||||||
|
|
||||||
* my search is not returning (enough) results !
|
|
||||||
|
|
||||||
- search is keyword or hash based
|
|
||||||
- keywords and hash(es) are NOT regexed or wildcarded so they have to be complete
|
|
||||||
so searching for 'musi' will not return results with 'music' - you have to search for 'music'
|
|
||||||
- ALL keywords have to match
|
|
||||||
- only use space for keyword separation
|
|
||||||
- if you already have the file in question it is not displayed ( can be changed via Options )
|
|
||||||
|
|
||||||
* what's this right click -> 'Copy hash to clipboard' for ?
|
|
||||||
|
|
||||||
- if you have a specific file you wish to share or download you can use the hash as a unique identifier
|
|
||||||
to make sure you have exactly the right file.
|
|
||||||
- you can share this hash with others to ensure they are getting the right file
|
|
||||||
|
9
TODO.md
9
TODO.md
@@ -12,18 +12,10 @@ This reduces query traffic by not sending last hop queries to peers that definit
|
|||||||
|
|
||||||
This helps with scalability
|
This helps with scalability
|
||||||
|
|
||||||
##### Trust List Sharing
|
|
||||||
|
|
||||||
For helping users make better decisions whom to trust
|
|
||||||
|
|
||||||
##### Content Control Panel
|
##### 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
|
To allow every user to not route queries for content they do not like. This is mostly GUI work, the backend part is simple
|
||||||
|
|
||||||
##### Packaging With JRE, Embedded Router
|
|
||||||
|
|
||||||
For ease of deployment for new users, and so that users do not need to run a separate I2P router
|
|
||||||
|
|
||||||
##### 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
|
||||||
@@ -36,5 +28,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
|
||||||
* Unsharing of files
|
|
||||||
* Multiple-selection download, Ctrl-A
|
* Multiple-selection download, Ctrl-A
|
||||||
|
@@ -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.41'
|
||||||
compile 'org.codehaus.groovy:groovy-all:2.4.15'
|
compile 'org.codehaus.groovy:groovy-all:2.4.15'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -35,7 +35,7 @@ class Cli {
|
|||||||
|
|
||||||
Core core
|
Core core
|
||||||
try {
|
try {
|
||||||
core = new Core(props, home, "0.3.6")
|
core = new Core(props, home, "0.4.8")
|
||||||
} catch (Exception bad) {
|
} catch (Exception bad) {
|
||||||
bad.printStackTrace(System.out)
|
bad.printStackTrace(System.out)
|
||||||
println "Failed to initialize core, exiting"
|
println "Failed to initialize core, exiting"
|
||||||
|
@@ -53,7 +53,7 @@ class CliDownloader {
|
|||||||
|
|
||||||
Core core
|
Core core
|
||||||
try {
|
try {
|
||||||
core = new Core(props, home, "0.3.6")
|
core = new Core(props, home, "0.4.8")
|
||||||
} catch (Exception bad) {
|
} catch (Exception bad) {
|
||||||
bad.printStackTrace(System.out)
|
bad.printStackTrace(System.out)
|
||||||
println "Failed to initialize core, exiting"
|
println "Failed to initialize core, exiting"
|
||||||
|
@@ -2,8 +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.client:mstreaming:0.9.40'
|
compile 'net.i2p:router:0.9.41'
|
||||||
compile 'net.i2p.client:streaming:0.9.40'
|
compile 'net.i2p.client:mstreaming:0.9.41'
|
||||||
|
compile 'net.i2p.client:streaming:0.9.41'
|
||||||
|
|
||||||
testCompile 'org.junit.jupiter:junit-jupiter-api:5.4.2'
|
testCompile 'org.junit.jupiter:junit-jupiter-api:5.4.2'
|
||||||
testCompile 'junit:junit:4.12'
|
testCompile 'junit:junit:4.12'
|
||||||
|
@@ -9,7 +9,5 @@ class Constants {
|
|||||||
public static final int MAX_HEADER_SIZE = 0x1 << 14
|
public static final int MAX_HEADER_SIZE = 0x1 << 14
|
||||||
public static final int MAX_HEADERS = 16
|
public static final int MAX_HEADERS = 16
|
||||||
|
|
||||||
public static final float DOWNLOAD_SEQUENTIAL_RATIO = 0.8f
|
public static final String SPLIT_PATTERN = "[\\*\\+\\-,\\.:;\\(\\)=_/\\\\\\!\\\"\\\'\\\$%\\|\\[\\]\\{\\}\\?]"
|
||||||
|
|
||||||
public static final String SPLIT_PATTERN = "[\\+\\-,\\.:;\\(\\)=_/\\\\\\!\\\"\\\'\\\$%\\|]"
|
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
@@ -27,6 +28,8 @@ import com.muwire.core.files.FileSharedEvent
|
|||||||
import com.muwire.core.files.FileUnsharedEvent
|
import com.muwire.core.files.FileUnsharedEvent
|
||||||
import com.muwire.core.files.HasherService
|
import com.muwire.core.files.HasherService
|
||||||
import com.muwire.core.files.PersisterService
|
import com.muwire.core.files.PersisterService
|
||||||
|
import com.muwire.core.files.AllFilesLoadedEvent
|
||||||
|
import com.muwire.core.files.DirectoryUnsharedEvent
|
||||||
import com.muwire.core.files.DirectoryWatcher
|
import com.muwire.core.files.DirectoryWatcher
|
||||||
import com.muwire.core.hostcache.CacheClient
|
import com.muwire.core.hostcache.CacheClient
|
||||||
import com.muwire.core.hostcache.HostCache
|
import com.muwire.core.hostcache.HostCache
|
||||||
@@ -37,11 +40,16 @@ import com.muwire.core.search.ResultsEvent
|
|||||||
import com.muwire.core.search.ResultsSender
|
import com.muwire.core.search.ResultsSender
|
||||||
import com.muwire.core.search.SearchEvent
|
import com.muwire.core.search.SearchEvent
|
||||||
import com.muwire.core.search.SearchManager
|
import com.muwire.core.search.SearchManager
|
||||||
|
import com.muwire.core.search.UIResultBatchEvent
|
||||||
import com.muwire.core.trust.TrustEvent
|
import com.muwire.core.trust.TrustEvent
|
||||||
import com.muwire.core.trust.TrustService
|
import com.muwire.core.trust.TrustService
|
||||||
|
import com.muwire.core.trust.TrustSubscriber
|
||||||
|
import com.muwire.core.trust.TrustSubscriptionEvent
|
||||||
import com.muwire.core.update.UpdateClient
|
import com.muwire.core.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
|
||||||
@@ -58,6 +66,9 @@ import net.i2p.data.PrivateKey
|
|||||||
import net.i2p.data.Signature
|
import net.i2p.data.Signature
|
||||||
import net.i2p.data.SigningPrivateKey
|
import net.i2p.data.SigningPrivateKey
|
||||||
|
|
||||||
|
import net.i2p.router.Router
|
||||||
|
import net.i2p.router.RouterContext
|
||||||
|
|
||||||
@Log
|
@Log
|
||||||
public class Core {
|
public class Core {
|
||||||
|
|
||||||
@@ -68,6 +79,7 @@ public class Core {
|
|||||||
final MuWireSettings muOptions
|
final MuWireSettings muOptions
|
||||||
|
|
||||||
private final TrustService trustService
|
private final TrustService trustService
|
||||||
|
private final TrustSubscriber trustSubscriber
|
||||||
private final PersisterService persisterService
|
private final PersisterService persisterService
|
||||||
private final HostCache hostCache
|
private final HostCache hostCache
|
||||||
private final ConnectionManager connectionManager
|
private final ConnectionManager connectionManager
|
||||||
@@ -79,25 +91,16 @@ public class Core {
|
|||||||
private final DownloadManager downloadManager
|
private final DownloadManager downloadManager
|
||||||
private final DirectoryWatcher directoryWatcher
|
private final DirectoryWatcher directoryWatcher
|
||||||
final FileManager fileManager
|
final FileManager fileManager
|
||||||
|
final UploadManager uploadManager
|
||||||
|
final ContentManager contentManager
|
||||||
|
|
||||||
|
private final Router router
|
||||||
|
|
||||||
final AtomicBoolean shutdown = new AtomicBoolean()
|
final AtomicBoolean shutdown = new AtomicBoolean()
|
||||||
|
|
||||||
public Core(MuWireSettings props, File home, String myVersion) {
|
public Core(MuWireSettings props, File home, String myVersion) {
|
||||||
this.home = home
|
this.home = home
|
||||||
this.muOptions = props
|
this.muOptions = props
|
||||||
log.info "Initializing I2P context"
|
|
||||||
I2PAppContext.getGlobalContext().logManager()
|
|
||||||
I2PAppContext.getGlobalContext()._logManager = new MuWireLogManager()
|
|
||||||
|
|
||||||
log.info("initializing I2P socket manager")
|
|
||||||
def i2pClient = new I2PClientFactory().createClient()
|
|
||||||
File keyDat = new File(home, "key.dat")
|
|
||||||
if (!keyDat.exists()) {
|
|
||||||
log.info("Creating new key.dat")
|
|
||||||
keyDat.withOutputStream {
|
|
||||||
i2pClient.createDestination(it, Constants.SIG_TYPE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
i2pOptions = new Properties()
|
i2pOptions = new Properties()
|
||||||
def i2pOptionsFile = new File(home,"i2p.properties")
|
def i2pOptionsFile = new File(home,"i2p.properties")
|
||||||
@@ -106,8 +109,8 @@ public class Core {
|
|||||||
|
|
||||||
if (!i2pOptions.containsKey("inbound.nickname"))
|
if (!i2pOptions.containsKey("inbound.nickname"))
|
||||||
i2pOptions["inbound.nickname"] = "MuWire"
|
i2pOptions["inbound.nickname"] = "MuWire"
|
||||||
if (!i2pOptions.containsKey("outbound.nickname"))
|
if (!i2pOptions.containsKey("outbound.nickname"))
|
||||||
i2pOptions["outbound.nickname"] = "MuWire"
|
i2pOptions["outbound.nickname"] = "MuWire"
|
||||||
} else {
|
} else {
|
||||||
i2pOptions["inbound.nickname"] = "MuWire"
|
i2pOptions["inbound.nickname"] = "MuWire"
|
||||||
i2pOptions["outbound.nickname"] = "MuWire"
|
i2pOptions["outbound.nickname"] = "MuWire"
|
||||||
@@ -117,18 +120,57 @@ public class Core {
|
|||||||
i2pOptions["outbound.quantity"] = "4"
|
i2pOptions["outbound.quantity"] = "4"
|
||||||
i2pOptions["i2cp.tcp.host"] = "127.0.0.1"
|
i2pOptions["i2cp.tcp.host"] = "127.0.0.1"
|
||||||
i2pOptions["i2cp.tcp.port"] = "7654"
|
i2pOptions["i2cp.tcp.port"] = "7654"
|
||||||
|
Random r = new Random()
|
||||||
|
int port = r.nextInt(60000) + 4000
|
||||||
|
i2pOptions["i2np.ntcp.port"] = String.valueOf(port)
|
||||||
|
i2pOptions["i2np.udp.port"] = String.valueOf(port)
|
||||||
|
i2pOptionsFile.withOutputStream { i2pOptions.store(it, "") }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!props.embeddedRouter) {
|
||||||
|
log.info "Initializing I2P context"
|
||||||
|
I2PAppContext.getGlobalContext().logManager()
|
||||||
|
I2PAppContext.getGlobalContext()._logManager = new MuWireLogManager()
|
||||||
|
router = null
|
||||||
|
} else {
|
||||||
|
log.info("launching embedded router")
|
||||||
|
Properties routerProps = new Properties()
|
||||||
|
routerProps.setProperty("i2p.dir.config", home.getAbsolutePath())
|
||||||
|
routerProps.setProperty("router.excludePeerCaps", "KLM")
|
||||||
|
routerProps.setProperty("i2np.inboundKBytesPerSecond", String.valueOf(props.inBw))
|
||||||
|
routerProps.setProperty("i2np.outboundKBytesPerSecond", String.valueOf(props.outBw))
|
||||||
|
routerProps.setProperty("i2cp.disableInterface", "true")
|
||||||
|
routerProps.setProperty("i2np.ntcp.port", i2pOptions["i2np.ntcp.port"])
|
||||||
|
routerProps.setProperty("i2np.udp.port", i2pOptions["i2np.udp.port"])
|
||||||
|
routerProps.setProperty("i2np.udp.internalPort", i2pOptions["i2np.udp.port"])
|
||||||
|
router = new Router(routerProps)
|
||||||
|
router.getContext().setLogManager(new MuWireLogManager())
|
||||||
|
router.runRouter()
|
||||||
|
while(!router.isRunning())
|
||||||
|
Thread.sleep(100)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("initializing I2P socket manager")
|
||||||
|
def i2pClient = new I2PClientFactory().createClient()
|
||||||
|
File keyDat = new File(home, "key.dat")
|
||||||
|
if (!keyDat.exists()) {
|
||||||
|
log.info("Creating new key.dat")
|
||||||
|
keyDat.withOutputStream {
|
||||||
|
i2pClient.createDestination(it, Constants.SIG_TYPE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// options like tunnel length and quantity
|
// 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)
|
||||||
@@ -137,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)
|
||||||
@@ -155,62 +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)
|
||||||
|
|
||||||
log.info("initializing mesh manager")
|
log.info("initializing mesh manager")
|
||||||
MeshManager meshManager = new MeshManager(fileManager, home)
|
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, 15000, 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)
|
updateClient = new UpdateClient(eventBus, i2pSession, myVersion, props, fileManager, me)
|
||||||
|
eventBus.register(FileDownloadedEvent.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)
|
||||||
@@ -223,28 +268,39 @@ public class Core {
|
|||||||
eventBus.register(UIDownloadResumedEvent.class, downloadManager)
|
eventBus.register(UIDownloadResumedEvent.class, downloadManager)
|
||||||
|
|
||||||
log.info("initializing upload manager")
|
log.info("initializing upload manager")
|
||||||
UploadManager uploadManager = new UploadManager(eventBus, fileManager, meshManager, downloadManager)
|
uploadManager = new UploadManager(eventBus, fileManager, meshManager, downloadManager)
|
||||||
|
|
||||||
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")
|
||||||
directoryWatcher = new DirectoryWatcher(eventBus, fileManager)
|
directoryWatcher = new DirectoryWatcher(eventBus, fileManager)
|
||||||
eventBus.register(FileSharedEvent.class, directoryWatcher)
|
eventBus.register(FileSharedEvent.class, directoryWatcher)
|
||||||
|
eventBus.register(AllFilesLoadedEvent.class, directoryWatcher)
|
||||||
|
eventBus.register(DirectoryUnsharedEvent.class, directoryWatcher)
|
||||||
|
|
||||||
log.info("initializing hasher service")
|
log.info("initializing hasher service")
|
||||||
hasherService = new HasherService(new FileHasher(), eventBus, fileManager)
|
hasherService = new HasherService(new FileHasher(), eventBus, fileManager)
|
||||||
eventBus.register(FileSharedEvent.class, hasherService)
|
eventBus.register(FileSharedEvent.class, hasherService)
|
||||||
}
|
|
||||||
|
log.info("initializing trust subscriber")
|
||||||
|
trustSubscriber = new TrustSubscriber(eventBus, i2pConnector, props)
|
||||||
|
eventBus.register(UILoadedEvent.class, trustSubscriber)
|
||||||
|
eventBus.register(TrustSubscriptionEvent.class, trustSubscriber)
|
||||||
|
|
||||||
|
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()
|
||||||
directoryWatcher.start()
|
|
||||||
trustService.start()
|
trustService.start()
|
||||||
trustService.waitForLoad()
|
trustService.waitForLoad()
|
||||||
hostCache.start()
|
hostCache.start()
|
||||||
@@ -261,6 +317,8 @@ public class Core {
|
|||||||
log.info("already shutting down")
|
log.info("already shutting down")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
log.info("shutting down trust subscriber")
|
||||||
|
trustSubscriber.stop()
|
||||||
log.info("shutting down download manageer")
|
log.info("shutting down download manageer")
|
||||||
downloadManager.shutdown()
|
downloadManager.shutdown()
|
||||||
log.info("shutting down connection acceeptor")
|
log.info("shutting down connection acceeptor")
|
||||||
@@ -269,8 +327,14 @@ public class Core {
|
|||||||
connectionEstablisher.stop()
|
connectionEstablisher.stop()
|
||||||
log.info("shutting down directory watcher")
|
log.info("shutting down directory watcher")
|
||||||
directoryWatcher.stop()
|
directoryWatcher.stop()
|
||||||
|
log.info("shutting down cache client")
|
||||||
|
cacheClient.stop()
|
||||||
log.info("shutting down connection manager")
|
log.info("shutting down connection manager")
|
||||||
connectionManager.shutdown()
|
connectionManager.shutdown()
|
||||||
|
if (router != null) {
|
||||||
|
log.info("shutting down embedded router")
|
||||||
|
router.shutdown(0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static main(args) {
|
static main(args) {
|
||||||
@@ -297,7 +361,7 @@ public class Core {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Core core = new Core(props, home, "0.3.6")
|
Core core = new Core(props, home, "0.4.8")
|
||||||
core.startServices()
|
core.startServices()
|
||||||
|
|
||||||
// ... at the end, sleep or execute script
|
// ... at the end, sleep or execute script
|
||||||
|
@@ -4,17 +4,17 @@ import java.util.concurrent.atomic.AtomicLong
|
|||||||
|
|
||||||
class Event {
|
class Event {
|
||||||
|
|
||||||
private static final AtomicLong SEQ_NO = new AtomicLong();
|
private static final AtomicLong SEQ_NO = new AtomicLong();
|
||||||
final long seqNo
|
final long seqNo
|
||||||
final long timestamp
|
final long timestamp
|
||||||
|
|
||||||
Event() {
|
Event() {
|
||||||
seqNo = SEQ_NO.getAndIncrement()
|
seqNo = SEQ_NO.getAndIncrement()
|
||||||
timestamp = System.currentTimeMillis()
|
timestamp = System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
"seqNo $seqNo timestamp $timestamp"
|
"seqNo $seqNo timestamp $timestamp"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,41 +11,46 @@ import groovy.util.logging.Log
|
|||||||
@Log
|
@Log
|
||||||
class EventBus {
|
class EventBus {
|
||||||
|
|
||||||
private Map handlers = new HashMap()
|
private Map handlers = new HashMap()
|
||||||
private final Executor executor = Executors.newSingleThreadExecutor {r ->
|
private final Executor executor = Executors.newSingleThreadExecutor {r ->
|
||||||
def rv = new Thread(r)
|
def rv = new Thread(r)
|
||||||
rv.setDaemon(true)
|
rv.setDaemon(true)
|
||||||
rv.setName("event-bus")
|
rv.setName("event-bus")
|
||||||
rv
|
rv
|
||||||
}
|
}
|
||||||
|
|
||||||
void publish(Event e) {
|
void publish(Event e) {
|
||||||
executor.execute({publishInternal(e)} as Runnable)
|
executor.execute({publishInternal(e)} as Runnable)
|
||||||
}
|
}
|
||||||
|
|
||||||
private void publishInternal(Event e) {
|
private void publishInternal(Event e) {
|
||||||
log.fine "publishing event $e of type ${e.getClass().getSimpleName()} event $e"
|
log.fine "publishing event $e of type ${e.getClass().getSimpleName()} event $e"
|
||||||
def currentHandlers
|
def currentHandlers
|
||||||
final def clazz = e.getClass()
|
final def clazz = e.getClass()
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
currentHandlers = handlers.getOrDefault(clazz, [])
|
currentHandlers = handlers.getOrDefault(clazz, [])
|
||||||
}
|
}
|
||||||
currentHandlers.each {
|
currentHandlers.each {
|
||||||
try {
|
try {
|
||||||
it."on${clazz.getSimpleName()}"(e)
|
it."on${clazz.getSimpleName()}"(e)
|
||||||
} catch (Exception bad) {
|
} catch (Exception bad) {
|
||||||
log.log(Level.SEVERE, "exception dispatching event",bad)
|
log.log(Level.SEVERE, "exception dispatching event",bad)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void register(Class<? extends Event> eventType, def handler) {
|
synchronized void register(Class<? extends Event> eventType, def handler) {
|
||||||
log.info "Registering $handler for type $eventType"
|
log.info "Registering $handler for type $eventType"
|
||||||
def currentHandlers = handlers.get(eventType)
|
def currentHandlers = handlers.get(eventType)
|
||||||
if (currentHandlers == null) {
|
if (currentHandlers == null) {
|
||||||
currentHandlers = new CopyOnWriteArrayList()
|
currentHandlers = new CopyOnWriteArrayList()
|
||||||
handlers.put(eventType, currentHandlers)
|
handlers.put(eventType, currentHandlers)
|
||||||
}
|
}
|
||||||
currentHandlers.add handler
|
currentHandlers.add handler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
synchronized void unregister(Class<? extends Event> eventType, def handler) {
|
||||||
|
log.info("Unregistering $handler for type $eventType")
|
||||||
|
handlers[eventType]?.remove(handler)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,77 +11,137 @@ class MuWireSettings {
|
|||||||
|
|
||||||
final boolean isLeaf
|
final boolean isLeaf
|
||||||
boolean allowUntrusted
|
boolean allowUntrusted
|
||||||
|
boolean allowTrustLists
|
||||||
|
int trustListInterval
|
||||||
|
Set<Persona> trustSubscriptions
|
||||||
int downloadRetryInterval
|
int downloadRetryInterval
|
||||||
int updateCheckInterval
|
int updateCheckInterval
|
||||||
|
boolean autoDownloadUpdate
|
||||||
|
String updateType
|
||||||
String nickname
|
String nickname
|
||||||
File downloadLocation
|
File downloadLocation
|
||||||
CrawlerResponse crawlerResponse
|
CrawlerResponse crawlerResponse
|
||||||
boolean shareDownloadedFiles
|
boolean shareDownloadedFiles
|
||||||
Set<String> watchedDirectories
|
Set<String> watchedDirectories
|
||||||
|
float downloadSequentialRatio
|
||||||
|
int hostClearInterval
|
||||||
|
int meshExpiration
|
||||||
|
boolean embeddedRouter
|
||||||
|
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.get("allowUntrusted","true"))
|
allowUntrusted = Boolean.valueOf(props.getProperty("allowUntrusted","true"))
|
||||||
crawlerResponse = CrawlerResponse.valueOf(props.get("crawlerResponse","REGISTERED"))
|
allowTrustLists = Boolean.valueOf(props.getProperty("allowTrustLists","true"))
|
||||||
|
trustListInterval = Integer.valueOf(props.getProperty("trustListInterval","1"))
|
||||||
|
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")))
|
||||||
downloadRetryInterval = Integer.parseInt(props.getProperty("downloadRetryInterval","1"))
|
downloadRetryInterval = Integer.parseInt(props.getProperty("downloadRetryInterval","1"))
|
||||||
updateCheckInterval = Integer.parseInt(props.getProperty("updateCheckInterval","24"))
|
updateCheckInterval = Integer.parseInt(props.getProperty("updateCheckInterval","24"))
|
||||||
|
autoDownloadUpdate = Boolean.parseBoolean(props.getProperty("autoDownloadUpdate","true"))
|
||||||
|
updateType = props.getProperty("updateType","jar")
|
||||||
shareDownloadedFiles = Boolean.parseBoolean(props.getProperty("shareDownloadedFiles","true"))
|
shareDownloadedFiles = Boolean.parseBoolean(props.getProperty("shareDownloadedFiles","true"))
|
||||||
|
downloadSequentialRatio = Float.valueOf(props.getProperty("downloadSequentialRatio","0.8"))
|
||||||
|
hostClearInterval = Integer.valueOf(props.getProperty("hostClearInterval","60"))
|
||||||
|
meshExpiration = Integer.valueOf(props.getProperty("meshExpiration","60"))
|
||||||
|
embeddedRouter = Boolean.valueOf(props.getProperty("embeddedRouter","false"))
|
||||||
|
inBw = Integer.valueOf(props.getProperty("inBw","256"))
|
||||||
|
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<>()
|
||||||
|
if (props.containsKey("trustSubscriptions")) {
|
||||||
|
props.getProperty("trustSubscriptions").split(",").each {
|
||||||
|
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()
|
||||||
props.setProperty("leaf", isLeaf.toString())
|
props.setProperty("leaf", isLeaf.toString())
|
||||||
props.setProperty("allowUntrusted", allowUntrusted.toString())
|
props.setProperty("allowUntrusted", allowUntrusted.toString())
|
||||||
|
props.setProperty("allowTrustLists", String.valueOf(allowTrustLists))
|
||||||
|
props.setProperty("trustListInterval", String.valueOf(trustListInterval))
|
||||||
props.setProperty("crawlerResponse", crawlerResponse.toString())
|
props.setProperty("crawlerResponse", crawlerResponse.toString())
|
||||||
props.setProperty("nickname", nickname)
|
props.setProperty("nickname", nickname)
|
||||||
props.setProperty("downloadLocation", downloadLocation.getAbsolutePath())
|
props.setProperty("downloadLocation", downloadLocation.getAbsolutePath())
|
||||||
props.setProperty("downloadRetryInterval", String.valueOf(downloadRetryInterval))
|
props.setProperty("downloadRetryInterval", String.valueOf(downloadRetryInterval))
|
||||||
props.setProperty("updateCheckInterval", String.valueOf(updateCheckInterval))
|
props.setProperty("updateCheckInterval", String.valueOf(updateCheckInterval))
|
||||||
|
props.setProperty("autoDownloadUpdate", String.valueOf(autoDownloadUpdate))
|
||||||
|
props.setProperty("updateType",String.valueOf(updateType))
|
||||||
props.setProperty("shareDownloadedFiles", String.valueOf(shareDownloadedFiles))
|
props.setProperty("shareDownloadedFiles", String.valueOf(shareDownloadedFiles))
|
||||||
|
props.setProperty("downloadSequentialRatio", String.valueOf(downloadSequentialRatio))
|
||||||
|
props.setProperty("hostClearInterval", String.valueOf(hostClearInterval))
|
||||||
|
props.setProperty("meshExpiration", String.valueOf(meshExpiration))
|
||||||
|
props.setProperty("embeddedRouter", String.valueOf(embeddedRouter))
|
||||||
|
props.setProperty("inBw", String.valueOf(inBw))
|
||||||
|
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)
|
||||||
|
|
||||||
|
if (!trustSubscriptions.isEmpty()) {
|
||||||
|
String encoded = trustSubscriptions.stream().
|
||||||
|
map({it.toBase64()}).
|
||||||
collect(Collectors.joining(","))
|
collect(Collectors.joining(","))
|
||||||
props.setProperty("watchedDirectories", encoded)
|
props.setProperty("trustSubscriptions", encoded)
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
@@ -82,4 +82,13 @@ public class Persona {
|
|||||||
Persona other = (Persona)o
|
Persona other = (Persona)o
|
||||||
name.equals(other.name) && destination.equals(other.destination)
|
name.equals(other.name) && destination.equals(other.destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void main(String []args) {
|
||||||
|
if (args.length != 1) {
|
||||||
|
println "This utility decodes a bas64-encoded persona"
|
||||||
|
System.exit(1)
|
||||||
|
}
|
||||||
|
Persona p = new Persona(new ByteArrayInputStream(Base64.decode(args[0])))
|
||||||
|
println p.getHumanReadableName()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -22,103 +22,107 @@ import net.i2p.data.Destination
|
|||||||
@Log
|
@Log
|
||||||
abstract class Connection implements Closeable {
|
abstract class Connection implements Closeable {
|
||||||
|
|
||||||
final EventBus eventBus
|
private static final int SEARCHES = 10
|
||||||
final Endpoint endpoint
|
private static final long INTERVAL = 1000
|
||||||
final boolean incoming
|
|
||||||
final HostCache hostCache
|
final EventBus eventBus
|
||||||
|
final Endpoint endpoint
|
||||||
|
final boolean incoming
|
||||||
|
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<>()
|
||||||
|
|
||||||
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 = [:]
|
||||||
@@ -136,27 +140,45 @@ 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() {
|
||||||
|
final long now = System.currentTimeMillis()
|
||||||
|
if (searchTimestamps.size() < SEARCHES) {
|
||||||
|
searchTimestamps.addLast(now)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
Long oldest = searchTimestamps.getFirst()
|
||||||
|
if (now - oldest.longValue() < INTERVAL)
|
||||||
|
return true
|
||||||
|
searchTimestamps.addLast(now)
|
||||||
|
searchTimestamps.removeFirst()
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
protected void handleSearch(def search) {
|
protected void handleSearch(def search) {
|
||||||
|
if (throttleSearch()) {
|
||||||
|
log.info("dropping excessive search")
|
||||||
|
return
|
||||||
|
}
|
||||||
UUID uuid = UUID.fromString(search.uuid)
|
UUID uuid = UUID.fromString(search.uuid)
|
||||||
byte [] infohash = null
|
byte [] infohash = null
|
||||||
if (search.infohash != null) {
|
if (search.infohash != null) {
|
||||||
|
@@ -14,6 +14,7 @@ import com.muwire.core.hostcache.HostCache
|
|||||||
import com.muwire.core.trust.TrustLevel
|
import com.muwire.core.trust.TrustLevel
|
||||||
import com.muwire.core.trust.TrustService
|
import com.muwire.core.trust.TrustService
|
||||||
import com.muwire.core.upload.UploadManager
|
import com.muwire.core.upload.UploadManager
|
||||||
|
import com.muwire.core.util.DataUtil
|
||||||
import com.muwire.core.search.InvalidSearchResultException
|
import com.muwire.core.search.InvalidSearchResultException
|
||||||
import com.muwire.core.search.ResultsParser
|
import com.muwire.core.search.ResultsParser
|
||||||
import com.muwire.core.search.SearchManager
|
import com.muwire.core.search.SearchManager
|
||||||
@@ -28,125 +29,128 @@ 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
|
||||||
case (byte)'P':
|
case (byte)'P':
|
||||||
processPOST(e)
|
processPOST(e)
|
||||||
break
|
break
|
||||||
default:
|
case (byte)'T':
|
||||||
throw new Exception("Invalid read $read")
|
processTRUST(e)
|
||||||
}
|
break
|
||||||
} catch (Exception ex) {
|
default:
|
||||||
log.log(Level.WARNING, "incoming connection failed",ex)
|
throw new Exception("Invalid read $read")
|
||||||
e.close()
|
}
|
||||||
eventBus.publish new ConnectionEvent(endpoint: e, incoming: true, leaf: null, status: ConnectionAttemptStatus.FAILED)
|
} catch (Exception ex) {
|
||||||
}
|
log.log(Level.WARNING, "incoming connection failed",ex)
|
||||||
}
|
e.close()
|
||||||
|
eventBus.publish new ConnectionEvent(endpoint: e, incoming: true, leaf: null, status: ConnectionAttemptStatus.FAILED)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void processMuWire(Endpoint e) {
|
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)
|
||||||
@@ -156,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]
|
||||||
@@ -243,4 +247,43 @@ class ConnectionAcceptor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void processTRUST(Endpoint e) {
|
||||||
|
byte[] RUST = new byte[6]
|
||||||
|
DataInputStream dis = new DataInputStream(e.getInputStream())
|
||||||
|
dis.readFully(RUST)
|
||||||
|
if (RUST != "RUST\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
|
throw new IOException("Invalid TRUST connection")
|
||||||
|
String header
|
||||||
|
while ((header = DataUtil.readTillRN(dis)) != ""); // ignore headers for now
|
||||||
|
|
||||||
|
OutputStream os = e.getOutputStream()
|
||||||
|
if (!settings.allowTrustLists) {
|
||||||
|
os.write("403 Not Allowed\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
|
os.flush()
|
||||||
|
e.close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
os.write("200 OK\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
|
List<Persona> good = new ArrayList<>(trustService.good.values())
|
||||||
|
int size = Math.min(Short.MAX_VALUE * 2, good.size())
|
||||||
|
good = good.subList(0, size)
|
||||||
|
DataOutputStream dos = new DataOutputStream(os)
|
||||||
|
dos.writeShort(size)
|
||||||
|
good.each {
|
||||||
|
it.write(dos)
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Persona> bad = new ArrayList<>(trustService.bad.values())
|
||||||
|
size = Math.min(Short.MAX_VALUE * 2, bad.size())
|
||||||
|
bad = bad.subList(0, size)
|
||||||
|
dos.writeShort(size)
|
||||||
|
bad.each {
|
||||||
|
it.write(dos)
|
||||||
|
}
|
||||||
|
|
||||||
|
dos.flush()
|
||||||
|
e.close()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -22,162 +22,162 @@ import net.i2p.util.ConcurrentHashSet
|
|||||||
@Log
|
@Log
|
||||||
class ConnectionEstablisher {
|
class ConnectionEstablisher {
|
||||||
|
|
||||||
private static final int CONCURRENT = 4
|
private static final int CONCURRENT = 4
|
||||||
|
|
||||||
final EventBus eventBus
|
final EventBus eventBus
|
||||||
final I2PConnector i2pConnector
|
final I2PConnector i2pConnector
|
||||||
final MuWireSettings settings
|
final MuWireSettings settings
|
||||||
final ConnectionManager connectionManager
|
final ConnectionManager connectionManager
|
||||||
final HostCache hostCache
|
final HostCache hostCache
|
||||||
|
|
||||||
final Timer timer
|
final Timer timer
|
||||||
final ExecutorService executor
|
final ExecutorService executor
|
||||||
|
|
||||||
final Set inProgress = new ConcurrentHashSet()
|
final Set inProgress = new ConcurrentHashSet()
|
||||||
|
|
||||||
ConnectionEstablisher(){}
|
ConnectionEstablisher(){}
|
||||||
|
|
||||||
ConnectionEstablisher(EventBus eventBus, I2PConnector i2pConnector, MuWireSettings settings,
|
ConnectionEstablisher(EventBus eventBus, I2PConnector i2pConnector, MuWireSettings settings,
|
||||||
ConnectionManager connectionManager, HostCache hostCache) {
|
ConnectionManager connectionManager, HostCache hostCache) {
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.i2pConnector = i2pConnector
|
this.i2pConnector = i2pConnector
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
this.connectionManager = connectionManager
|
this.connectionManager = connectionManager
|
||||||
this.hostCache = hostCache
|
this.hostCache = hostCache
|
||||||
timer = new Timer("connection-timer",true)
|
timer = new Timer("connection-timer",true)
|
||||||
executor = Executors.newFixedThreadPool(CONCURRENT, { r ->
|
executor = Executors.newFixedThreadPool(CONCURRENT, { r ->
|
||||||
def rv = new Thread(r)
|
def rv = new Thread(r)
|
||||||
rv.setDaemon(true)
|
rv.setDaemon(true)
|
||||||
rv.setName("connector-${System.currentTimeMillis()}")
|
rv.setName("connector-${System.currentTimeMillis()}")
|
||||||
rv
|
rv
|
||||||
} as ThreadFactory)
|
} as ThreadFactory)
|
||||||
}
|
}
|
||||||
|
|
||||||
void start() {
|
void start() {
|
||||||
timer.schedule({connectIfNeeded()} as TimerTask, 100, 1000)
|
timer.schedule({connectIfNeeded()} as TimerTask, 100, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
timer.cancel()
|
timer.cancel()
|
||||||
executor.shutdownNow()
|
executor.shutdownNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
private void connectIfNeeded() {
|
private void connectIfNeeded() {
|
||||||
if (!connectionManager.needsConnections())
|
if (!connectionManager.needsConnections())
|
||||||
return
|
return
|
||||||
if (inProgress.size() >= CONCURRENT)
|
if (inProgress.size() >= CONCURRENT)
|
||||||
return
|
return
|
||||||
|
|
||||||
def toTry = null
|
def toTry = null
|
||||||
for (int i = 0; i < 5; i++) {
|
for (int i = 0; i < 5; i++) {
|
||||||
toTry = hostCache.getHosts(1)
|
toTry = hostCache.getHosts(1)
|
||||||
if (toTry.isEmpty())
|
if (toTry.isEmpty())
|
||||||
return
|
return
|
||||||
toTry = toTry[0]
|
toTry = toTry[0]
|
||||||
if (!connectionManager.isConnected(toTry) &&
|
if (!connectionManager.isConnected(toTry) &&
|
||||||
!inProgress.contains(toTry)) {
|
!inProgress.contains(toTry)) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (toTry == null)
|
if (toTry == null)
|
||||||
return
|
return
|
||||||
if (!connectionManager.isConnected(toTry) && inProgress.add(toTry))
|
if (!connectionManager.isConnected(toTry) && inProgress.add(toTry))
|
||||||
executor.execute({connect(toTry)} as Runnable)
|
executor.execute({connect(toTry)} as Runnable)
|
||||||
}
|
}
|
||||||
|
|
||||||
private void connect(Destination toTry) {
|
private void connect(Destination toTry) {
|
||||||
log.info("starting connect to ${toTry.toBase32()}")
|
log.info("starting connect to ${toTry.toBase32()}")
|
||||||
try {
|
try {
|
||||||
def endpoint = i2pConnector.connect(toTry)
|
def endpoint = i2pConnector.connect(toTry)
|
||||||
log.info("successful transport connect to ${toTry.toBase32()}")
|
log.info("successful transport connect to ${toTry.toBase32()}")
|
||||||
|
|
||||||
// outgoing handshake
|
// outgoing handshake
|
||||||
endpoint.outputStream.write("MuWire ".bytes)
|
endpoint.outputStream.write("MuWire ".bytes)
|
||||||
def type = settings.isLeaf() ? "leaf" : "peer"
|
def type = settings.isLeaf() ? "leaf" : "peer"
|
||||||
endpoint.outputStream.write(type.bytes)
|
endpoint.outputStream.write(type.bytes)
|
||||||
endpoint.outputStream.flush()
|
endpoint.outputStream.flush()
|
||||||
|
|
||||||
InputStream is = endpoint.inputStream
|
InputStream is = endpoint.inputStream
|
||||||
int read = is.read()
|
int read = is.read()
|
||||||
if (read == -1) {
|
if (read == -1) {
|
||||||
fail endpoint
|
fail endpoint
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch(read) {
|
switch(read) {
|
||||||
case (byte)'O': readK(endpoint); break
|
case (byte)'O': readK(endpoint); break
|
||||||
case (byte)'R': readEJECT(endpoint); break
|
case (byte)'R': readEJECT(endpoint); break
|
||||||
default :
|
default :
|
||||||
log.warning("unknown response $read")
|
log.warning("unknown response $read")
|
||||||
fail endpoint
|
fail endpoint
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.log(Level.WARNING, "Couldn't connect to ${toTry.toBase32()}", e)
|
log.log(Level.WARNING, "Couldn't connect to ${toTry.toBase32()}", e)
|
||||||
def endpoint = new Endpoint(toTry, null, null, null)
|
def endpoint = new Endpoint(toTry, null, null, null)
|
||||||
fail(endpoint)
|
fail(endpoint)
|
||||||
} finally {
|
} finally {
|
||||||
inProgress.remove(toTry)
|
inProgress.remove(toTry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fail(Endpoint endpoint) {
|
private void fail(Endpoint endpoint) {
|
||||||
endpoint.close()
|
endpoint.close()
|
||||||
eventBus.publish(new ConnectionEvent(endpoint: endpoint, incoming: false, leaf: false, status: ConnectionAttemptStatus.FAILED))
|
eventBus.publish(new ConnectionEvent(endpoint: endpoint, incoming: false, leaf: false, status: ConnectionAttemptStatus.FAILED))
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readK(Endpoint e) {
|
private void readK(Endpoint e) {
|
||||||
int read = e.inputStream.read()
|
int read = e.inputStream.read()
|
||||||
if (read != 'K') {
|
if (read != 'K') {
|
||||||
log.warning("unknown response after O: $read")
|
log.warning("unknown response after O: $read")
|
||||||
fail e
|
fail e
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("connection to ${e.destination.toBase32()} established")
|
log.info("connection to ${e.destination.toBase32()} established")
|
||||||
|
|
||||||
// wrap into deflater / inflater streams and publish
|
// wrap into deflater / inflater streams and publish
|
||||||
def wrapped = new Endpoint(e.destination, new InflaterInputStream(e.inputStream), new DeflaterOutputStream(e.outputStream, true), e.toClose)
|
def wrapped = new Endpoint(e.destination, new InflaterInputStream(e.inputStream), new DeflaterOutputStream(e.outputStream, true), e.toClose)
|
||||||
eventBus.publish(new ConnectionEvent(endpoint: wrapped, incoming: false, leaf: false, status: ConnectionAttemptStatus.SUCCESSFUL))
|
eventBus.publish(new ConnectionEvent(endpoint: wrapped, incoming: false, leaf: false, status: ConnectionAttemptStatus.SUCCESSFUL))
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readEJECT(Endpoint e) {
|
private void readEJECT(Endpoint e) {
|
||||||
byte[] eject = "EJECT".bytes
|
byte[] eject = "EJECT".bytes
|
||||||
for (int i = 0; i < eject.length; i++) {
|
for (int i = 0; i < eject.length; i++) {
|
||||||
int read = e.inputStream.read()
|
int read = e.inputStream.read()
|
||||||
if (read != eject[i]) {
|
if (read != eject[i]) {
|
||||||
log.warning("Unknown response after R at position $i")
|
log.warning("Unknown response after R at position $i")
|
||||||
fail e
|
fail e
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.info("connection to ${e.destination.toBase32()} rejected")
|
log.info("connection to ${e.destination.toBase32()} rejected")
|
||||||
|
|
||||||
|
|
||||||
eventBus.publish(new ConnectionEvent(endpoint: e, incoming: false, leaf: false, status: ConnectionAttemptStatus.REJECTED))
|
eventBus.publish(new ConnectionEvent(endpoint: e, incoming: false, leaf: false, status: ConnectionAttemptStatus.REJECTED))
|
||||||
try {
|
try {
|
||||||
DataInputStream dais = new DataInputStream(e.inputStream)
|
DataInputStream dais = new DataInputStream(e.inputStream)
|
||||||
int payloadSize = dais.readUnsignedShort()
|
int payloadSize = dais.readUnsignedShort()
|
||||||
byte[] payload = new byte[payloadSize]
|
byte[] payload = new byte[payloadSize]
|
||||||
dais.readFully(payload)
|
dais.readFully(payload)
|
||||||
|
|
||||||
def json = new JsonSlurper()
|
def json = new JsonSlurper()
|
||||||
json = json.parse(payload)
|
json = json.parse(payload)
|
||||||
|
|
||||||
if (json.tryHosts == null) {
|
if (json.tryHosts == null) {
|
||||||
log.warning("post-rejection json didn't contain hosts to try")
|
log.warning("post-rejection json didn't contain hosts to try")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
json.tryHosts.asList().each {
|
json.tryHosts.asList().each {
|
||||||
Destination suggested = new Destination(it)
|
Destination suggested = new Destination(it)
|
||||||
eventBus.publish(new HostDiscoveredEvent(destination: suggested))
|
eventBus.publish(new HostDiscoveredEvent(destination: suggested))
|
||||||
}
|
}
|
||||||
} catch (Exception ignore) {
|
} catch (Exception ignore) {
|
||||||
log.log(Level.WARNING,"Problem parsing post-rejection payload",ignore)
|
log.log(Level.WARNING,"Problem parsing post-rejection payload",ignore)
|
||||||
} finally {
|
} finally {
|
||||||
// the end
|
// the end
|
||||||
e.close()
|
e.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isInProgress(Destination d) {
|
public boolean isInProgress(Destination d) {
|
||||||
inProgress.contains(d)
|
inProgress.contains(d)
|
||||||
|
@@ -6,14 +6,14 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
class ConnectionEvent extends Event {
|
class ConnectionEvent extends Event {
|
||||||
|
|
||||||
Endpoint endpoint
|
Endpoint endpoint
|
||||||
boolean incoming
|
boolean incoming
|
||||||
Boolean leaf // can be null if uknown
|
Boolean leaf // can be null if uknown
|
||||||
ConnectionAttemptStatus status
|
ConnectionAttemptStatus status
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
"ConnectionEvent ${super.toString()} endpoint: $endpoint incoming: $incoming leaf : $leaf status : $status"
|
"ConnectionEvent ${super.toString()} endpoint: $endpoint incoming: $incoming leaf : $leaf status : $status"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -12,63 +12,63 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
abstract class ConnectionManager {
|
abstract class ConnectionManager {
|
||||||
|
|
||||||
private static final int PING_TIME = 20000
|
private static final int PING_TIME = 20000
|
||||||
|
|
||||||
final EventBus eventBus
|
final EventBus eventBus
|
||||||
|
|
||||||
private final Timer timer
|
private final Timer timer
|
||||||
|
|
||||||
protected final HostCache hostCache
|
protected final HostCache hostCache
|
||||||
protected final Persona me
|
protected final Persona me
|
||||||
protected final MuWireSettings settings
|
protected final MuWireSettings settings
|
||||||
|
|
||||||
ConnectionManager() {}
|
ConnectionManager() {}
|
||||||
|
|
||||||
ConnectionManager(EventBus eventBus, Persona me, HostCache hostCache, MuWireSettings settings) {
|
ConnectionManager(EventBus eventBus, Persona me, HostCache hostCache, MuWireSettings settings) {
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.me = me
|
this.me = me
|
||||||
this.hostCache = hostCache
|
this.hostCache = hostCache
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
this.timer = new Timer("connections-pinger",true)
|
this.timer = new Timer("connections-pinger",true)
|
||||||
}
|
}
|
||||||
|
|
||||||
void start() {
|
void start() {
|
||||||
timer.schedule({sendPings()} as TimerTask, 1000,1000)
|
timer.schedule({sendPings()} as TimerTask, 1000,1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
timer.cancel()
|
timer.cancel()
|
||||||
getConnections().each { it.close() }
|
getConnections().each { it.close() }
|
||||||
}
|
}
|
||||||
|
|
||||||
void onTrustEvent(TrustEvent e) {
|
void onTrustEvent(TrustEvent e) {
|
||||||
if (e.level == TrustLevel.DISTRUSTED)
|
if (e.level == TrustLevel.DISTRUSTED)
|
||||||
drop(e.persona.destination)
|
drop(e.persona.destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract void drop(Destination d)
|
abstract void drop(Destination d)
|
||||||
|
|
||||||
abstract Collection<Connection> getConnections()
|
abstract Collection<Connection> getConnections()
|
||||||
|
|
||||||
protected abstract int getDesiredConnections()
|
protected abstract int getDesiredConnections()
|
||||||
|
|
||||||
boolean needsConnections() {
|
boolean needsConnections() {
|
||||||
return getConnections().size() < getDesiredConnections()
|
return getConnections().size() < getDesiredConnections()
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract boolean isConnected(Destination d)
|
abstract boolean isConnected(Destination d)
|
||||||
|
|
||||||
abstract void onConnectionEvent(ConnectionEvent e)
|
abstract void onConnectionEvent(ConnectionEvent e)
|
||||||
|
|
||||||
abstract void onDisconnectionEvent(DisconnectionEvent e)
|
abstract void onDisconnectionEvent(DisconnectionEvent e)
|
||||||
|
|
||||||
abstract void shutdown()
|
abstract void shutdown()
|
||||||
|
|
||||||
protected void sendPings() {
|
protected void sendPings() {
|
||||||
final long now = System.currentTimeMillis()
|
final long now = System.currentTimeMillis()
|
||||||
getConnections().each {
|
getConnections().each {
|
||||||
if (now - it.lastPingSentTime > PING_TIME)
|
if (now - it.lastPingSentTime > PING_TIME)
|
||||||
it.sendPing()
|
it.sendPing()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,10 +6,10 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
class DisconnectionEvent extends Event {
|
class DisconnectionEvent extends Event {
|
||||||
|
|
||||||
Destination destination
|
Destination destination
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
"DisconnectionEvent ${super.toString()} destination:${destination.toBase32()}"
|
"DisconnectionEvent ${super.toString()} destination:${destination.toBase32()}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,39 +8,39 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
@Log
|
@Log
|
||||||
class Endpoint implements Closeable {
|
class Endpoint implements Closeable {
|
||||||
final Destination destination
|
final Destination destination
|
||||||
final InputStream inputStream
|
final InputStream inputStream
|
||||||
final OutputStream outputStream
|
final OutputStream outputStream
|
||||||
final def toClose
|
final def toClose
|
||||||
|
|
||||||
private final AtomicBoolean closed = new AtomicBoolean()
|
private final AtomicBoolean closed = new AtomicBoolean()
|
||||||
|
|
||||||
Endpoint(Destination destination, InputStream inputStream, OutputStream outputStream, def toClose) {
|
Endpoint(Destination destination, InputStream inputStream, OutputStream outputStream, def toClose) {
|
||||||
this.destination = destination
|
this.destination = destination
|
||||||
this.inputStream = inputStream
|
this.inputStream = inputStream
|
||||||
this.outputStream = outputStream
|
this.outputStream = outputStream
|
||||||
this.toClose = toClose
|
this.toClose = toClose
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
if (!closed.compareAndSet(false, true)) {
|
if (!closed.compareAndSet(false, true)) {
|
||||||
log.log(Level.WARNING,"Close loop detected for ${destination.toBase32()}", new Exception())
|
log.log(Level.WARNING,"Close loop detected for ${destination.toBase32()}", new Exception())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (inputStream != null) {
|
if (inputStream != null) {
|
||||||
try {inputStream.close()} catch (Exception ignore) {}
|
try {inputStream.close()} catch (Exception ignore) {}
|
||||||
}
|
}
|
||||||
if (outputStream != null) {
|
if (outputStream != null) {
|
||||||
try {outputStream.close()} catch (Exception ignore) {}
|
try {outputStream.close()} catch (Exception ignore) {}
|
||||||
}
|
}
|
||||||
if (toClose != null) {
|
if (toClose != null) {
|
||||||
try {toClose.reset()} catch (Exception ignore) {}
|
try {toClose.reset()} catch (Exception ignore) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
"destination: ${destination.toBase32()}"
|
"destination: ${destination.toBase32()}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,18 +5,18 @@ import net.i2p.client.streaming.I2PSocketManager
|
|||||||
|
|
||||||
class I2PAcceptor {
|
class I2PAcceptor {
|
||||||
|
|
||||||
final I2PSocketManager socketManager
|
final I2PSocketManager socketManager
|
||||||
final I2PServerSocket serverSocket
|
final I2PServerSocket serverSocket
|
||||||
|
|
||||||
I2PAcceptor() {}
|
I2PAcceptor() {}
|
||||||
|
|
||||||
I2PAcceptor(I2PSocketManager socketManager) {
|
I2PAcceptor(I2PSocketManager socketManager) {
|
||||||
this.socketManager = socketManager
|
this.socketManager = socketManager
|
||||||
this.serverSocket = socketManager.getServerSocket()
|
this.serverSocket = socketManager.getServerSocket()
|
||||||
}
|
}
|
||||||
|
|
||||||
Endpoint accept() {
|
Endpoint accept() {
|
||||||
def socket = serverSocket.accept()
|
def socket = serverSocket.accept()
|
||||||
new Endpoint(socket.getPeerDestination(), socket.getInputStream(), socket.getOutputStream(), socket)
|
new Endpoint(socket.getPeerDestination(), socket.getInputStream(), socket.getOutputStream(), socket)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,17 +5,17 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
class I2PConnector {
|
class I2PConnector {
|
||||||
|
|
||||||
final I2PSocketManager socketManager
|
final I2PSocketManager socketManager
|
||||||
|
|
||||||
I2PConnector() {}
|
I2PConnector() {}
|
||||||
|
|
||||||
I2PConnector(I2PSocketManager socketManager) {
|
I2PConnector(I2PSocketManager socketManager) {
|
||||||
this.socketManager = socketManager
|
this.socketManager = socketManager
|
||||||
}
|
}
|
||||||
|
|
||||||
Endpoint connect(Destination dest) {
|
Endpoint connect(Destination dest) {
|
||||||
def socket = socketManager.connect(dest)
|
def socket = socketManager.connect(dest)
|
||||||
new Endpoint(dest, socket.getInputStream(), socket.getOutputStream(), socket)
|
new Endpoint(dest, socket.getInputStream(), socket.getOutputStream(), socket)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -17,21 +17,21 @@ import net.i2p.data.Destination
|
|||||||
*/
|
*/
|
||||||
class LeafConnection extends Connection {
|
class LeafConnection extends Connection {
|
||||||
|
|
||||||
public LeafConnection(EventBus eventBus, Endpoint endpoint, HostCache hostCache,
|
public LeafConnection(EventBus eventBus, Endpoint endpoint, HostCache hostCache,
|
||||||
TrustService trustService, MuWireSettings settings) {
|
TrustService trustService, MuWireSettings settings) {
|
||||||
super(eventBus, endpoint, true, hostCache, trustService, settings);
|
super(eventBus, endpoint, true, hostCache, trustService, settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void read() {
|
protected void read() {
|
||||||
// TODO Auto-generated method stub
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void write(Object message) {
|
protected void write(Object message) {
|
||||||
// TODO Auto-generated method stub
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -14,21 +14,21 @@ import net.i2p.data.Destination
|
|||||||
@Log
|
@Log
|
||||||
class LeafConnectionManager extends ConnectionManager {
|
class LeafConnectionManager extends ConnectionManager {
|
||||||
|
|
||||||
final int maxConnections
|
final int maxConnections
|
||||||
|
|
||||||
final Map<Destination, UltrapeerConnection> connections = new ConcurrentHashMap()
|
final Map<Destination, UltrapeerConnection> connections = new ConcurrentHashMap()
|
||||||
|
|
||||||
public LeafConnectionManager(EventBus eventBus, Persona me, int maxConnections,
|
public LeafConnectionManager(EventBus eventBus, Persona me, int maxConnections,
|
||||||
HostCache hostCache, MuWireSettings settings) {
|
HostCache hostCache, MuWireSettings settings) {
|
||||||
super(eventBus, me, hostCache, settings)
|
super(eventBus, me, hostCache, settings)
|
||||||
this.maxConnections = maxConnections
|
this.maxConnections = maxConnections
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void drop(Destination d) {
|
public void drop(Destination d) {
|
||||||
// TODO Auto-generated method stub
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onQueryEvent(QueryEvent e) {
|
void onQueryEvent(QueryEvent e) {
|
||||||
if (me.destination == e.receivedOn) {
|
if (me.destination == e.receivedOn) {
|
||||||
@@ -37,41 +37,41 @@ class LeafConnectionManager extends ConnectionManager {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<Connection> getConnections() {
|
public Collection<Connection> getConnections() {
|
||||||
connections.values()
|
connections.values()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int getDesiredConnections() {
|
protected int getDesiredConnections() {
|
||||||
return maxConnections;
|
return maxConnections;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isConnected(Destination d) {
|
public boolean isConnected(Destination d) {
|
||||||
connections.containsKey(d)
|
connections.containsKey(d)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConnectionEvent(ConnectionEvent e) {
|
public void onConnectionEvent(ConnectionEvent e) {
|
||||||
if (e.incoming || e.leaf) {
|
if (e.incoming || e.leaf) {
|
||||||
log.severe("Got inconsistent event as a leaf! $e")
|
log.severe("Got inconsistent event as a leaf! $e")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (e.status != ConnectionAttemptStatus.SUCCESSFUL)
|
if (e.status != ConnectionAttemptStatus.SUCCESSFUL)
|
||||||
return
|
return
|
||||||
|
|
||||||
Connection c = new UltrapeerConnection(eventBus, e.endpoint)
|
Connection c = new UltrapeerConnection(eventBus, e.endpoint)
|
||||||
connections.put(e.endpoint.destination, c)
|
connections.put(e.endpoint.destination, c)
|
||||||
c.start()
|
c.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisconnectionEvent(DisconnectionEvent e) {
|
public void onDisconnectionEvent(DisconnectionEvent e) {
|
||||||
def removed = connections.remove(e.destination)
|
def removed = connections.remove(e.destination)
|
||||||
if (removed == null)
|
if (removed == null)
|
||||||
log.severe("removed destination not present in connection manager ${e.destination.toBase32()}")
|
log.severe("removed destination not present in connection manager ${e.destination.toBase32()}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void shutdown() {
|
void shutdown() {
|
||||||
|
@@ -21,62 +21,62 @@ import net.i2p.data.Destination
|
|||||||
@Log
|
@Log
|
||||||
class PeerConnection extends Connection {
|
class PeerConnection extends Connection {
|
||||||
|
|
||||||
private final DataInputStream dis
|
private final DataInputStream dis
|
||||||
private final DataOutputStream dos
|
private final DataOutputStream dos
|
||||||
|
|
||||||
private final byte[] readHeader = new byte[3]
|
private final byte[] readHeader = new byte[3]
|
||||||
private final byte[] writeHeader = new byte[3]
|
private final byte[] writeHeader = new byte[3]
|
||||||
|
|
||||||
private final JsonSlurper slurper = new JsonSlurper()
|
private final JsonSlurper slurper = new JsonSlurper()
|
||||||
|
|
||||||
public PeerConnection(EventBus eventBus, Endpoint endpoint,
|
public PeerConnection(EventBus eventBus, Endpoint endpoint,
|
||||||
boolean incoming, HostCache hostCache, TrustService trustService,
|
boolean incoming, HostCache hostCache, TrustService trustService,
|
||||||
MuWireSettings settings) {
|
MuWireSettings settings) {
|
||||||
super(eventBus, endpoint, incoming, hostCache, trustService, settings)
|
super(eventBus, endpoint, incoming, hostCache, trustService, settings)
|
||||||
this.dis = new DataInputStream(endpoint.inputStream)
|
this.dis = new DataInputStream(endpoint.inputStream)
|
||||||
this.dos = new DataOutputStream(endpoint.outputStream)
|
this.dos = new DataOutputStream(endpoint.outputStream)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void read() {
|
protected void read() {
|
||||||
dis.readFully(readHeader)
|
dis.readFully(readHeader)
|
||||||
int length = DataUtil.readLength(readHeader)
|
int length = DataUtil.readLength(readHeader)
|
||||||
log.fine("$name read length $length")
|
log.fine("$name read length $length")
|
||||||
|
|
||||||
byte[] payload = new byte[length]
|
byte[] payload = new byte[length]
|
||||||
dis.readFully(payload)
|
dis.readFully(payload)
|
||||||
|
|
||||||
if ((readHeader[0] & (byte)0x80) == 0x80) {
|
if ((readHeader[0] & (byte)0x80) == 0x80) {
|
||||||
// TODO process binary
|
// TODO process binary
|
||||||
} else {
|
} else {
|
||||||
def json = slurper.parse(payload)
|
def json = slurper.parse(payload)
|
||||||
if (json.type == null)
|
if (json.type == null)
|
||||||
throw new Exception("missing json type")
|
throw new Exception("missing json type")
|
||||||
switch(json.type) {
|
switch(json.type) {
|
||||||
case "Ping" : handlePing(); break;
|
case "Ping" : handlePing(); break;
|
||||||
case "Pong" : handlePong(json); break;
|
case "Pong" : handlePong(json); break;
|
||||||
case "Search": handleSearch(json); break
|
case "Search": handleSearch(json); break
|
||||||
default :
|
default :
|
||||||
throw new Exception("unknown json type ${json.type}")
|
throw new Exception("unknown json type ${json.type}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void write(Object message) {
|
protected void write(Object message) {
|
||||||
byte[] payload
|
byte[] payload
|
||||||
if (message instanceof Map) {
|
if (message instanceof Map) {
|
||||||
payload = JsonOutput.toJson(message).bytes
|
payload = JsonOutput.toJson(message).bytes
|
||||||
DataUtil.packHeader(payload.length, writeHeader)
|
DataUtil.packHeader(payload.length, writeHeader)
|
||||||
log.fine "$name writing message type ${message.type} length $payload.length"
|
log.fine "$name writing message type ${message.type} length $payload.length"
|
||||||
writeHeader[0] &= (byte)0x7F
|
writeHeader[0] &= (byte)0x7F
|
||||||
} else {
|
} else {
|
||||||
// TODO: write binary
|
// TODO: write binary
|
||||||
}
|
}
|
||||||
|
|
||||||
dos.write(writeHeader)
|
dos.write(writeHeader)
|
||||||
dos.write(payload)
|
dos.write(payload)
|
||||||
dos.flush()
|
dos.flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -17,30 +17,30 @@ import net.i2p.data.Destination
|
|||||||
*/
|
*/
|
||||||
class UltrapeerConnection extends Connection {
|
class UltrapeerConnection extends Connection {
|
||||||
|
|
||||||
public UltrapeerConnection(EventBus eventBus, Endpoint endpoint, HostCache hostCache, TrustService trustService) {
|
public UltrapeerConnection(EventBus eventBus, Endpoint endpoint, HostCache hostCache, TrustService trustService) {
|
||||||
super(eventBus, endpoint, false, hostCache, trustService)
|
super(eventBus, endpoint, false, hostCache, trustService)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void read() {
|
protected void read() {
|
||||||
// TODO Auto-generated method stub
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void write(Object message) {
|
protected void write(Object message) {
|
||||||
if (message instanceof Map) {
|
if (message instanceof Map) {
|
||||||
writeJsonMessage(message)
|
writeJsonMessage(message)
|
||||||
} else {
|
} else {
|
||||||
writeBinaryMessage(message)
|
writeBinaryMessage(message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeJsonMessage(def message) {
|
private void writeJsonMessage(def message) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeBinaryMessage(def message) {
|
private void writeBinaryMessage(def message) {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -16,26 +16,26 @@ import net.i2p.data.Destination
|
|||||||
@Log
|
@Log
|
||||||
class UltrapeerConnectionManager extends ConnectionManager {
|
class UltrapeerConnectionManager extends ConnectionManager {
|
||||||
|
|
||||||
final int maxPeers, maxLeafs
|
final int maxPeers, maxLeafs
|
||||||
final TrustService trustService
|
final TrustService trustService
|
||||||
|
|
||||||
final Map<Destination, PeerConnection> peerConnections = new ConcurrentHashMap()
|
final Map<Destination, PeerConnection> peerConnections = new ConcurrentHashMap()
|
||||||
final Map<Destination, LeafConnection> leafConnections = new ConcurrentHashMap()
|
final Map<Destination, LeafConnection> leafConnections = new ConcurrentHashMap()
|
||||||
|
|
||||||
UltrapeerConnectionManager() {}
|
UltrapeerConnectionManager() {}
|
||||||
|
|
||||||
public UltrapeerConnectionManager(EventBus eventBus, Persona me, int maxPeers, int maxLeafs,
|
public UltrapeerConnectionManager(EventBus eventBus, Persona me, int maxPeers, int maxLeafs,
|
||||||
HostCache hostCache, TrustService trustService, MuWireSettings settings) {
|
HostCache hostCache, TrustService trustService, MuWireSettings settings) {
|
||||||
super(eventBus, me, hostCache, settings)
|
super(eventBus, me, hostCache, settings)
|
||||||
this.maxPeers = maxPeers
|
this.maxPeers = maxPeers
|
||||||
this.maxLeafs = maxLeafs
|
this.maxLeafs = maxLeafs
|
||||||
this.trustService = trustService
|
this.trustService = trustService
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void drop(Destination d) {
|
public void drop(Destination d) {
|
||||||
peerConnections.get(d)?.close()
|
peerConnections.get(d)?.close()
|
||||||
leafConnections.get(d)?.close()
|
leafConnections.get(d)?.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
void onQueryEvent(QueryEvent e) {
|
void onQueryEvent(QueryEvent e) {
|
||||||
forwardQueryToLeafs(e)
|
forwardQueryToLeafs(e)
|
||||||
@@ -50,57 +50,57 @@ class UltrapeerConnectionManager extends ConnectionManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<Connection> getConnections() {
|
public Collection<Connection> getConnections() {
|
||||||
def rv = new ArrayList(peerConnections.size() + leafConnections.size())
|
def rv = new ArrayList(peerConnections.size() + leafConnections.size())
|
||||||
rv.addAll(peerConnections.values())
|
rv.addAll(peerConnections.values())
|
||||||
rv.addAll(leafConnections.values())
|
rv.addAll(leafConnections.values())
|
||||||
rv
|
rv
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasLeafSlots() {
|
boolean hasLeafSlots() {
|
||||||
leafConnections.size() < maxLeafs
|
leafConnections.size() < maxLeafs
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasPeerSlots() {
|
boolean hasPeerSlots() {
|
||||||
peerConnections.size() < maxPeers
|
peerConnections.size() < maxPeers
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int getDesiredConnections() {
|
protected int getDesiredConnections() {
|
||||||
return maxPeers / 2;
|
return maxPeers / 2;
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public boolean isConnected(Destination d) {
|
public boolean isConnected(Destination d) {
|
||||||
peerConnections.containsKey(d) || leafConnections.containsKey(d)
|
peerConnections.containsKey(d) || leafConnections.containsKey(d)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConnectionEvent(ConnectionEvent e) {
|
public void onConnectionEvent(ConnectionEvent e) {
|
||||||
if (!e.incoming && e.leaf) {
|
if (!e.incoming && e.leaf) {
|
||||||
log.severe("Inconsistent event $e")
|
log.severe("Inconsistent event $e")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.status != ConnectionAttemptStatus.SUCCESSFUL)
|
if (e.status != ConnectionAttemptStatus.SUCCESSFUL)
|
||||||
return
|
return
|
||||||
|
|
||||||
Connection c = e.leaf ?
|
Connection c = e.leaf ?
|
||||||
new LeafConnection(eventBus, e.endpoint, hostCache, trustService, settings) :
|
new LeafConnection(eventBus, e.endpoint, hostCache, trustService, settings) :
|
||||||
new PeerConnection(eventBus, e.endpoint, e.incoming, hostCache, trustService, settings)
|
new PeerConnection(eventBus, e.endpoint, e.incoming, hostCache, trustService, settings)
|
||||||
def map = e.leaf ? leafConnections : peerConnections
|
def map = e.leaf ? leafConnections : peerConnections
|
||||||
map.put(e.endpoint.destination, c)
|
map.put(e.endpoint.destination, c)
|
||||||
c.start()
|
c.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisconnectionEvent(DisconnectionEvent e) {
|
public void onDisconnectionEvent(DisconnectionEvent e) {
|
||||||
def removed = peerConnections.remove(e.destination)
|
def removed = peerConnections.remove(e.destination)
|
||||||
if (removed == null)
|
if (removed == null)
|
||||||
removed = leafConnections.remove(e.destination)
|
removed = leafConnections.remove(e.destination)
|
||||||
if (removed == null)
|
if (removed == null)
|
||||||
log.severe("Removed connection not present in either leaf or peer map ${e.destination.toBase32()}")
|
log.severe("Removed connection not present in either leaf or peer map ${e.destination.toBase32()}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void shutdown() {
|
void shutdown() {
|
||||||
@@ -110,7 +110,7 @@ class UltrapeerConnectionManager extends ConnectionManager {
|
|||||||
leafConnections.clear()
|
leafConnections.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
void forwardQueryToLeafs(QueryEvent e) {
|
void forwardQueryToLeafs(QueryEvent e) {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,9 @@
|
|||||||
|
package com.muwire.core.content
|
||||||
|
|
||||||
|
import com.muwire.core.Event
|
||||||
|
|
||||||
|
class ContentControlEvent extends Event {
|
||||||
|
String term
|
||||||
|
boolean regex
|
||||||
|
boolean add
|
||||||
|
}
|
@@ -0,0 +1,30 @@
|
|||||||
|
package com.muwire.core.content
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
|
import com.muwire.core.search.QueryEvent
|
||||||
|
|
||||||
|
import net.i2p.util.ConcurrentHashSet
|
||||||
|
|
||||||
|
class ContentManager {
|
||||||
|
|
||||||
|
Set<Matcher> matchers = new ConcurrentHashSet()
|
||||||
|
|
||||||
|
void onContentControlEvent(ContentControlEvent e) {
|
||||||
|
Matcher m
|
||||||
|
if (e.regex)
|
||||||
|
m = new RegexMatcher(e.term)
|
||||||
|
else
|
||||||
|
m = new KeywordMatcher(e.term)
|
||||||
|
if (e.add)
|
||||||
|
matchers.add(m)
|
||||||
|
else
|
||||||
|
matchers.remove(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
void onQueryEvent(QueryEvent e) {
|
||||||
|
if (e.searchEvent.searchTerms == null)
|
||||||
|
return
|
||||||
|
matchers.each { it.process(e) }
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,36 @@
|
|||||||
|
package com.muwire.core.content
|
||||||
|
|
||||||
|
class KeywordMatcher extends Matcher {
|
||||||
|
private final String keyword
|
||||||
|
KeywordMatcher(String keyword) {
|
||||||
|
this.keyword = keyword
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean match(List<String> searchTerms) {
|
||||||
|
boolean found = false
|
||||||
|
searchTerms.each {
|
||||||
|
if (keyword == it)
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
found
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTerm() {
|
||||||
|
keyword
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
keyword.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (!(o instanceof KeywordMatcher))
|
||||||
|
return false
|
||||||
|
KeywordMatcher other = (KeywordMatcher) o
|
||||||
|
keyword.equals(other.keyword)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,9 @@
|
|||||||
|
package com.muwire.core.content
|
||||||
|
|
||||||
|
import com.muwire.core.Persona
|
||||||
|
|
||||||
|
class Match {
|
||||||
|
Persona persona
|
||||||
|
String [] keywords
|
||||||
|
long timestamp
|
||||||
|
}
|
20
core/src/main/groovy/com/muwire/core/content/Matcher.groovy
Normal file
20
core/src/main/groovy/com/muwire/core/content/Matcher.groovy
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package com.muwire.core.content
|
||||||
|
|
||||||
|
import com.muwire.core.search.QueryEvent
|
||||||
|
|
||||||
|
abstract class Matcher {
|
||||||
|
final List<Match> matches = Collections.synchronizedList(new ArrayList<>())
|
||||||
|
final Set<UUID> uuids = new HashSet<>()
|
||||||
|
|
||||||
|
protected abstract boolean match(List<String> searchTerms);
|
||||||
|
|
||||||
|
public abstract String getTerm();
|
||||||
|
|
||||||
|
public void process(QueryEvent qe) {
|
||||||
|
def terms = qe.searchEvent.searchTerms
|
||||||
|
if (match(terms) && uuids.add(qe.searchEvent.uuid)) {
|
||||||
|
long now = System.currentTimeMillis()
|
||||||
|
matches << new Match(persona : qe.originator, keywords : terms, timestamp : now)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,35 @@
|
|||||||
|
package com.muwire.core.content
|
||||||
|
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
import java.util.stream.Collectors
|
||||||
|
|
||||||
|
class RegexMatcher extends Matcher {
|
||||||
|
private final Pattern pattern
|
||||||
|
RegexMatcher(String pattern) {
|
||||||
|
this.pattern = Pattern.compile(pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean match(List<String> keywords) {
|
||||||
|
String combined = keywords.join(" ")
|
||||||
|
return pattern.matcher(combined).find()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTerm() {
|
||||||
|
pattern.pattern()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
pattern.pattern().hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (!(o instanceof RegexMatcher))
|
||||||
|
return false
|
||||||
|
RegexMatcher other = (RegexMatcher) o
|
||||||
|
pattern.pattern() == other.pattern.pattern()
|
||||||
|
}
|
||||||
|
}
|
@@ -14,6 +14,7 @@ import static com.muwire.core.util.DataUtil.readTillRN
|
|||||||
import groovy.util.logging.Log
|
import groovy.util.logging.Log
|
||||||
|
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.MappedByteBuffer
|
||||||
import java.nio.channels.FileChannel
|
import java.nio.channels.FileChannel
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
@@ -25,8 +26,6 @@ import java.util.logging.Level
|
|||||||
@Log
|
@Log
|
||||||
class DownloadSession {
|
class DownloadSession {
|
||||||
|
|
||||||
private static int SAMPLES = 10
|
|
||||||
|
|
||||||
private final EventBus eventBus
|
private final EventBus eventBus
|
||||||
private final String meB64
|
private final String meB64
|
||||||
private final Pieces pieces
|
private final Pieces pieces
|
||||||
@@ -38,10 +37,10 @@ class DownloadSession {
|
|||||||
private final Set<Integer> available
|
private final Set<Integer> available
|
||||||
private final MessageDigest digest
|
private final MessageDigest digest
|
||||||
|
|
||||||
private final LinkedList<Long> timestamps = new LinkedList<>()
|
private long lastSpeedRead = System.currentTimeMillis()
|
||||||
private final LinkedList<Integer> reads = new LinkedList<>()
|
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) {
|
||||||
@@ -71,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(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 {
|
||||||
@@ -174,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()) {
|
||||||
@@ -186,13 +188,8 @@ class DownloadSession {
|
|||||||
throw new IOException()
|
throw new IOException()
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
mapped.put(tmp, 0, read)
|
mapped.put(tmp, 0, read)
|
||||||
|
dataSinceLastRead += read
|
||||||
if (timestamps.size() == SAMPLES) {
|
pieces.markPartial(piece, mapped.position())
|
||||||
timestamps.removeFirst()
|
|
||||||
reads.removeFirst()
|
|
||||||
}
|
|
||||||
timestamps.addLast(System.currentTimeMillis())
|
|
||||||
reads.addLast(read)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,15 +198,18 @@ class DownloadSession {
|
|||||||
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
|
||||||
@@ -222,24 +222,11 @@ class DownloadSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
synchronized int speed() {
|
synchronized int speed() {
|
||||||
if (timestamps.size() < SAMPLES)
|
|
||||||
return 0
|
|
||||||
int totalRead = 0
|
|
||||||
int idx = 0
|
|
||||||
final long now = System.currentTimeMillis()
|
final long now = System.currentTimeMillis()
|
||||||
|
long interval = Math.max(1000, now - lastSpeedRead)
|
||||||
while(idx < SAMPLES && timestamps.get(idx) < now - 1000)
|
lastSpeedRead = now;
|
||||||
idx++
|
int rv = (int) (dataSinceLastRead * 1000.0 / interval)
|
||||||
if (idx == SAMPLES)
|
dataSinceLastRead = 0
|
||||||
return 0
|
rv
|
||||||
if (idx == SAMPLES - 1)
|
|
||||||
return reads[idx]
|
|
||||||
|
|
||||||
long interval = timestamps.last - timestamps[idx]
|
|
||||||
if (interval == 0)
|
|
||||||
interval = 1
|
|
||||||
for (int i = idx; i < SAMPLES; i++)
|
|
||||||
totalRead += reads[idx]
|
|
||||||
(int)(totalRead * 1000.0 / interval)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,6 +7,7 @@ import com.muwire.core.connection.Endpoint
|
|||||||
import java.nio.file.AtomicMoveNotSupportedException
|
import java.nio.file.AtomicMoveNotSupportedException
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.StandardCopyOption
|
import java.nio.file.StandardCopyOption
|
||||||
|
import java.time.Instant
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.ExecutorService
|
import java.util.concurrent.ExecutorService
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
@@ -18,6 +19,7 @@ import com.muwire.core.DownloadedFile
|
|||||||
import com.muwire.core.EventBus
|
import com.muwire.core.EventBus
|
||||||
import com.muwire.core.connection.I2PConnector
|
import com.muwire.core.connection.I2PConnector
|
||||||
import com.muwire.core.files.FileDownloadedEvent
|
import com.muwire.core.files.FileDownloadedEvent
|
||||||
|
import com.muwire.core.util.DataUtil
|
||||||
|
|
||||||
import groovy.util.logging.Log
|
import groovy.util.logging.Log
|
||||||
import net.i2p.data.Destination
|
import net.i2p.data.Destination
|
||||||
@@ -57,6 +59,11 @@ public class Downloader {
|
|||||||
private final AtomicBoolean eventFired = new AtomicBoolean()
|
private final AtomicBoolean eventFired = new AtomicBoolean()
|
||||||
private boolean piecesFileClosed
|
private boolean piecesFileClosed
|
||||||
|
|
||||||
|
private ArrayList speedArr = new ArrayList<Integer>()
|
||||||
|
private int speedPos = 0
|
||||||
|
private int speedAvg = 0
|
||||||
|
private long timestamp = Instant.now().toEpochMilli()
|
||||||
|
|
||||||
public Downloader(EventBus eventBus, DownloadManager downloadManager,
|
public Downloader(EventBus eventBus, DownloadManager downloadManager,
|
||||||
Persona me, File file, long length, InfoHash infoHash,
|
Persona me, File file, long length, InfoHash infoHash,
|
||||||
int pieceSizePow2, I2PConnector connector, Set<Destination> destinations,
|
int pieceSizePow2, I2PConnector connector, Set<Destination> destinations,
|
||||||
@@ -75,6 +82,10 @@ public class Downloader {
|
|||||||
this.pieceSize = 1 << pieceSizePow2
|
this.pieceSize = 1 << pieceSizePow2
|
||||||
this.pieces = pieces
|
this.pieces = pieces
|
||||||
this.nPieces = pieces.nPieces
|
this.nPieces = pieces.nPieces
|
||||||
|
|
||||||
|
// default size suitable for an average of 5 seconds / 5 elements / 5 interval units
|
||||||
|
// it's easily adjustable by resizing the size of speedArr
|
||||||
|
this.speedArr = [ 0, 0, 0, 0, 0 ]
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized InfoHash getInfoHash() {
|
public synchronized InfoHash getInfoHash() {
|
||||||
@@ -100,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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,14 +138,35 @@ public class Downloader {
|
|||||||
|
|
||||||
|
|
||||||
public int speed() {
|
public int speed() {
|
||||||
int total = 0
|
int currSpeed = 0
|
||||||
if (getCurrentState() == DownloadState.DOWNLOADING) {
|
if (getCurrentState() == DownloadState.DOWNLOADING) {
|
||||||
activeWorkers.values().each {
|
activeWorkers.values().each {
|
||||||
if (it.currentState == WorkerState.DOWNLOADING)
|
if (it.currentState == WorkerState.DOWNLOADING)
|
||||||
total += it.speed()
|
currSpeed += it.speed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
total
|
|
||||||
|
// normalize to speedArr.size
|
||||||
|
currSpeed /= speedArr.size()
|
||||||
|
|
||||||
|
// compute new speedAvg and update speedArr
|
||||||
|
if ( speedArr[speedPos] > speedAvg ) {
|
||||||
|
speedAvg = 0
|
||||||
|
} else {
|
||||||
|
speedAvg -= speedArr[speedPos]
|
||||||
|
}
|
||||||
|
speedAvg += currSpeed
|
||||||
|
speedArr[speedPos] = currSpeed
|
||||||
|
// this might be necessary due to rounding errors
|
||||||
|
if (speedAvg < 0)
|
||||||
|
speedAvg = 0
|
||||||
|
|
||||||
|
// rolling index over the speedArr
|
||||||
|
speedPos++
|
||||||
|
if (speedPos >= speedArr.size())
|
||||||
|
speedPos=0
|
||||||
|
|
||||||
|
speedAvg
|
||||||
}
|
}
|
||||||
|
|
||||||
public DownloadState getCurrentState() {
|
public DownloadState getCurrentState() {
|
||||||
@@ -269,14 +305,19 @@ public class Downloader {
|
|||||||
writePieces()
|
writePieces()
|
||||||
}
|
}
|
||||||
} catch (Exception bad) {
|
} catch (Exception bad) {
|
||||||
log.log(Level.WARNING,"Exception while downloading",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) {
|
||||||
|
@@ -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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,7 @@
|
|||||||
|
package com.muwire.core.files
|
||||||
|
|
||||||
|
import com.muwire.core.Event
|
||||||
|
|
||||||
|
class DirectoryUnsharedEvent extends Event {
|
||||||
|
File directory
|
||||||
|
}
|
@@ -35,6 +35,7 @@ class DirectoryWatcher {
|
|||||||
private final FileManager fileManager
|
private final FileManager fileManager
|
||||||
private final Thread watcherThread, publisherThread
|
private final Thread watcherThread, publisherThread
|
||||||
private final Map<File, Long> waitingFiles = new ConcurrentHashMap<>()
|
private final Map<File, Long> waitingFiles = new ConcurrentHashMap<>()
|
||||||
|
private final Map<File, WatchKey> watchedDirectories = new ConcurrentHashMap<>()
|
||||||
private WatchService watchService
|
private WatchService watchService
|
||||||
private volatile boolean shutdown
|
private volatile boolean shutdown
|
||||||
|
|
||||||
@@ -47,7 +48,7 @@ class DirectoryWatcher {
|
|||||||
publisherThread.setDaemon(true)
|
publisherThread.setDaemon(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
void start() {
|
void onAllFilesLoadedEvent(AllFilesLoadedEvent e) {
|
||||||
watchService = FileSystems.getDefault().newWatchService()
|
watchService = FileSystems.getDefault().newWatchService()
|
||||||
watcherThread.start()
|
watcherThread.start()
|
||||||
publisherThread.start()
|
publisherThread.start()
|
||||||
@@ -55,19 +56,25 @@ class DirectoryWatcher {
|
|||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
shutdown = true
|
shutdown = true
|
||||||
watcherThread.interrupt()
|
watcherThread?.interrupt()
|
||||||
publisherThread.interrupt()
|
publisherThread?.interrupt()
|
||||||
watchService.close()
|
watchService?.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFileSharedEvent(FileSharedEvent e) {
|
void onFileSharedEvent(FileSharedEvent e) {
|
||||||
if (!e.file.isDirectory())
|
if (!e.file.isDirectory())
|
||||||
return
|
return
|
||||||
Path path = e.file.getCanonicalFile().toPath()
|
Path path = e.file.getCanonicalFile().toPath()
|
||||||
path.register(watchService, kinds)
|
WatchKey wk = path.register(watchService, kinds)
|
||||||
|
watchedDirectories.put(e.file, wk)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onDirectoryUnsharedEvent(DirectoryUnsharedEvent e) {
|
||||||
|
WatchKey wk = watchedDirectories.remove(e.directory)
|
||||||
|
wk?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
private void watch() {
|
private void watch() {
|
||||||
try {
|
try {
|
||||||
while(!shutdown) {
|
while(!shutdown) {
|
||||||
@@ -113,7 +120,7 @@ class DirectoryWatcher {
|
|||||||
|
|
||||||
private static File join(Path parent, Path path) {
|
private static File join(Path parent, Path path) {
|
||||||
File parentFile = parent.toFile().getCanonicalFile()
|
File parentFile = parent.toFile().getCanonicalFile()
|
||||||
new File(parentFile, path.toFile().getName())
|
new File(parentFile, path.toFile().getName()).getCanonicalFile()
|
||||||
}
|
}
|
||||||
|
|
||||||
private void publish() {
|
private void publish() {
|
||||||
|
@@ -8,5 +8,5 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
class FileDownloadedEvent extends Event {
|
class FileDownloadedEvent extends Event {
|
||||||
Downloader downloader
|
Downloader downloader
|
||||||
DownloadedFile downloadedFile
|
DownloadedFile downloadedFile
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,12 @@ import com.muwire.core.SharedFile
|
|||||||
|
|
||||||
class FileHashedEvent extends Event {
|
class FileHashedEvent extends Event {
|
||||||
|
|
||||||
SharedFile sharedFile
|
SharedFile sharedFile
|
||||||
String error
|
String error
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
super.toString() + " sharedFile " + sharedFile?.file.getAbsolutePath() + " error: $error"
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package com.muwire.core.files
|
package com.muwire.core.files
|
||||||
|
|
||||||
import com.muwire.core.InfoHash
|
import com.muwire.core.InfoHash
|
||||||
|
import com.muwire.core.util.DataUtil
|
||||||
|
|
||||||
import net.i2p.data.Base64
|
import net.i2p.data.Base64
|
||||||
|
|
||||||
@@ -12,64 +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)
|
||||||
static int getPieceSize(long size) {
|
* there can be up to 8192 pieces maximum per file
|
||||||
if (size <= 0x1 << 30)
|
*/
|
||||||
return 17
|
static int getPieceSize(long size) {
|
||||||
|
if (size <= 0x1 << 30)
|
||||||
|
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
|
||||||
output.write(digest.digest(), 0, 32)
|
DataUtil.tryUnmap(buf)
|
||||||
}
|
output.write(digest.digest(), 0, 32)
|
||||||
def lastPieceLength = length - (numPieces - 1) * ((long)size)
|
}
|
||||||
buf = raf.getChannel().map(MapMode.READ_ONLY, length - lastPieceLength, lastPieceLength)
|
def lastPieceLength = length - (numPieces - 1) * ((long)size)
|
||||||
digest.update buf
|
buf = raf.getChannel().map(MapMode.READ_ONLY, length - lastPieceLength, lastPieceLength)
|
||||||
output.write(digest.digest(), 0, 32)
|
digest.update buf
|
||||||
} finally {
|
output.write(digest.digest(), 0, 32)
|
||||||
raf.close()
|
} finally {
|
||||||
}
|
raf.close()
|
||||||
|
}
|
||||||
|
|
||||||
byte [] hashList = output.toByteArray()
|
byte [] hashList = output.toByteArray()
|
||||||
InfoHash.fromHashList(hashList)
|
InfoHash.fromHashList(hashList)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
if (args.length != 1) {
|
if (args.length != 1) {
|
||||||
|
@@ -0,0 +1,15 @@
|
|||||||
|
package com.muwire.core.files
|
||||||
|
|
||||||
|
import com.muwire.core.Event
|
||||||
|
import com.muwire.core.SharedFile
|
||||||
|
|
||||||
|
class FileHashingEvent extends Event {
|
||||||
|
|
||||||
|
File hashingFile
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
super.toString() + " hashingFile " + hashingFile.getAbsolutePath()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -5,5 +5,5 @@ import com.muwire.core.SharedFile
|
|||||||
|
|
||||||
class FileLoadedEvent extends Event {
|
class FileLoadedEvent extends Event {
|
||||||
|
|
||||||
SharedFile loadedFile
|
SharedFile loadedFile
|
||||||
}
|
}
|
||||||
|
@@ -15,26 +15,26 @@ import groovy.util.logging.Log
|
|||||||
class FileManager {
|
class FileManager {
|
||||||
|
|
||||||
|
|
||||||
final EventBus eventBus
|
final EventBus eventBus
|
||||||
final MuWireSettings settings
|
final MuWireSettings settings
|
||||||
final Map<InfoHash, Set<SharedFile>> rootToFiles = Collections.synchronizedMap(new HashMap<>())
|
final Map<InfoHash, Set<SharedFile>> rootToFiles = Collections.synchronizedMap(new HashMap<>())
|
||||||
final Map<File, SharedFile> fileToSharedFile = Collections.synchronizedMap(new HashMap<>())
|
final Map<File, SharedFile> fileToSharedFile = Collections.synchronizedMap(new HashMap<>())
|
||||||
final Map<String, Set<File>> nameToFiles = new HashMap<>()
|
final Map<String, Set<File>> nameToFiles = new HashMap<>()
|
||||||
final SearchIndex index = new SearchIndex()
|
final SearchIndex index = new SearchIndex()
|
||||||
|
|
||||||
FileManager(EventBus eventBus, MuWireSettings settings) {
|
FileManager(EventBus eventBus, MuWireSettings settings) {
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFileHashedEvent(FileHashedEvent e) {
|
void onFileHashedEvent(FileHashedEvent e) {
|
||||||
if (e.sharedFile != null)
|
if (e.sharedFile != null)
|
||||||
addToIndex(e.sharedFile)
|
addToIndex(e.sharedFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFileLoadedEvent(FileLoadedEvent e) {
|
void onFileLoadedEvent(FileLoadedEvent e) {
|
||||||
addToIndex(e.loadedFile)
|
addToIndex(e.loadedFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFileDownloadedEvent(FileDownloadedEvent e) {
|
void onFileDownloadedEvent(FileDownloadedEvent e) {
|
||||||
if (settings.shareDownloadedFiles) {
|
if (settings.shareDownloadedFiles) {
|
||||||
@@ -42,88 +42,88 @@ class FileManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addToIndex(SharedFile sf) {
|
private void addToIndex(SharedFile sf) {
|
||||||
log.info("Adding shared file " + sf.getFile())
|
log.info("Adding shared file " + sf.getFile())
|
||||||
InfoHash infoHash = sf.getInfoHash()
|
InfoHash infoHash = sf.getInfoHash()
|
||||||
Set<SharedFile> existing = rootToFiles.get(infoHash)
|
Set<SharedFile> existing = rootToFiles.get(infoHash)
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
log.info("adding new root")
|
log.info("adding new root")
|
||||||
existing = new HashSet<>()
|
existing = new HashSet<>()
|
||||||
rootToFiles.put(infoHash, existing);
|
rootToFiles.put(infoHash, existing);
|
||||||
}
|
}
|
||||||
existing.add(sf)
|
existing.add(sf)
|
||||||
fileToSharedFile.put(sf.file, sf)
|
fileToSharedFile.put(sf.file, sf)
|
||||||
|
|
||||||
String name = sf.getFile().getName()
|
String name = sf.getFile().getName()
|
||||||
Set<File> existingFiles = nameToFiles.get(name)
|
Set<File> existingFiles = nameToFiles.get(name)
|
||||||
if (existingFiles == null) {
|
if (existingFiles == null) {
|
||||||
existingFiles = new HashSet<>()
|
existingFiles = new HashSet<>()
|
||||||
nameToFiles.put(name, existingFiles)
|
nameToFiles.put(name, existingFiles)
|
||||||
}
|
}
|
||||||
existingFiles.add(sf.getFile())
|
existingFiles.add(sf.getFile())
|
||||||
|
|
||||||
index.add(name)
|
index.add(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFileUnsharedEvent(FileUnsharedEvent e) {
|
void onFileUnsharedEvent(FileUnsharedEvent e) {
|
||||||
SharedFile sf = e.unsharedFile
|
SharedFile sf = e.unsharedFile
|
||||||
InfoHash infoHash = sf.getInfoHash()
|
InfoHash infoHash = sf.getInfoHash()
|
||||||
Set<SharedFile> existing = rootToFiles.get(infoHash)
|
Set<SharedFile> existing = rootToFiles.get(infoHash)
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
existing.remove(sf)
|
existing.remove(sf)
|
||||||
if (existing.isEmpty()) {
|
if (existing.isEmpty()) {
|
||||||
rootToFiles.remove(infoHash)
|
rootToFiles.remove(infoHash)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fileToSharedFile.remove(sf.file)
|
fileToSharedFile.remove(sf.file)
|
||||||
|
|
||||||
String name = sf.getFile().getName()
|
String name = sf.getFile().getName()
|
||||||
Set<File> existingFiles = nameToFiles.get(name)
|
Set<File> existingFiles = nameToFiles.get(name)
|
||||||
if (existingFiles != null) {
|
if (existingFiles != null) {
|
||||||
existingFiles.remove(sf.file)
|
existingFiles.remove(sf.file)
|
||||||
if (existingFiles.isEmpty()) {
|
if (existingFiles.isEmpty()) {
|
||||||
nameToFiles.remove(name)
|
nameToFiles.remove(name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
index.remove(name)
|
index.remove(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<File, SharedFile> getSharedFiles() {
|
Map<File, SharedFile> getSharedFiles() {
|
||||||
synchronized(fileToSharedFile) {
|
synchronized(fileToSharedFile) {
|
||||||
return new HashMap<>(fileToSharedFile)
|
return new HashMap<>(fileToSharedFile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<SharedFile> getSharedFiles(byte []root) {
|
Set<SharedFile> getSharedFiles(byte []root) {
|
||||||
return rootToFiles.get(new InfoHash(root))
|
return rootToFiles.get(new InfoHash(root))
|
||||||
}
|
}
|
||||||
|
|
||||||
void onSearchEvent(SearchEvent e) {
|
void onSearchEvent(SearchEvent e) {
|
||||||
// hash takes precedence
|
// hash takes precedence
|
||||||
ResultsEvent re = null
|
ResultsEvent re = null
|
||||||
if (e.searchHash != null) {
|
if (e.searchHash != null) {
|
||||||
Set<SharedFile> found
|
Set<SharedFile> found
|
||||||
found = rootToFiles.get new InfoHash(e.searchHash)
|
found = rootToFiles.get new InfoHash(e.searchHash)
|
||||||
found = filter(found, e.oobInfohash)
|
found = filter(found, e.oobInfohash)
|
||||||
if (found != null && !found.isEmpty())
|
if (found != null && !found.isEmpty())
|
||||||
re = new ResultsEvent(results: found.asList(), uuid: e.uuid, searchEvent: e)
|
re = new ResultsEvent(results: found.asList(), uuid: e.uuid, searchEvent: e)
|
||||||
} else {
|
} else {
|
||||||
def names = index.search e.searchTerms
|
def names = index.search e.searchTerms
|
||||||
Set<File> files = new HashSet<>()
|
Set<File> files = new HashSet<>()
|
||||||
names.each { files.addAll nameToFiles.getOrDefault(it, []) }
|
names.each { files.addAll nameToFiles.getOrDefault(it, []) }
|
||||||
Set<SharedFile> sharedFiles = new HashSet<>()
|
Set<SharedFile> sharedFiles = new HashSet<>()
|
||||||
files.each { sharedFiles.add fileToSharedFile[it] }
|
files.each { sharedFiles.add fileToSharedFile[it] }
|
||||||
files = filter(sharedFiles, e.oobInfohash)
|
files = filter(sharedFiles, e.oobInfohash)
|
||||||
if (!sharedFiles.isEmpty())
|
if (!sharedFiles.isEmpty())
|
||||||
re = new ResultsEvent(results: sharedFiles.asList(), uuid: e.uuid, searchEvent: e)
|
re = new ResultsEvent(results: sharedFiles.asList(), uuid: e.uuid, searchEvent: e)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (re != null)
|
if (re != null)
|
||||||
eventBus.publish(re)
|
eventBus.publish(re)
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Set<SharedFile> filter(Set<SharedFile> files, boolean oob) {
|
private static Set<SharedFile> filter(Set<SharedFile> files, boolean oob) {
|
||||||
if (!oob)
|
if (!oob)
|
||||||
@@ -135,4 +135,16 @@ class FileManager {
|
|||||||
}
|
}
|
||||||
rv
|
rv
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onDirectoryUnsharedEvent(DirectoryUnsharedEvent e) {
|
||||||
|
e.directory.listFiles().each {
|
||||||
|
if (it.isDirectory())
|
||||||
|
eventBus.publish(new DirectoryUnsharedEvent(directory : it))
|
||||||
|
else {
|
||||||
|
SharedFile sf = fileToSharedFile.get(it)
|
||||||
|
if (sf != null)
|
||||||
|
eventBus.publish(new FileUnsharedEvent(unsharedFile : sf))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,5 +4,10 @@ import com.muwire.core.Event
|
|||||||
|
|
||||||
class FileSharedEvent extends Event {
|
class FileSharedEvent extends Event {
|
||||||
|
|
||||||
File file
|
File file
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return super.toString() + " file: "+file.getAbsolutePath()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,5 +4,5 @@ import com.muwire.core.Event
|
|||||||
import com.muwire.core.SharedFile
|
import com.muwire.core.SharedFile
|
||||||
|
|
||||||
class FileUnsharedEvent extends Event {
|
class FileUnsharedEvent extends Event {
|
||||||
SharedFile unsharedFile
|
SharedFile unsharedFile
|
||||||
}
|
}
|
||||||
|
@@ -8,40 +8,41 @@ import com.muwire.core.SharedFile
|
|||||||
|
|
||||||
class HasherService {
|
class HasherService {
|
||||||
|
|
||||||
final FileHasher hasher
|
final FileHasher hasher
|
||||||
final EventBus eventBus
|
final EventBus eventBus
|
||||||
final FileManager fileManager
|
final FileManager fileManager
|
||||||
Executor executor
|
Executor executor
|
||||||
|
|
||||||
HasherService(FileHasher hasher, EventBus eventBus, FileManager fileManager) {
|
HasherService(FileHasher hasher, EventBus eventBus, FileManager fileManager) {
|
||||||
this.hasher = hasher
|
this.hasher = hasher
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.fileManager = fileManager
|
this.fileManager = fileManager
|
||||||
}
|
}
|
||||||
|
|
||||||
void start() {
|
void start() {
|
||||||
executor = Executors.newSingleThreadExecutor()
|
executor = Executors.newSingleThreadExecutor()
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFileSharedEvent(FileSharedEvent evt) {
|
void onFileSharedEvent(FileSharedEvent evt) {
|
||||||
if (fileManager.fileToSharedFile.containsKey(evt.file))
|
if (fileManager.fileToSharedFile.containsKey(evt.file.getCanonicalFile()))
|
||||||
return
|
return
|
||||||
executor.execute( { -> process(evt.file) } as Runnable)
|
executor.execute( { -> process(evt.file) } as Runnable)
|
||||||
}
|
}
|
||||||
|
|
||||||
private void process(File f) {
|
private void process(File f) {
|
||||||
f = f.getCanonicalFile()
|
f = f.getCanonicalFile()
|
||||||
if (f.isDirectory()) {
|
if (f.isDirectory()) {
|
||||||
f.listFiles().each {eventBus.publish new FileSharedEvent(file: it) }
|
f.listFiles().each {eventBus.publish new FileSharedEvent(file: it) }
|
||||||
} else {
|
} else {
|
||||||
if (f.length() == 0) {
|
if (f.length() == 0) {
|
||||||
eventBus.publish new FileHashedEvent(error: "Not sharing empty file $f")
|
eventBus.publish new FileHashedEvent(error: "Not sharing empty file $f")
|
||||||
} else if (f.length() > FileHasher.MAX_SIZE) {
|
} else if (f.length() > FileHasher.MAX_SIZE) {
|
||||||
eventBus.publish new FileHashedEvent(error: "$f is too large to be shared ${f.length()}")
|
eventBus.publish new FileHashedEvent(error: "$f is too large to be shared ${f.length()}")
|
||||||
} else {
|
} else {
|
||||||
def hash = hasher.hashFile f
|
eventBus.publish new FileHashingEvent(hashingFile: f)
|
||||||
eventBus.publish new FileHashedEvent(sharedFile: new SharedFile(f, hash, FileHasher.getPieceSize(f.length())))
|
def hash = hasher.hashFile f
|
||||||
}
|
eventBus.publish new FileHashedEvent(sharedFile: new SharedFile(f, hash, FileHasher.getPieceSize(f.length())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -23,135 +23,135 @@ import net.i2p.data.Destination
|
|||||||
@Log
|
@Log
|
||||||
class PersisterService extends Service {
|
class PersisterService extends Service {
|
||||||
|
|
||||||
final File location
|
final File location
|
||||||
final EventBus listener
|
final EventBus listener
|
||||||
final int interval
|
final int interval
|
||||||
final Timer timer
|
final Timer timer
|
||||||
final FileManager fileManager
|
final FileManager fileManager
|
||||||
|
|
||||||
PersisterService(File location, EventBus listener, int interval, FileManager fileManager) {
|
PersisterService(File location, EventBus listener, int interval, FileManager fileManager) {
|
||||||
this.location = location
|
this.location = location
|
||||||
this.listener = listener
|
this.listener = listener
|
||||||
this.interval = interval
|
this.interval = interval
|
||||||
this.fileManager = fileManager
|
this.fileManager = fileManager
|
||||||
timer = new Timer("file persister", true)
|
timer = new Timer("file persister", true)
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
timer.cancel()
|
timer.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
void onUILoadedEvent(UILoadedEvent e) {
|
void onUILoadedEvent(UILoadedEvent e) {
|
||||||
timer.schedule({load()} as TimerTask, 1)
|
timer.schedule({load()} as TimerTask, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
void load() {
|
void load() {
|
||||||
if (location.exists() && location.isFile()) {
|
if (location.exists() && location.isFile()) {
|
||||||
def slurper = new JsonSlurper()
|
def slurper = new JsonSlurper()
|
||||||
try {
|
try {
|
||||||
location.eachLine {
|
location.eachLine {
|
||||||
if (it.trim().length() > 0) {
|
if (it.trim().length() > 0) {
|
||||||
def parsed = slurper.parseText it
|
def parsed = slurper.parseText it
|
||||||
def event = fromJson parsed
|
def event = fromJson parsed
|
||||||
if (event != null) {
|
if (event != null) {
|
||||||
log.fine("loaded file $event.loadedFile.file")
|
log.fine("loaded file $event.loadedFile.file")
|
||||||
listener.publish event
|
listener.publish event
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
listener.publish(new AllFilesLoadedEvent())
|
listener.publish(new AllFilesLoadedEvent())
|
||||||
} catch (IllegalArgumentException|NumberFormatException e) {
|
} catch (IllegalArgumentException|NumberFormatException e) {
|
||||||
log.log(Level.WARNING, "couldn't load files",e)
|
log.log(Level.WARNING, "couldn't load files",e)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
listener.publish(new AllFilesLoadedEvent())
|
listener.publish(new AllFilesLoadedEvent())
|
||||||
}
|
}
|
||||||
timer.schedule({persistFiles()} as TimerTask, 0, interval)
|
timer.schedule({persistFiles()} as TimerTask, 0, interval)
|
||||||
loaded = true
|
loaded = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FileLoadedEvent fromJson(def json) {
|
private static FileLoadedEvent fromJson(def json) {
|
||||||
if (json.file == null || json.length == null || json.infoHash == null || json.hashList == null)
|
if (json.file == null || json.length == null || json.infoHash == null || json.hashList == null)
|
||||||
throw new IllegalArgumentException()
|
throw new IllegalArgumentException()
|
||||||
if (!(json.hashList instanceof List))
|
if (!(json.hashList instanceof List))
|
||||||
throw new IllegalArgumentException()
|
throw new IllegalArgumentException()
|
||||||
|
|
||||||
def file = new File(DataUtil.readi18nString(Base64.decode(json.file)))
|
def file = new File(DataUtil.readi18nString(Base64.decode(json.file)))
|
||||||
file = file.getCanonicalFile()
|
file = file.getCanonicalFile()
|
||||||
if (!file.exists() || file.isDirectory())
|
if (!file.exists() || file.isDirectory())
|
||||||
return null
|
return null
|
||||||
long length = Long.valueOf(json.length)
|
long length = Long.valueOf(json.length)
|
||||||
if (length != file.length())
|
if (length != file.length())
|
||||||
return null
|
return null
|
||||||
|
|
||||||
List hashList = (List) json.hashList
|
List hashList = (List) json.hashList
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
||||||
hashList.each {
|
hashList.each {
|
||||||
byte [] hash = Base64.decode it.toString()
|
byte [] hash = Base64.decode it.toString()
|
||||||
if (hash == null)
|
if (hash == null)
|
||||||
throw new IllegalArgumentException()
|
throw new IllegalArgumentException()
|
||||||
baos.write hash
|
baos.write hash
|
||||||
}
|
}
|
||||||
byte[] hashListBytes = baos.toByteArray()
|
byte[] hashListBytes = baos.toByteArray()
|
||||||
|
|
||||||
InfoHash ih = InfoHash.fromHashList(hashListBytes)
|
InfoHash ih = InfoHash.fromHashList(hashListBytes)
|
||||||
byte [] root = Base64.decode(json.infoHash.toString())
|
byte [] root = Base64.decode(json.infoHash.toString())
|
||||||
if (root == null)
|
if (root == null)
|
||||||
throw new IllegalArgumentException()
|
throw new IllegalArgumentException()
|
||||||
if (!Arrays.equals(root, ih.getRoot()))
|
if (!Arrays.equals(root, ih.getRoot()))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
int pieceSize = 0
|
int pieceSize = 0
|
||||||
if (json.pieceSize != null)
|
if (json.pieceSize != null)
|
||||||
pieceSize = json.pieceSize
|
pieceSize = json.pieceSize
|
||||||
|
|
||||||
if (json.sources != null) {
|
if (json.sources != null) {
|
||||||
List sources = (List)json.sources
|
List sources = (List)json.sources
|
||||||
Set<Destination> sourceSet = sources.stream().map({d -> new Destination(d.toString())}).collect Collectors.toSet()
|
Set<Destination> sourceSet = sources.stream().map({d -> new Destination(d.toString())}).collect Collectors.toSet()
|
||||||
DownloadedFile df = new DownloadedFile(file, ih, pieceSize, sourceSet)
|
DownloadedFile df = new DownloadedFile(file, ih, pieceSize, sourceSet)
|
||||||
return new FileLoadedEvent(loadedFile : df)
|
return new FileLoadedEvent(loadedFile : df)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
SharedFile sf = new SharedFile(file, ih, pieceSize)
|
SharedFile sf = new SharedFile(file, ih, pieceSize)
|
||||||
return new FileLoadedEvent(loadedFile: sf)
|
return new FileLoadedEvent(loadedFile: sf)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void persistFiles() {
|
private void persistFiles() {
|
||||||
def sharedFiles = fileManager.getSharedFiles()
|
def sharedFiles = fileManager.getSharedFiles()
|
||||||
|
|
||||||
File tmp = File.createTempFile("muwire-files", "tmp")
|
File tmp = File.createTempFile("muwire-files", "tmp")
|
||||||
tmp.deleteOnExit()
|
tmp.deleteOnExit()
|
||||||
tmp.withPrintWriter { writer ->
|
tmp.withPrintWriter { writer ->
|
||||||
sharedFiles.each { k, v ->
|
sharedFiles.each { k, v ->
|
||||||
def json = toJson(k,v)
|
def json = toJson(k,v)
|
||||||
json = JsonOutput.toJson(json)
|
json = JsonOutput.toJson(json)
|
||||||
writer.println json
|
writer.println json
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Files.copy(tmp.toPath(), location.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
Files.copy(tmp.toPath(), location.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
||||||
tmp.delete()
|
tmp.delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
private def toJson(File f, SharedFile sf) {
|
private def toJson(File f, SharedFile sf) {
|
||||||
def json = [:]
|
def json = [:]
|
||||||
json.file = Base64.encode DataUtil.encodei18nString(f.getCanonicalFile().toString())
|
json.file = Base64.encode DataUtil.encodei18nString(f.getCanonicalFile().toString())
|
||||||
json.length = f.length()
|
json.length = f.length()
|
||||||
InfoHash ih = sf.getInfoHash()
|
InfoHash ih = sf.getInfoHash()
|
||||||
json.infoHash = Base64.encode ih.getRoot()
|
json.infoHash = Base64.encode ih.getRoot()
|
||||||
json.pieceSize = sf.getPieceSize()
|
json.pieceSize = sf.getPieceSize()
|
||||||
byte [] tmp = new byte [32]
|
byte [] tmp = new byte [32]
|
||||||
json.hashList = []
|
json.hashList = []
|
||||||
for (int i = 0;i < ih.getHashList().length / 32; i++) {
|
for (int i = 0;i < ih.getHashList().length / 32; i++) {
|
||||||
System.arraycopy(ih.getHashList(), i * 32, tmp, 0, 32)
|
System.arraycopy(ih.getHashList(), i * 32, tmp, 0, 32)
|
||||||
json.hashList.add Base64.encode(tmp)
|
json.hashList.add Base64.encode(tmp)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sf instanceof DownloadedFile) {
|
if (sf instanceof DownloadedFile) {
|
||||||
json.sources = sf.sources.stream().map( {d -> d.toBase64()}).collect(Collectors.toList())
|
json.sources = sf.sources.stream().map( {d -> d.toBase64()}).collect(Collectors.toList())
|
||||||
}
|
}
|
||||||
|
|
||||||
json
|
json
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -18,176 +18,176 @@ import net.i2p.data.Destination
|
|||||||
@Log
|
@Log
|
||||||
class CacheClient {
|
class CacheClient {
|
||||||
|
|
||||||
private static final int CRAWLER_RETURN = 10
|
private static final int CRAWLER_RETURN = 10
|
||||||
|
|
||||||
final EventBus eventBus
|
final EventBus eventBus
|
||||||
final HostCache cache
|
final HostCache cache
|
||||||
final ConnectionManager manager
|
final ConnectionManager manager
|
||||||
final I2PSession session
|
final I2PSession session
|
||||||
final long interval
|
final long interval
|
||||||
final MuWireSettings settings
|
final MuWireSettings settings
|
||||||
final Timer timer
|
final Timer timer
|
||||||
|
|
||||||
public CacheClient(EventBus eventBus, HostCache cache,
|
public CacheClient(EventBus eventBus, HostCache cache,
|
||||||
ConnectionManager manager, I2PSession session,
|
ConnectionManager manager, I2PSession session,
|
||||||
MuWireSettings settings, long interval) {
|
MuWireSettings settings, long interval) {
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.cache = cache
|
this.cache = cache
|
||||||
this.manager = manager
|
this.manager = manager
|
||||||
this.session = session
|
this.session = session
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
this.interval = interval
|
this.interval = interval
|
||||||
this.timer = new Timer("hostcache-client",true)
|
this.timer = new Timer("hostcache-client",true)
|
||||||
}
|
}
|
||||||
|
|
||||||
void start() {
|
void start() {
|
||||||
session.addMuxedSessionListener(new Listener(), I2PSession.PROTO_DATAGRAM, 0)
|
session.addMuxedSessionListener(new Listener(), I2PSession.PROTO_DATAGRAM, 0)
|
||||||
timer.schedule({queryIfNeeded()} as TimerTask, 1, interval)
|
timer.schedule({queryIfNeeded()} as TimerTask, 1, interval)
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
timer.cancel()
|
timer.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
private void queryIfNeeded() {
|
private void queryIfNeeded() {
|
||||||
if (!manager.getConnections().isEmpty())
|
if (!manager.getConnections().isEmpty())
|
||||||
return
|
return
|
||||||
if (!cache.getHosts(1).isEmpty())
|
if (!cache.getHosts(1).isEmpty())
|
||||||
return
|
return
|
||||||
|
|
||||||
log.info "Will query hostcaches"
|
log.info "Will query hostcaches"
|
||||||
|
|
||||||
def ping = [type: "Ping", version: 1, leaf: settings.isLeaf()]
|
def ping = [type: "Ping", version: 1, leaf: settings.isLeaf()]
|
||||||
ping = JsonOutput.toJson(ping)
|
ping = JsonOutput.toJson(ping)
|
||||||
def maker = new I2PDatagramMaker(session)
|
def maker = new I2PDatagramMaker(session)
|
||||||
ping = maker.makeI2PDatagram(ping.bytes)
|
ping = maker.makeI2PDatagram(ping.bytes)
|
||||||
def options = new SendMessageOptions()
|
def options = new SendMessageOptions()
|
||||||
options.setSendLeaseSet(true)
|
options.setSendLeaseSet(true)
|
||||||
CacheServers.getCacheServers().each {
|
CacheServers.getCacheServers().each {
|
||||||
log.info "Querying hostcache ${it.toBase32()}"
|
log.info "Querying hostcache ${it.toBase32()}"
|
||||||
session.sendMessage(it, ping, 0, ping.length, I2PSession.PROTO_DATAGRAM, 1, 0, options)
|
session.sendMessage(it, ping, 0, ping.length, I2PSession.PROTO_DATAGRAM, 1, 0, options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Listener implements I2PSessionMuxedListener {
|
class Listener implements I2PSessionMuxedListener {
|
||||||
|
|
||||||
private final JsonSlurper slurper = new JsonSlurper()
|
private final JsonSlurper slurper = new JsonSlurper()
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void messageAvailable(I2PSession session, int msgId, long size) {
|
public void messageAvailable(I2PSession session, int msgId, long size) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void messageAvailable(I2PSession session, int msgId, long size, int proto, int fromport, int toport) {
|
public void messageAvailable(I2PSession session, int msgId, long size, int proto, int fromport, int toport) {
|
||||||
|
|
||||||
if (proto != I2PSession.PROTO_DATAGRAM) {
|
if (proto != I2PSession.PROTO_DATAGRAM) {
|
||||||
log.warning "Received unexpected protocol $proto"
|
log.warning "Received unexpected protocol $proto"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
def payload = session.receiveMessage(msgId)
|
def payload = session.receiveMessage(msgId)
|
||||||
def dissector = new I2PDatagramDissector()
|
def dissector = new I2PDatagramDissector()
|
||||||
try {
|
try {
|
||||||
dissector.loadI2PDatagram(payload)
|
dissector.loadI2PDatagram(payload)
|
||||||
def sender = dissector.getSender()
|
def sender = dissector.getSender()
|
||||||
log.info("Received something from ${sender.toBase32()}")
|
log.info("Received something from ${sender.toBase32()}")
|
||||||
|
|
||||||
payload = dissector.getPayload()
|
payload = dissector.getPayload()
|
||||||
payload = slurper.parse(payload)
|
payload = slurper.parse(payload)
|
||||||
|
|
||||||
if (payload.type == null) {
|
if (payload.type == null) {
|
||||||
log.warning("type missing")
|
log.warning("type missing")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(payload.type) {
|
switch(payload.type) {
|
||||||
case "Pong" : handlePong(sender, payload); break
|
case "Pong" : handlePong(sender, payload); break
|
||||||
case "CrawlerPing": handleCrawlerPing(session, sender, payload); break
|
case "CrawlerPing": handleCrawlerPing(session, sender, payload); break
|
||||||
default : log.warning("unknown type ${payload.type}")
|
default : log.warning("unknown type ${payload.type}")
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warning("Invalid datagram $e")
|
log.warning("Invalid datagram $e")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reportAbuse(I2PSession session, int severity) {
|
public void reportAbuse(I2PSession session, int severity) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void disconnected(I2PSession session) {
|
public void disconnected(I2PSession session) {
|
||||||
log.severe "I2P session disconnected"
|
log.severe "I2P session disconnected"
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void errorOccurred(I2PSession session, String message, Throwable error) {
|
public void errorOccurred(I2PSession session, String message, Throwable error) {
|
||||||
log.severe "I2P error occured $message $error"
|
log.severe "I2P error occured $message $error"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handlePong(Destination from, def pong) {
|
private void handlePong(Destination from, def pong) {
|
||||||
if (!CacheServers.isRegistered(from)) {
|
if (!CacheServers.isRegistered(from)) {
|
||||||
log.warning("received pong from non-registered destination")
|
log.warning("received pong from non-registered destination")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pong.pongs == null) {
|
if (pong.pongs == null) {
|
||||||
log.warning("malformed pong - no pongs")
|
log.warning("malformed pong - no pongs")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pong.pongs.asList().each {
|
pong.pongs.asList().each {
|
||||||
Destination dest = new Destination(it)
|
Destination dest = new Destination(it)
|
||||||
if (!session.getMyDestination().equals(dest))
|
if (!session.getMyDestination().equals(dest))
|
||||||
eventBus.publish(new HostDiscoveredEvent(destination: dest, fromHostcache : true))
|
eventBus.publish(new HostDiscoveredEvent(destination: dest, fromHostcache : true))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleCrawlerPing(I2PSession session, Destination from, def ping) {
|
private void handleCrawlerPing(I2PSession session, Destination from, def ping) {
|
||||||
if (settings.isLeaf()) {
|
if (settings.isLeaf()) {
|
||||||
log.warning("Received crawler ping but I'm a leaf")
|
log.warning("Received crawler ping but I'm a leaf")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(settings.getCrawlerResponse()) {
|
switch(settings.getCrawlerResponse()) {
|
||||||
case CrawlerResponse.NONE:
|
case CrawlerResponse.NONE:
|
||||||
log.info("Responding to crawlers is disabled by user")
|
log.info("Responding to crawlers is disabled by user")
|
||||||
break
|
break
|
||||||
case CrawlerResponse.ALL:
|
case CrawlerResponse.ALL:
|
||||||
respondToCrawler(session, from, ping)
|
respondToCrawler(session, from, ping)
|
||||||
break;
|
break;
|
||||||
case CrawlerResponse.REGISTERED:
|
case CrawlerResponse.REGISTERED:
|
||||||
if (CacheServers.isRegistered(from))
|
if (CacheServers.isRegistered(from))
|
||||||
respondToCrawler(session, from, ping)
|
respondToCrawler(session, from, ping)
|
||||||
else
|
else
|
||||||
log.warning("Ignoring crawler ping from non-registered crawler")
|
log.warning("Ignoring crawler ping from non-registered crawler")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void respondToCrawler(I2PSession session, Destination from, def ping) {
|
private void respondToCrawler(I2PSession session, Destination from, def ping) {
|
||||||
log.info "responding to crawler ping"
|
log.info "responding to crawler ping"
|
||||||
|
|
||||||
def neighbors = manager.getConnections().collect { c -> c.endpoint.destination.toBase64() }
|
def neighbors = manager.getConnections().collect { c -> c.endpoint.destination.toBase64() }
|
||||||
Collections.shuffle(neighbors)
|
Collections.shuffle(neighbors)
|
||||||
if (neighbors.size() > CRAWLER_RETURN)
|
if (neighbors.size() > CRAWLER_RETURN)
|
||||||
neighbors = neighbors[0..CRAWLER_RETURN - 1]
|
neighbors = neighbors[0..CRAWLER_RETURN - 1]
|
||||||
|
|
||||||
def upManager = (UltrapeerConnectionManager) manager;
|
def upManager = (UltrapeerConnectionManager) manager;
|
||||||
def pong = [:]
|
def pong = [:]
|
||||||
pong.peers = neighbors
|
pong.peers = neighbors
|
||||||
pong.uuid = ping.uuid
|
pong.uuid = ping.uuid
|
||||||
pong.type = "CrawlerPong"
|
pong.type = "CrawlerPong"
|
||||||
pong.version = 1
|
pong.version = 1
|
||||||
pong.leafSlots = upManager.hasLeafSlots()
|
pong.leafSlots = upManager.hasLeafSlots()
|
||||||
pong.peerSlots = upManager.hasPeerSlots()
|
pong.peerSlots = upManager.hasPeerSlots()
|
||||||
pong = JsonOutput.toJson(pong)
|
pong = JsonOutput.toJson(pong)
|
||||||
|
|
||||||
def maker = new I2PDatagramMaker(session)
|
def maker = new I2PDatagramMaker(session)
|
||||||
pong = maker.makeI2PDatagram(pong.bytes)
|
pong = maker.makeI2PDatagram(pong.bytes)
|
||||||
session.sendMessage(from, pong, I2PSession.PROTO_DATAGRAM, 0, 0)
|
session.sendMessage(from, pong, I2PSession.PROTO_DATAGRAM, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -4,20 +4,21 @@ 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")
|
new Destination("Wddh2E6FyyXBF7SvUYHKdN-vjf3~N6uqQWNeBDTM0P33YjiQCOsyedrjmDZmWFrXUJfJLWnCb5bnKezfk4uDaMyj~uvDG~yvLVcFgcPWSUd7BfGgym-zqcG1q1DcM8vfun-US7YamBlmtC6MZ2j-~Igqzmgshita8aLPCfNAA6S6e2UMjjtG7QIXlxpMec75dkHdJlVWbzrk9z8Qgru3YIk0UztYgEwDNBbm9wInsbHhr3HtAfa02QcgRVqRN2PnQXuqUJs7R7~09FZPEviiIcUpkY3FeyLlX1sgQFBeGeA96blaPvZNGd6KnNdgfLgMebx5SSxC-N4KZMSMBz5cgonQF3~m2HHFRSI85zqZNG5X9bJN85t80ltiv1W1es8ZnQW4es11r7MrvJNXz5bmSH641yJIvS6qI8OJJNpFVBIQSXLD-96TayrLQPaYw~uNZ-eXaE6G5dYhiuN8xHsFI1QkdaUaVZnvDGfsRbpS5GtpUbBDbyLkdPurG0i7dN1wAAAA"),
|
||||||
]
|
new Destination("JC63wJNOqSJmymkj4~UJWywBTvDGikKMoYP0HX2Wz9c5l3otXSkwnxWAFL4cKr~Ygh3BNNi2t93vuLIiI1W8AsE42kR~PwRx~Y-WvIHXR6KUejRmOp-n8WidtjKg9k4aDy428uSOedqXDxys5mpoeQXwDsv1CoPTTwnmb1GWFy~oTGIsCguCl~aJWGnqiKarPO3GJQ~ev-NbvAQzUfC3HeP1e6pdI5CGGjExahTCID5UjpJw8GaDXWlGmYWWH303Xu4x-vAHQy1dJLsOBCn8dZravsn5BKJk~j0POUon45CCx-~NYtaPe0Itt9cMdD2ciC76Rep1D0X0sm1SjlSs8sZ52KmF3oaLZ6OzgI9QLMIyBUrfi41sK5I0qTuUVBAkvW1xr~L-20dYJ9TrbOaOb2-vDIfKaxVi6xQOuhgQDiSBhd3qv2m0xGu-BM9DQYfNA0FdMjnZmqjmji9RMavzQSsVFIbQGLbrLepiEFlb7TseCK5UtRp8TxnG7L4gbYevBQAEAAcAAA==")
|
||||||
|
]
|
||||||
|
|
||||||
static List<Destination> getCacheServers() {
|
static List<Destination> getCacheServers() {
|
||||||
List<Destination> allCaches = new ArrayList<>(CACHES)
|
List<Destination> allCaches = new ArrayList<>(CACHES)
|
||||||
Collections.shuffle(allCaches)
|
Collections.shuffle(allCaches)
|
||||||
if (allCaches.size() <= TO_GIVE)
|
if (allCaches.size() <= TO_GIVE)
|
||||||
return allCaches
|
return allCaches
|
||||||
allCaches[0..TO_GIVE-1]
|
allCaches[0..TO_GIVE-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
static boolean isRegistered(Destination d) {
|
static boolean isRegistered(Destination d) {
|
||||||
return CACHES.contains(d)
|
return CACHES.contains(d)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,42 +4,43 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
class Host {
|
class Host {
|
||||||
|
|
||||||
private static final int MAX_FAILURES = 3
|
private static final int MAX_FAILURES = 3
|
||||||
private static final int CLEAR_INTERVAL = 60 * 60 * 1000
|
|
||||||
|
|
||||||
final Destination destination
|
final Destination destination
|
||||||
int failures,successes
|
private final int clearInterval
|
||||||
|
int failures,successes
|
||||||
long lastAttempt
|
long lastAttempt
|
||||||
|
|
||||||
public Host(Destination destination) {
|
public Host(Destination destination, int clearInterval) {
|
||||||
this.destination = destination
|
this.destination = destination
|
||||||
}
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void canTryAgain() {
|
synchronized void canTryAgain() {
|
||||||
System.currentTimeMillis() - lastAttempt > CLEAR_INTERVAL
|
System.currentTimeMillis() - lastAttempt > (clearInterval * 60 * 1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
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)
|
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)
|
Host host = new Host(dest, settings.hostClearInterval)
|
||||||
host.failures = Integer.valueOf(String.valueOf(entry.failures))
|
host.failures = Integer.valueOf(String.valueOf(entry.failures))
|
||||||
host.successes = Integer.valueOf(String.valueOf(entry.successes))
|
host.successes = Integer.valueOf(String.valueOf(entry.successes))
|
||||||
if (entry.lastAttempt != null)
|
if (entry.lastAttempt != null)
|
||||||
host.lastAttempt = entry.lastAttempt
|
host.lastAttempt = entry.lastAttempt
|
||||||
if (allowHost(host))
|
if (allowHost(host))
|
||||||
hosts.put(dest, host)
|
hosts.put(dest, host)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
timer.schedule({save()} as TimerTask, interval, interval)
|
timer.schedule({save()} as TimerTask, interval, interval)
|
||||||
loaded = true
|
loaded = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean allowHost(Host host) {
|
private boolean allowHost(Host host) {
|
||||||
if (host.isFailed() && !host.canTryAgain())
|
if (host.isFailed() && !host.canTryAgain())
|
||||||
return false
|
return false
|
||||||
if (host.destination == myself)
|
if (host.destination == myself)
|
||||||
return false
|
return false
|
||||||
TrustLevel trust = trustService.getLevel(host.destination)
|
TrustLevel trust = trustService.getLevel(host.destination)
|
||||||
switch(trust) {
|
switch(trust) {
|
||||||
case TrustLevel.DISTRUSTED :
|
case TrustLevel.DISTRUSTED :
|
||||||
return false
|
return false
|
||||||
case TrustLevel.TRUSTED :
|
case TrustLevel.TRUSTED :
|
||||||
return true
|
return true
|
||||||
case TrustLevel.NEUTRAL :
|
case TrustLevel.NEUTRAL :
|
||||||
return settings.allowUntrusted()
|
return settings.allowUntrusted()
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
private void save() {
|
private void save() {
|
||||||
storage.delete()
|
storage.delete()
|
||||||
storage.withPrintWriter { writer ->
|
storage.withPrintWriter { writer ->
|
||||||
hosts.each { dest, host ->
|
hosts.each { dest, host ->
|
||||||
if (allowHost(host)) {
|
if (allowHost(host)) {
|
||||||
def map = [:]
|
def map = [:]
|
||||||
map.destination = dest.toBase64()
|
map.destination = dest.toBase64()
|
||||||
map.failures = host.failures
|
map.failures = host.failures
|
||||||
map.successes = host.successes
|
map.successes = host.successes
|
||||||
map.lastAttempt = host.lastAttempt
|
map.lastAttempt = host.lastAttempt
|
||||||
def json = JsonOutput.toJson(map)
|
def json = JsonOutput.toJson(map)
|
||||||
writer.println json
|
writer.println json
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,11 +6,11 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
class HostDiscoveredEvent extends Event {
|
class HostDiscoveredEvent extends Event {
|
||||||
|
|
||||||
Destination destination
|
Destination destination
|
||||||
boolean fromHostcache
|
boolean fromHostcache
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
"HostDiscoveredEvent ${super.toString()} destination:${destination.toBase32()} from hostcache $fromHostcache"
|
"HostDiscoveredEvent ${super.toString()} destination:${destination.toBase32()} from hostcache $fromHostcache"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,10 +4,12 @@ import java.util.stream.Collectors
|
|||||||
|
|
||||||
import com.muwire.core.Constants
|
import com.muwire.core.Constants
|
||||||
import com.muwire.core.InfoHash
|
import com.muwire.core.InfoHash
|
||||||
|
import com.muwire.core.MuWireSettings
|
||||||
import com.muwire.core.Persona
|
import com.muwire.core.Persona
|
||||||
import com.muwire.core.download.Pieces
|
import com.muwire.core.download.Pieces
|
||||||
import com.muwire.core.download.SourceDiscoveredEvent
|
import com.muwire.core.download.SourceDiscoveredEvent
|
||||||
import com.muwire.core.files.FileManager
|
import com.muwire.core.files.FileManager
|
||||||
|
import com.muwire.core.util.DataUtil
|
||||||
|
|
||||||
import groovy.json.JsonOutput
|
import groovy.json.JsonOutput
|
||||||
import groovy.json.JsonSlurper
|
import groovy.json.JsonSlurper
|
||||||
@@ -15,15 +17,15 @@ import net.i2p.data.Base64
|
|||||||
|
|
||||||
class MeshManager {
|
class MeshManager {
|
||||||
|
|
||||||
private static final int EXPIRATION = 60 * 60 * 1000
|
|
||||||
|
|
||||||
private final Map<InfoHash, Mesh> meshes = Collections.synchronizedMap(new HashMap<>())
|
private final Map<InfoHash, Mesh> meshes = Collections.synchronizedMap(new HashMap<>())
|
||||||
private final FileManager fileManager
|
private final FileManager fileManager
|
||||||
private final File home
|
private final File home
|
||||||
|
private final MuWireSettings settings
|
||||||
|
|
||||||
MeshManager(FileManager fileManager, File home) {
|
MeshManager(FileManager fileManager, File home, MuWireSettings settings) {
|
||||||
this.fileManager = fileManager
|
this.fileManager = fileManager
|
||||||
this.home = home
|
this.home = home
|
||||||
|
this.settings = settings
|
||||||
load()
|
load()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +37,7 @@ class MeshManager {
|
|||||||
synchronized(meshes) {
|
synchronized(meshes) {
|
||||||
if (meshes.containsKey(infoHash))
|
if (meshes.containsKey(infoHash))
|
||||||
return meshes.get(infoHash)
|
return meshes.get(infoHash)
|
||||||
Pieces pieces = new Pieces(nPieces, Constants.DOWNLOAD_SEQUENTIAL_RATIO)
|
Pieces pieces = new Pieces(nPieces, settings.downloadSequentialRatio)
|
||||||
if (fileManager.rootToFiles.containsKey(infoHash)) {
|
if (fileManager.rootToFiles.containsKey(infoHash)) {
|
||||||
for (int i = 0; i < nPieces; i++)
|
for (int i = 0; i < nPieces; i++)
|
||||||
pieces.markDownloaded(i)
|
pieces.markDownloaded(i)
|
||||||
@@ -50,8 +52,8 @@ class MeshManager {
|
|||||||
Mesh mesh = meshes.get(e.infoHash)
|
Mesh mesh = meshes.get(e.infoHash)
|
||||||
if (mesh == null)
|
if (mesh == null)
|
||||||
return
|
return
|
||||||
if (mesh.sources.add(e.source))
|
mesh.sources.add(e.source)
|
||||||
save()
|
save()
|
||||||
}
|
}
|
||||||
|
|
||||||
private void save() {
|
private void save() {
|
||||||
@@ -64,6 +66,7 @@ class MeshManager {
|
|||||||
json.infoHash = Base64.encode(mesh.infoHash.getRoot())
|
json.infoHash = Base64.encode(mesh.infoHash.getRoot())
|
||||||
json.sources = mesh.sources.stream().map({it.toBase64()}).collect(Collectors.toList())
|
json.sources = mesh.sources.stream().map({it.toBase64()}).collect(Collectors.toList())
|
||||||
json.nPieces = mesh.pieces.nPieces
|
json.nPieces = mesh.pieces.nPieces
|
||||||
|
json.xHave = DataUtil.encodeXHave(mesh.pieces.downloaded, mesh.pieces.nPieces)
|
||||||
writer.println(JsonOutput.toJson(json))
|
writer.println(JsonOutput.toJson(json))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,10 +81,10 @@ class MeshManager {
|
|||||||
JsonSlurper slurper = new JsonSlurper()
|
JsonSlurper slurper = new JsonSlurper()
|
||||||
meshFile.eachLine {
|
meshFile.eachLine {
|
||||||
def json = slurper.parseText(it)
|
def json = slurper.parseText(it)
|
||||||
if (now - json.timestamp > EXPIRATION)
|
if (now - json.timestamp > settings.meshExpiration * 60 * 1000)
|
||||||
return
|
return
|
||||||
InfoHash infoHash = new InfoHash(Base64.decode(json.infoHash))
|
InfoHash infoHash = new InfoHash(Base64.decode(json.infoHash))
|
||||||
Pieces pieces = new Pieces(json.nPieces, Constants.DOWNLOAD_SEQUENTIAL_RATIO)
|
Pieces pieces = new Pieces(json.nPieces, settings.downloadSequentialRatio)
|
||||||
|
|
||||||
Mesh mesh = new Mesh(infoHash, pieces)
|
Mesh mesh = new Mesh(infoHash, pieces)
|
||||||
json.sources.each { source ->
|
json.sources.each { source ->
|
||||||
@@ -89,6 +92,9 @@ class MeshManager {
|
|||||||
mesh.sources.add(persona)
|
mesh.sources.add(persona)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (json.xHave != null)
|
||||||
|
DataUtil.decodeXHave(json.xHave).each { pieces.markDownloaded(it) }
|
||||||
|
|
||||||
if (!mesh.sources.isEmpty())
|
if (!mesh.sources.isEmpty())
|
||||||
meshes.put(infoHash, mesh)
|
meshes.put(infoHash, mesh)
|
||||||
}
|
}
|
||||||
|
@@ -6,11 +6,11 @@ import net.i2p.data.Base32
|
|||||||
import net.i2p.data.Destination
|
import net.i2p.data.Destination
|
||||||
|
|
||||||
class DeleteEvent extends Event {
|
class DeleteEvent extends Event {
|
||||||
byte [] infoHash
|
byte [] infoHash
|
||||||
Destination leaf
|
Destination leaf
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
"DeleteEvent ${super.toString()} infoHash:${Base32.encode(infoHash)} leaf:${leaf.toBase32()}"
|
"DeleteEvent ${super.toString()} infoHash:${Base32.encode(infoHash)} leaf:${leaf.toBase32()}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,32 +7,32 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
class LeafSearcher {
|
class LeafSearcher {
|
||||||
|
|
||||||
final UltrapeerConnectionManager connectionManager
|
final UltrapeerConnectionManager connectionManager
|
||||||
final SearchIndex searchIndex = new SearchIndex()
|
final SearchIndex searchIndex = new SearchIndex()
|
||||||
|
|
||||||
final Map<String, Set<byte[]>> fileNameToHashes = new HashMap<>()
|
final Map<String, Set<byte[]>> fileNameToHashes = new HashMap<>()
|
||||||
final Map<byte[], Set<Destination>> hashToLeafs = new HashMap<>()
|
final Map<byte[], Set<Destination>> hashToLeafs = new HashMap<>()
|
||||||
|
|
||||||
final Map<Destination, Map<byte[], Set<String>>> leafToFiles = new HashMap<>()
|
final Map<Destination, Map<byte[], Set<String>>> leafToFiles = new HashMap<>()
|
||||||
|
|
||||||
LeafSearcher(UltrapeerConnectionManager connectionManager) {
|
LeafSearcher(UltrapeerConnectionManager connectionManager) {
|
||||||
this.connectionManager = connectionManager
|
this.connectionManager = connectionManager
|
||||||
}
|
}
|
||||||
|
|
||||||
void onUpsertEvent(UpsertEvent e) {
|
void onUpsertEvent(UpsertEvent e) {
|
||||||
// TODO: implement
|
// TODO: implement
|
||||||
}
|
}
|
||||||
|
|
||||||
void onDeleteEvent(DeleteEvent e) {
|
void onDeleteEvent(DeleteEvent e) {
|
||||||
// TODO: implement
|
// TODO: implement
|
||||||
}
|
}
|
||||||
|
|
||||||
void onDisconnectionEvent(DisconnectionEvent e) {
|
void onDisconnectionEvent(DisconnectionEvent e) {
|
||||||
// TODO: implement
|
// TODO: implement
|
||||||
}
|
}
|
||||||
|
|
||||||
void onQueryEvent(QueryEvent e) {
|
void onQueryEvent(QueryEvent e) {
|
||||||
// TODO: implement
|
// TODO: implement
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -8,10 +8,10 @@ import net.i2p.data.Destination
|
|||||||
class QueryEvent extends Event {
|
class QueryEvent extends Event {
|
||||||
|
|
||||||
SearchEvent searchEvent
|
SearchEvent searchEvent
|
||||||
boolean firstHop
|
boolean firstHop
|
||||||
Destination replyTo
|
Destination replyTo
|
||||||
Persona originator
|
Persona originator
|
||||||
Destination receivedOn
|
Destination receivedOn
|
||||||
|
|
||||||
String toString() {
|
String toString() {
|
||||||
"searchEvent: $searchEvent firstHop:$firstHop, replyTo:${replyTo.toBase32()}" +
|
"searchEvent: $searchEvent firstHop:$firstHop, replyTo:${replyTo.toBase32()}" +
|
||||||
|
@@ -6,6 +6,6 @@ import com.muwire.core.SharedFile
|
|||||||
class ResultsEvent extends Event {
|
class ResultsEvent extends Event {
|
||||||
|
|
||||||
SearchEvent searchEvent
|
SearchEvent searchEvent
|
||||||
SharedFile[] results
|
SharedFile[] results
|
||||||
UUID uuid
|
UUID uuid
|
||||||
}
|
}
|
||||||
|
@@ -5,9 +5,9 @@ import com.muwire.core.InfoHash
|
|||||||
|
|
||||||
class SearchEvent extends Event {
|
class SearchEvent extends Event {
|
||||||
|
|
||||||
List<String> searchTerms
|
List<String> searchTerms
|
||||||
byte [] searchHash
|
byte [] searchHash
|
||||||
UUID uuid
|
UUID uuid
|
||||||
boolean oobInfohash
|
boolean oobInfohash
|
||||||
|
|
||||||
String toString() {
|
String toString() {
|
||||||
|
@@ -4,56 +4,56 @@ import com.muwire.core.Constants
|
|||||||
|
|
||||||
class SearchIndex {
|
class SearchIndex {
|
||||||
|
|
||||||
final Map<String, Set<String>> keywords = new HashMap<>()
|
final Map<String, Set<String>> keywords = new HashMap<>()
|
||||||
|
|
||||||
void add(String string) {
|
void add(String string) {
|
||||||
String [] split = split(string)
|
String [] split = split(string)
|
||||||
split.each {
|
split.each {
|
||||||
Set<String> existing = keywords.get(it)
|
Set<String> existing = keywords.get(it)
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
existing = new HashSet<>()
|
existing = new HashSet<>()
|
||||||
keywords.put(it, existing)
|
keywords.put(it, existing)
|
||||||
}
|
}
|
||||||
existing.add(string)
|
existing.add(string)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void remove(String string) {
|
void remove(String string) {
|
||||||
String [] split = split(string)
|
String [] split = split(string)
|
||||||
split.each {
|
split.each {
|
||||||
Set<String> existing = keywords.get it
|
Set<String> existing = keywords.get it
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
existing.remove(string)
|
existing.remove(string)
|
||||||
if (existing.isEmpty()) {
|
if (existing.isEmpty()) {
|
||||||
keywords.remove(it)
|
keywords.remove(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String[] split(String source) {
|
private static String[] split(String source) {
|
||||||
source = source.replaceAll(Constants.SPLIT_PATTERN, " ").toLowerCase()
|
source = source.replaceAll(Constants.SPLIT_PATTERN, " ").toLowerCase()
|
||||||
String [] split = source.split(" ")
|
String [] split = source.split(" ")
|
||||||
def rv = []
|
def rv = []
|
||||||
split.each { if (it.length() > 0) rv << it }
|
split.each { if (it.length() > 0) rv << it }
|
||||||
rv.toArray(new String[0])
|
rv.toArray(new String[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
String[] search(List<String> terms) {
|
String[] search(List<String> terms) {
|
||||||
Set<String> rv = null;
|
Set<String> rv = null;
|
||||||
|
|
||||||
terms.each {
|
terms.each {
|
||||||
Set<String> forWord = keywords.getOrDefault(it,[])
|
Set<String> forWord = keywords.getOrDefault(it,[])
|
||||||
if (rv == null) {
|
if (rv == null) {
|
||||||
rv = new HashSet<>(forWord)
|
rv = new HashSet<>(forWord)
|
||||||
} else {
|
} else {
|
||||||
rv.retainAll(forWord)
|
rv.retainAll(forWord)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rv != null)
|
if (rv != null)
|
||||||
return rv.asList()
|
return rv.asList()
|
||||||
[]
|
[]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,12 +7,12 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
class UpsertEvent extends Event {
|
class UpsertEvent extends Event {
|
||||||
|
|
||||||
Set<String> names
|
Set<String> names
|
||||||
byte [] infoHash
|
byte [] infoHash
|
||||||
Destination leaf
|
Destination leaf
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
"UpsertEvent ${super.toString()} names:$names infoHash:${Base32.encode(infoHash)} leaf:${leaf.toBase32()}"
|
"UpsertEvent ${super.toString()} names:$names infoHash:${Base32.encode(infoHash)} leaf:${leaf.toBase32()}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,31 @@
|
|||||||
|
package com.muwire.core.trust
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
|
import com.muwire.core.Persona
|
||||||
|
|
||||||
|
import net.i2p.util.ConcurrentHashSet
|
||||||
|
|
||||||
|
class RemoteTrustList {
|
||||||
|
public enum Status { NEW, UPDATING, UPDATED, UPDATE_FAILED }
|
||||||
|
|
||||||
|
private final Persona persona
|
||||||
|
private final Set<Persona> good, bad
|
||||||
|
volatile long timestamp
|
||||||
|
volatile boolean forceUpdate
|
||||||
|
Status status = Status.NEW
|
||||||
|
|
||||||
|
RemoteTrustList(Persona persona) {
|
||||||
|
this.persona = persona
|
||||||
|
good = new ConcurrentHashSet<>()
|
||||||
|
bad = new ConcurrentHashSet<>()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (!(o instanceof RemoteTrustList))
|
||||||
|
return false
|
||||||
|
RemoteTrustList other = (RemoteTrustList)o
|
||||||
|
persona == other.persona
|
||||||
|
}
|
||||||
|
}
|
@@ -5,6 +5,6 @@ import com.muwire.core.Persona
|
|||||||
|
|
||||||
class TrustEvent extends Event {
|
class TrustEvent extends Event {
|
||||||
|
|
||||||
Persona persona
|
Persona persona
|
||||||
TrustLevel level
|
TrustLevel level
|
||||||
}
|
}
|
||||||
|
@@ -11,87 +11,87 @@ import net.i2p.util.ConcurrentHashSet
|
|||||||
|
|
||||||
class TrustService extends Service {
|
class TrustService extends Service {
|
||||||
|
|
||||||
final File persistGood, persistBad
|
final File persistGood, persistBad
|
||||||
final long persistInterval
|
final long persistInterval
|
||||||
|
|
||||||
final Map<Destination, Persona> good = new ConcurrentHashMap<>()
|
final Map<Destination, Persona> good = new ConcurrentHashMap<>()
|
||||||
final Map<Destination, Persona> bad = new ConcurrentHashMap<>()
|
final Map<Destination, Persona> bad = new ConcurrentHashMap<>()
|
||||||
|
|
||||||
final Timer timer
|
final Timer timer
|
||||||
|
|
||||||
TrustService() {}
|
TrustService() {}
|
||||||
|
|
||||||
TrustService(File persistGood, File persistBad, long persistInterval) {
|
TrustService(File persistGood, File persistBad, long persistInterval) {
|
||||||
this.persistBad = persistBad
|
this.persistBad = persistBad
|
||||||
this.persistGood = persistGood
|
this.persistGood = persistGood
|
||||||
this.persistInterval = persistInterval
|
this.persistInterval = persistInterval
|
||||||
this.timer = new Timer("trust-persister",true)
|
this.timer = new Timer("trust-persister",true)
|
||||||
}
|
}
|
||||||
|
|
||||||
void start() {
|
void start() {
|
||||||
timer.schedule({load()} as TimerTask, 1)
|
timer.schedule({load()} as TimerTask, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
timer.cancel()
|
timer.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
void load() {
|
void load() {
|
||||||
if (persistGood.exists()) {
|
if (persistGood.exists()) {
|
||||||
persistGood.eachLine {
|
persistGood.eachLine {
|
||||||
byte [] decoded = Base64.decode(it)
|
byte [] decoded = Base64.decode(it)
|
||||||
Persona persona = new Persona(new ByteArrayInputStream(decoded))
|
Persona persona = new Persona(new ByteArrayInputStream(decoded))
|
||||||
good.put(persona.destination, persona)
|
good.put(persona.destination, persona)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (persistBad.exists()) {
|
if (persistBad.exists()) {
|
||||||
persistBad.eachLine {
|
persistBad.eachLine {
|
||||||
byte [] decoded = Base64.decode(it)
|
byte [] decoded = Base64.decode(it)
|
||||||
Persona persona = new Persona(new ByteArrayInputStream(decoded))
|
Persona persona = new Persona(new ByteArrayInputStream(decoded))
|
||||||
bad.put(persona.destination, persona)
|
bad.put(persona.destination, persona)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
timer.schedule({persist()} as TimerTask, persistInterval, persistInterval)
|
timer.schedule({persist()} as TimerTask, persistInterval, persistInterval)
|
||||||
loaded = true
|
loaded = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private void persist() {
|
private void persist() {
|
||||||
persistGood.delete()
|
persistGood.delete()
|
||||||
persistGood.withPrintWriter { writer ->
|
persistGood.withPrintWriter { writer ->
|
||||||
good.each {k,v ->
|
good.each {k,v ->
|
||||||
writer.println v.toBase64()
|
writer.println v.toBase64()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
persistBad.delete()
|
persistBad.delete()
|
||||||
persistBad.withPrintWriter { writer ->
|
persistBad.withPrintWriter { writer ->
|
||||||
bad.each { k,v ->
|
bad.each { k,v ->
|
||||||
writer.println v.toBase64()
|
writer.println v.toBase64()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TrustLevel getLevel(Destination dest) {
|
TrustLevel getLevel(Destination dest) {
|
||||||
if (good.containsKey(dest))
|
if (good.containsKey(dest))
|
||||||
return TrustLevel.TRUSTED
|
return TrustLevel.TRUSTED
|
||||||
else if (bad.containsKey(dest))
|
else if (bad.containsKey(dest))
|
||||||
return TrustLevel.DISTRUSTED
|
return TrustLevel.DISTRUSTED
|
||||||
TrustLevel.NEUTRAL
|
TrustLevel.NEUTRAL
|
||||||
}
|
}
|
||||||
|
|
||||||
void onTrustEvent(TrustEvent e) {
|
void onTrustEvent(TrustEvent e) {
|
||||||
switch(e.level) {
|
switch(e.level) {
|
||||||
case TrustLevel.TRUSTED:
|
case TrustLevel.TRUSTED:
|
||||||
bad.remove(e.persona.destination)
|
bad.remove(e.persona.destination)
|
||||||
good.put(e.persona.destination, e.persona)
|
good.put(e.persona.destination, e.persona)
|
||||||
break
|
break
|
||||||
case TrustLevel.DISTRUSTED:
|
case TrustLevel.DISTRUSTED:
|
||||||
good.remove(e.persona.destination)
|
good.remove(e.persona.destination)
|
||||||
bad.put(e.persona.destination, e.persona)
|
bad.put(e.persona.destination, e.persona)
|
||||||
break
|
break
|
||||||
case TrustLevel.NEUTRAL:
|
case TrustLevel.NEUTRAL:
|
||||||
good.remove(e.persona.destination)
|
good.remove(e.persona.destination)
|
||||||
bad.remove(e.persona.destination)
|
bad.remove(e.persona.destination)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,161 @@
|
|||||||
|
package com.muwire.core.trust
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import java.util.concurrent.ExecutorService
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
import java.util.logging.Level
|
||||||
|
|
||||||
|
import com.muwire.core.EventBus
|
||||||
|
import com.muwire.core.MuWireSettings
|
||||||
|
import com.muwire.core.Persona
|
||||||
|
import com.muwire.core.UILoadedEvent
|
||||||
|
import com.muwire.core.connection.Endpoint
|
||||||
|
import com.muwire.core.connection.I2PConnector
|
||||||
|
import com.muwire.core.util.DataUtil
|
||||||
|
|
||||||
|
import groovy.util.logging.Log
|
||||||
|
import net.i2p.data.Destination
|
||||||
|
|
||||||
|
@Log
|
||||||
|
class TrustSubscriber {
|
||||||
|
private final EventBus eventBus
|
||||||
|
private final I2PConnector i2pConnector
|
||||||
|
private final MuWireSettings settings
|
||||||
|
|
||||||
|
private final Map<Destination, RemoteTrustList> remoteTrustLists = new ConcurrentHashMap<>()
|
||||||
|
|
||||||
|
private final Object waitLock = new Object()
|
||||||
|
private volatile boolean shutdown
|
||||||
|
private volatile Thread thread
|
||||||
|
private final ExecutorService updateThreads = Executors.newCachedThreadPool()
|
||||||
|
|
||||||
|
TrustSubscriber(EventBus eventBus, I2PConnector i2pConnector, MuWireSettings settings) {
|
||||||
|
this.eventBus = eventBus
|
||||||
|
this.i2pConnector = i2pConnector
|
||||||
|
this.settings = settings
|
||||||
|
}
|
||||||
|
|
||||||
|
void onUILoadedEvent(UILoadedEvent e) {
|
||||||
|
thread = new Thread({checkLoop()} as Runnable, "trust-subscriber")
|
||||||
|
thread.setDaemon(true)
|
||||||
|
thread.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop() {
|
||||||
|
shutdown = true
|
||||||
|
thread?.interrupt()
|
||||||
|
updateThreads.shutdownNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
void onTrustSubscriptionEvent(TrustSubscriptionEvent e) {
|
||||||
|
if (!e.subscribe) {
|
||||||
|
remoteTrustLists.remove(e.persona.destination)
|
||||||
|
} else {
|
||||||
|
RemoteTrustList trustList = remoteTrustLists.putIfAbsent(e.persona.destination, new RemoteTrustList(e.persona))
|
||||||
|
trustList?.forceUpdate = true
|
||||||
|
synchronized(waitLock) {
|
||||||
|
waitLock.notify()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkLoop() {
|
||||||
|
try {
|
||||||
|
while(!shutdown) {
|
||||||
|
synchronized(waitLock) {
|
||||||
|
waitLock.wait(60 * 1000)
|
||||||
|
}
|
||||||
|
final long now = System.currentTimeMillis()
|
||||||
|
remoteTrustLists.values().each { trustList ->
|
||||||
|
if (trustList.status == RemoteTrustList.Status.UPDATING)
|
||||||
|
return
|
||||||
|
if (!trustList.forceUpdate &&
|
||||||
|
now - trustList.timestamp < settings.trustListInterval * 60 * 60 * 1000)
|
||||||
|
return
|
||||||
|
trustList.forceUpdate = false
|
||||||
|
updateThreads.submit(new UpdateJob(trustList))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
if (!shutdown)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class UpdateJob implements Runnable {
|
||||||
|
|
||||||
|
private final RemoteTrustList trustList
|
||||||
|
|
||||||
|
UpdateJob(RemoteTrustList trustList) {
|
||||||
|
this.trustList = trustList
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
trustList.status = RemoteTrustList.Status.UPDATING
|
||||||
|
eventBus.publish(new TrustSubscriptionUpdatedEvent(trustList : trustList))
|
||||||
|
if (check(trustList, System.currentTimeMillis()))
|
||||||
|
trustList.status = RemoteTrustList.Status.UPDATED
|
||||||
|
else
|
||||||
|
trustList.status = RemoteTrustList.Status.UPDATE_FAILED
|
||||||
|
eventBus.publish(new TrustSubscriptionUpdatedEvent(trustList : trustList))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean check(RemoteTrustList trustList, long now) {
|
||||||
|
log.info("fetching trust list from ${trustList.persona.getHumanReadableName()}")
|
||||||
|
Endpoint endpoint = null
|
||||||
|
try {
|
||||||
|
endpoint = i2pConnector.connect(trustList.persona.destination)
|
||||||
|
OutputStream os = endpoint.getOutputStream()
|
||||||
|
InputStream is = endpoint.getInputStream()
|
||||||
|
os.write("TRUST\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
|
os.flush()
|
||||||
|
|
||||||
|
String codeString = DataUtil.readTillRN(is)
|
||||||
|
int space = codeString.indexOf(' ')
|
||||||
|
if (space > 0)
|
||||||
|
codeString = codeString.substring(0,space)
|
||||||
|
int code = Integer.parseInt(codeString.trim())
|
||||||
|
|
||||||
|
if (code != 200) {
|
||||||
|
log.info("couldn't fetch trust list, code $code")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// swallow any headers
|
||||||
|
String header
|
||||||
|
while (( header = DataUtil.readTillRN(is)) != "");
|
||||||
|
|
||||||
|
DataInputStream dis = new DataInputStream(is)
|
||||||
|
|
||||||
|
Set<Persona> good = new HashSet<>()
|
||||||
|
int nGood = dis.readUnsignedShort()
|
||||||
|
for (int i = 0; i < nGood; i++) {
|
||||||
|
Persona p = new Persona(dis)
|
||||||
|
good.add(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<Persona> bad = new HashSet<>()
|
||||||
|
int nBad = dis.readUnsignedShort()
|
||||||
|
for (int i = 0; i < nBad; i++) {
|
||||||
|
Persona p = new Persona(dis)
|
||||||
|
bad.add(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
trustList.timestamp = now
|
||||||
|
trustList.good.clear()
|
||||||
|
trustList.good.addAll(good)
|
||||||
|
trustList.bad.clear()
|
||||||
|
trustList.bad.addAll(bad)
|
||||||
|
|
||||||
|
return true
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.log(Level.WARNING,"exception fetching trust list from ${trustList.persona.getHumanReadableName()}",e)
|
||||||
|
return false
|
||||||
|
} finally {
|
||||||
|
endpoint?.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,9 @@
|
|||||||
|
package com.muwire.core.trust
|
||||||
|
|
||||||
|
import com.muwire.core.Event
|
||||||
|
import com.muwire.core.Persona
|
||||||
|
|
||||||
|
class TrustSubscriptionEvent extends Event {
|
||||||
|
Persona persona
|
||||||
|
boolean subscribe
|
||||||
|
}
|
@@ -0,0 +1,7 @@
|
|||||||
|
package com.muwire.core.trust
|
||||||
|
|
||||||
|
import com.muwire.core.Event
|
||||||
|
|
||||||
|
class TrustSubscriptionUpdatedEvent extends Event {
|
||||||
|
RemoteTrustList trustList
|
||||||
|
}
|
@@ -3,7 +3,15 @@ package com.muwire.core.update
|
|||||||
import java.util.logging.Level
|
import java.util.logging.Level
|
||||||
|
|
||||||
import com.muwire.core.EventBus
|
import com.muwire.core.EventBus
|
||||||
|
import com.muwire.core.InfoHash
|
||||||
import com.muwire.core.MuWireSettings
|
import com.muwire.core.MuWireSettings
|
||||||
|
import com.muwire.core.Persona
|
||||||
|
import com.muwire.core.download.UIDownloadEvent
|
||||||
|
import com.muwire.core.files.FileDownloadedEvent
|
||||||
|
import com.muwire.core.files.FileManager
|
||||||
|
import com.muwire.core.search.QueryEvent
|
||||||
|
import com.muwire.core.search.SearchEvent
|
||||||
|
import com.muwire.core.search.UIResultBatchEvent
|
||||||
|
|
||||||
import groovy.json.JsonOutput
|
import groovy.json.JsonOutput
|
||||||
import groovy.json.JsonSlurper
|
import groovy.json.JsonSlurper
|
||||||
@@ -13,6 +21,7 @@ import net.i2p.client.I2PSessionMuxedListener
|
|||||||
import net.i2p.client.SendMessageOptions
|
import net.i2p.client.SendMessageOptions
|
||||||
import net.i2p.client.datagram.I2PDatagramDissector
|
import net.i2p.client.datagram.I2PDatagramDissector
|
||||||
import net.i2p.client.datagram.I2PDatagramMaker
|
import net.i2p.client.datagram.I2PDatagramMaker
|
||||||
|
import net.i2p.data.Base64
|
||||||
import net.i2p.util.VersionComparator
|
import net.i2p.util.VersionComparator
|
||||||
|
|
||||||
@Log
|
@Log
|
||||||
@@ -21,16 +30,24 @@ class UpdateClient {
|
|||||||
final I2PSession session
|
final I2PSession session
|
||||||
final String myVersion
|
final String myVersion
|
||||||
final MuWireSettings settings
|
final MuWireSettings settings
|
||||||
|
final FileManager fileManager
|
||||||
|
final Persona me
|
||||||
|
|
||||||
private final Timer timer
|
private final Timer timer
|
||||||
|
|
||||||
private long lastUpdateCheckTime
|
private long lastUpdateCheckTime
|
||||||
|
|
||||||
UpdateClient(EventBus eventBus, I2PSession session, String myVersion, MuWireSettings settings) {
|
private volatile InfoHash updateInfoHash
|
||||||
|
private volatile String version, signer
|
||||||
|
private volatile boolean updateDownloading
|
||||||
|
|
||||||
|
UpdateClient(EventBus eventBus, I2PSession session, String myVersion, MuWireSettings settings, FileManager fileManager, Persona me) {
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.session = session
|
this.session = session
|
||||||
this.myVersion = myVersion
|
this.myVersion = myVersion
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
|
this.fileManager = fileManager
|
||||||
|
this.me = me
|
||||||
timer = new Timer("update-client",true)
|
timer = new Timer("update-client",true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,6 +60,24 @@ class UpdateClient {
|
|||||||
timer.cancel()
|
timer.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onUIResultBatchEvent(UIResultBatchEvent results) {
|
||||||
|
if (results.results[0].infohash != updateInfoHash)
|
||||||
|
return
|
||||||
|
if (updateDownloading)
|
||||||
|
return
|
||||||
|
updateDownloading = true
|
||||||
|
def file = new File(settings.downloadLocation, results.results[0].name)
|
||||||
|
def downloadEvent = new UIDownloadEvent(result: results.results[0], sources : results.results[0].sources, target : file)
|
||||||
|
eventBus.publish(downloadEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
void onFileDownloadedEvent(FileDownloadedEvent e) {
|
||||||
|
if (e.downloadedFile.infoHash != updateInfoHash)
|
||||||
|
return
|
||||||
|
updateDownloading = false
|
||||||
|
eventBus.publish(new UpdateDownloadedEvent(version : version, signer : signer))
|
||||||
|
}
|
||||||
|
|
||||||
private void checkUpdate() {
|
private void checkUpdate() {
|
||||||
final long now = System.currentTimeMillis()
|
final long now = System.currentTimeMillis()
|
||||||
if (lastUpdateCheckTime > 0) {
|
if (lastUpdateCheckTime > 0) {
|
||||||
@@ -106,8 +141,32 @@ class UpdateClient {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("new version $payload.version available, publishing event")
|
String infoHash
|
||||||
eventBus.publish(new UpdateAvailableEvent(version : payload.version, signer : payload.signer, infoHash : payload.infoHash))
|
if (settings.updateType == "jar") {
|
||||||
|
infoHash = payload.infoHash
|
||||||
|
} else
|
||||||
|
infoHash = payload[settings.updateType]
|
||||||
|
|
||||||
|
|
||||||
|
if (!settings.autoDownloadUpdate) {
|
||||||
|
log.info("new version $payload.version available, publishing event")
|
||||||
|
eventBus.publish(new UpdateAvailableEvent(version : payload.version, signer : payload.signer, infoHash : infoHash))
|
||||||
|
} else {
|
||||||
|
log.info("new version $payload.version available")
|
||||||
|
updateInfoHash = new InfoHash(Base64.decode(infoHash))
|
||||||
|
if (fileManager.rootToFiles.containsKey(updateInfoHash))
|
||||||
|
eventBus.publish(new UpdateDownloadedEvent(version : payload.version, signer : payload.signer))
|
||||||
|
else {
|
||||||
|
updateDownloading = false
|
||||||
|
version = payload.version
|
||||||
|
signer = payload.signer
|
||||||
|
log.info("starting search for new version hash $payload.infoHash")
|
||||||
|
def searchEvent = new SearchEvent(searchHash : updateInfoHash.getRoot(), uuid : UUID.randomUUID(), oobInfohash : true)
|
||||||
|
def queryEvent = new QueryEvent(searchEvent : searchEvent, firstHop : true, replyTo : me.destination,
|
||||||
|
receivedOn : me.destination, originator : me)
|
||||||
|
eventBus.publish(queryEvent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.log(Level.WARNING,"Invalid datagram",e)
|
log.log(Level.WARNING,"Invalid datagram",e)
|
||||||
|
@@ -0,0 +1,8 @@
|
|||||||
|
package com.muwire.core.update
|
||||||
|
|
||||||
|
import com.muwire.core.Event
|
||||||
|
|
||||||
|
class UpdateDownloadedEvent extends Event {
|
||||||
|
String version
|
||||||
|
String signer
|
||||||
|
}
|
@@ -2,5 +2,5 @@ package com.muwire.core.upload
|
|||||||
|
|
||||||
class ContentRequest extends Request {
|
class ContentRequest extends Request {
|
||||||
Range range
|
Range range
|
||||||
boolean have
|
int have
|
||||||
}
|
}
|
||||||
|
@@ -56,7 +56,7 @@ class ContentUploader extends Uploader {
|
|||||||
writeMesh(request.downloader)
|
writeMesh(request.downloader)
|
||||||
os.write("\r\n".getBytes(StandardCharsets.US_ASCII))
|
os.write("\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
|
|
||||||
FileChannel channel
|
FileChannel channel = null
|
||||||
try {
|
try {
|
||||||
channel = Files.newByteChannel(file.toPath(), EnumSet.of(StandardOpenOption.READ))
|
channel = Files.newByteChannel(file.toPath(), EnumSet.of(StandardOpenOption.READ))
|
||||||
mapped = channel.map(FileChannel.MapMode.READ_ONLY, range.start, range.end - range.start + 1)
|
mapped = channel.map(FileChannel.MapMode.READ_ONLY, range.start, range.end - range.start + 1)
|
||||||
@@ -72,6 +72,10 @@ class ContentUploader extends Uploader {
|
|||||||
} finally {
|
} finally {
|
||||||
try {channel?.close() } catch (IOException ignored) {}
|
try {channel?.close() } catch (IOException ignored) {}
|
||||||
endpoint.getOutputStream().flush()
|
endpoint.getOutputStream().flush()
|
||||||
|
synchronized(this) {
|
||||||
|
DataUtil.tryUnmap(mapped)
|
||||||
|
mapped = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,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))
|
||||||
@@ -105,4 +109,18 @@ class ContentUploader extends Uploader {
|
|||||||
request.downloader.getHumanReadableName()
|
request.downloader.getHumanReadableName()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDonePieces() {
|
||||||
|
return request.have;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTotalPieces() {
|
||||||
|
return mesh.pieces.nPieces;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTotalSize() {
|
||||||
|
return file.length();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -51,5 +51,18 @@ class HashListUploader extends Uploader {
|
|||||||
request.downloader.getHumanReadableName()
|
request.downloader.getHumanReadableName()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDonePieces() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTotalPieces() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getTotalSize() {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -50,10 +50,10 @@ class Request {
|
|||||||
downloader = new Persona(new ByteArrayInputStream(decoded))
|
downloader = new Persona(new ByteArrayInputStream(decoded))
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean have = false
|
int have = 0
|
||||||
if (headers.containsKey("X-Have")) {
|
if (headers.containsKey("X-Have")) {
|
||||||
def encoded = headers["X-Have"].trim()
|
def encoded = headers["X-Have"].trim()
|
||||||
have = DataUtil.decodeXHave(encoded).size() > 0
|
have = DataUtil.decodeXHave(encoded).size()
|
||||||
}
|
}
|
||||||
new ContentRequest( infoHash : infoHash, range : new Range(start, end),
|
new ContentRequest( infoHash : infoHash, range : new Range(start, end),
|
||||||
headers : headers, downloader : downloader, have : have)
|
headers : headers, downloader : downloader, have : have)
|
||||||
|
@@ -80,7 +80,7 @@ public class UploadManager {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.have)
|
if (request.have > 0)
|
||||||
eventBus.publish(new SourceDiscoveredEvent(infoHash : request.infoHash, source : request.downloader))
|
eventBus.publish(new SourceDiscoveredEvent(infoHash : request.infoHash, source : request.downloader))
|
||||||
|
|
||||||
Mesh mesh
|
Mesh mesh
|
||||||
@@ -205,7 +205,7 @@ public class UploadManager {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.have)
|
if (request.have > 0)
|
||||||
eventBus.publish(new SourceDiscoveredEvent(infoHash : request.infoHash, source : request.downloader))
|
eventBus.publish(new SourceDiscoveredEvent(infoHash : request.infoHash, source : request.downloader))
|
||||||
|
|
||||||
Mesh mesh
|
Mesh mesh
|
||||||
|
@@ -32,4 +32,10 @@ abstract class Uploader {
|
|||||||
abstract int getProgress();
|
abstract int getProgress();
|
||||||
|
|
||||||
abstract String getDownloader();
|
abstract String getDownloader();
|
||||||
|
|
||||||
|
abstract int getDonePieces();
|
||||||
|
|
||||||
|
abstract int getTotalPieces();
|
||||||
|
|
||||||
|
abstract long getTotalSize();
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
package com.muwire.core.util
|
package com.muwire.core.util
|
||||||
|
|
||||||
|
import java.lang.reflect.Field
|
||||||
|
import java.lang.reflect.Method
|
||||||
|
import java.nio.ByteBuffer
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
|
|
||||||
import com.muwire.core.Constants
|
import com.muwire.core.Constants
|
||||||
@@ -8,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)
|
||||||
@@ -109,4 +112,45 @@ class DataUtil {
|
|||||||
}
|
}
|
||||||
available
|
available
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Exception findRoot(Exception e) {
|
||||||
|
while(e.getCause() != null)
|
||||||
|
e = e.getCause()
|
||||||
|
e
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void tryUnmap(ByteBuffer cb) {
|
||||||
|
if (cb==null || !cb.isDirect()) return;
|
||||||
|
// we could use this type cast and call functions without reflection code,
|
||||||
|
// but static import from sun.* package is risky for non-SUN virtual machine.
|
||||||
|
//try { ((sun.nio.ch.DirectBuffer)cb).cleaner().clean(); } catch (Exception ex) { }
|
||||||
|
|
||||||
|
// JavaSpecVer: 1.6, 1.7, 1.8, 9, 10
|
||||||
|
boolean isOldJDK = System.getProperty("java.specification.version","99").startsWith("1.");
|
||||||
|
try {
|
||||||
|
if (isOldJDK) {
|
||||||
|
Method cleaner = cb.getClass().getMethod("cleaner");
|
||||||
|
cleaner.setAccessible(true);
|
||||||
|
Method clean = Class.forName("sun.misc.Cleaner").getMethod("clean");
|
||||||
|
clean.setAccessible(true);
|
||||||
|
clean.invoke(cleaner.invoke(cb));
|
||||||
|
} else {
|
||||||
|
Class unsafeClass;
|
||||||
|
try {
|
||||||
|
unsafeClass = Class.forName("sun.misc.Unsafe");
|
||||||
|
} catch(Exception ex) {
|
||||||
|
// jdk.internal.misc.Unsafe doesn't yet have an invokeCleaner() method,
|
||||||
|
// but that method should be added if sun.misc.Unsafe is removed.
|
||||||
|
unsafeClass = Class.forName("jdk.internal.misc.Unsafe");
|
||||||
|
}
|
||||||
|
Method clean = unsafeClass.getMethod("invokeCleaner", ByteBuffer.class);
|
||||||
|
clean.setAccessible(true);
|
||||||
|
Field theUnsafeField = unsafeClass.getDeclaredField("theUnsafe");
|
||||||
|
theUnsafeField.setAccessible(true);
|
||||||
|
Object theUnsafe = theUnsafeField.get(null);
|
||||||
|
clean.invoke(theUnsafe, cb);
|
||||||
|
}
|
||||||
|
} catch(Exception ex) { }
|
||||||
|
cb = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,21 +1,23 @@
|
|||||||
package com.muwire.core;
|
package com.muwire.core;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import net.i2p.data.Destination;
|
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)
|
||||||
super(file, infoHash, pieceSize);
|
throws IOException {
|
||||||
this.sources = sources;
|
super(file, infoHash, pieceSize);
|
||||||
}
|
this.sources = sources;
|
||||||
|
}
|
||||||
|
|
||||||
public Set<Destination> getSources() {
|
public Set<Destination> getSources() {
|
||||||
return sources;
|
return sources;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -11,83 +11,83 @@ import net.i2p.data.Base64;
|
|||||||
|
|
||||||
public class InfoHash {
|
public class InfoHash {
|
||||||
|
|
||||||
public static final int SIZE = 0x1 << 5;
|
public static final int SIZE = 0x1 << 5;
|
||||||
|
|
||||||
private final byte[] root;
|
private final byte[] root;
|
||||||
private final byte[] hashList;
|
private final byte[] hashList;
|
||||||
|
|
||||||
private final int hashCode;
|
private final int hashCode;
|
||||||
|
|
||||||
public InfoHash(byte[] root, byte[] hashList) {
|
public InfoHash(byte[] root, byte[] hashList) {
|
||||||
if (root.length != SIZE)
|
if (root.length != SIZE)
|
||||||
throw new IllegalArgumentException("invalid root size "+root.length);
|
throw new IllegalArgumentException("invalid root size "+root.length);
|
||||||
if (hashList != null && hashList.length % SIZE != 0)
|
if (hashList != null && hashList.length % SIZE != 0)
|
||||||
throw new IllegalArgumentException("invalid hashList size " + hashList.length);
|
throw new IllegalArgumentException("invalid hashList size " + hashList.length);
|
||||||
this.root = root;
|
this.root = root;
|
||||||
this.hashList = hashList;
|
this.hashList = hashList;
|
||||||
hashCode = root[0] << 24 |
|
hashCode = root[0] << 24 |
|
||||||
root[1] << 16 |
|
root[1] << 16 |
|
||||||
root[2] << 8 |
|
root[2] << 8 |
|
||||||
root[3];
|
root[3];
|
||||||
}
|
}
|
||||||
|
|
||||||
public InfoHash(byte[] root) {
|
public InfoHash(byte[] root) {
|
||||||
this(root, null);
|
this(root, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public InfoHash(String base32) {
|
public InfoHash(String base32) {
|
||||||
this(Base32.decode(base32));
|
this(Base32.decode(base32));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static InfoHash fromHashList(byte []hashList) {
|
public static InfoHash fromHashList(byte []hashList) {
|
||||||
try {
|
try {
|
||||||
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
|
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
|
||||||
byte[] root = sha256.digest(hashList);
|
byte[] root = sha256.digest(hashList);
|
||||||
return new InfoHash(root, hashList);
|
return new InfoHash(root, hashList);
|
||||||
} catch (NoSuchAlgorithmException impossible) {
|
} catch (NoSuchAlgorithmException impossible) {
|
||||||
impossible.printStackTrace();
|
impossible.printStackTrace();
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getRoot() {
|
public byte[] getRoot() {
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getHashList() {
|
public byte[] getHashList() {
|
||||||
return hashList;
|
return hashList;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return hashCode;
|
return hashCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) {
|
if (this == o) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!(o instanceof InfoHash)) {
|
if (!(o instanceof InfoHash)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
InfoHash other = (InfoHash) o;
|
InfoHash other = (InfoHash) o;
|
||||||
return Arrays.equals(root, other.root);
|
return Arrays.equals(root, other.root);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
String rv = "InfoHash[root:"+Base64.encode(root) + " hashList:";
|
String rv = "InfoHash[root:"+Base64.encode(root) + " hashList:";
|
||||||
List<String> b64HashList = new ArrayList<>();
|
List<String> b64HashList = new ArrayList<>();
|
||||||
if (hashList != null) {
|
if (hashList != null) {
|
||||||
byte [] tmp = new byte[SIZE];
|
byte [] tmp = new byte[SIZE];
|
||||||
for (int i = 0; i < hashList.length / SIZE; i++) {
|
for (int i = 0; i < hashList.length / SIZE; i++) {
|
||||||
System.arraycopy(hashList, SIZE * i, tmp, 0, SIZE);
|
System.arraycopy(hashList, SIZE * i, tmp, 0, SIZE);
|
||||||
b64HashList.add(Base64.encode(tmp));
|
b64HashList.add(Base64.encode(tmp));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rv += b64HashList.toString();
|
rv += b64HashList.toString();
|
||||||
rv += "]";
|
rv += "]";
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,50 +1,64 @@
|
|||||||
package com.muwire.core;
|
package com.muwire.core;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
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;
|
||||||
|
|
||||||
public SharedFile(File file, InfoHash infoHash, int pieceSize) {
|
private final String cachedPath;
|
||||||
this.file = file;
|
private final long cachedLength;
|
||||||
this.infoHash = infoHash;
|
|
||||||
this.pieceSize = pieceSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
public File getFile() {
|
public SharedFile(File file, InfoHash infoHash, int pieceSize) throws IOException {
|
||||||
return file;
|
this.file = file;
|
||||||
}
|
this.infoHash = infoHash;
|
||||||
|
this.pieceSize = pieceSize;
|
||||||
|
this.cachedPath = file.getAbsolutePath();
|
||||||
|
this.cachedLength = file.length();
|
||||||
|
}
|
||||||
|
|
||||||
public InfoHash getInfoHash() {
|
public File getFile() {
|
||||||
return infoHash;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getPieceSize() {
|
public InfoHash getInfoHash() {
|
||||||
return pieceSize;
|
return infoHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getNPieces() {
|
public int getPieceSize() {
|
||||||
long length = file.length();
|
return pieceSize;
|
||||||
int rawPieceSize = 0x1 << pieceSize;
|
}
|
||||||
int rv = (int) (length / rawPieceSize);
|
|
||||||
if (length % pieceSize != 0)
|
|
||||||
rv++;
|
|
||||||
return rv;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
public int getNPieces() {
|
||||||
public int hashCode() {
|
long length = file.length();
|
||||||
return file.hashCode() ^ infoHash.hashCode();
|
int rawPieceSize = 0x1 << pieceSize;
|
||||||
}
|
int rv = (int) (length / rawPieceSize);
|
||||||
|
if (length % rawPieceSize != 0)
|
||||||
|
rv++;
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
public String getCachedPath() {
|
||||||
public boolean equals(Object o) {
|
return cachedPath;
|
||||||
if (!(o instanceof SharedFile))
|
}
|
||||||
return false;
|
|
||||||
SharedFile other = (SharedFile)o;
|
public long getCachedLength() {
|
||||||
return file.equals(other.file) && infoHash.equals(other.infoHash);
|
return cachedLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return file.hashCode() ^ infoHash.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (!(o instanceof SharedFile))
|
||||||
|
return false;
|
||||||
|
SharedFile other = (SharedFile)o;
|
||||||
|
return file.equals(other.file) && infoHash.equals(other.infoHash);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
package com.muwire.core.connection;
|
package com.muwire.core.connection;
|
||||||
|
|
||||||
public enum ConnectionAttemptStatus {
|
public enum ConnectionAttemptStatus {
|
||||||
SUCCESSFUL, REJECTED, FAILED
|
SUCCESSFUL, REJECTED, FAILED
|
||||||
}
|
}
|
||||||
|
@@ -6,5 +6,5 @@ package com.muwire.core.hostcache;
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public enum CrawlerResponse {
|
public enum CrawlerResponse {
|
||||||
ALL, REGISTERED, NONE
|
ALL, REGISTERED, NONE
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
package com.muwire.core.trust;
|
package com.muwire.core.trust;
|
||||||
|
|
||||||
public enum TrustLevel {
|
public enum TrustLevel {
|
||||||
TRUSTED, NEUTRAL, DISTRUSTED
|
TRUSTED, NEUTRAL, DISTRUSTED
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,6 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
class Destinations {
|
class Destinations {
|
||||||
|
|
||||||
Destination dest1 = new Destination("KvwWPKMSAtzf7Yruj8TQaHi2jaQpSNsXJskbpmSBTxkcYlDB2GllH~QBu-cs4FSYdaRmKDUUx7793jjnYJgTMbrjqeIL5-BTORZ09n6PUfhSejDpJjdkUxaV1OHRatfYs70RNBv7rvdj1-nXUow5tMfOJtoWVocUoKefUGFQFbJLDDkBqjm1kFyKFZv6m6S6YqXxBgVB1qYicooy67cNQF5HLUFtP15pk5fMDNGz5eNCjPfC~2Gp8FF~OpSy92HT0XN7uAMJykPcbdnWfcvVwqD7eS0K4XEnsqnMPLEiMAhqsugEFiFqtB3Wmm7UHVc03lcAfRhr1e2uZBNFTtM2Uol4MD5sCCKRZVHGcH-WGPSEz0BM5YO~Xi~dQ~N3NVud32PVzhh8xoGcAlhTqMqAbRJndCv-H6NflX90pYmbirCTIDOaR9758mThrqX0d4CwCn4jFXer52l8Qe8CErGoLuB-4LL~Gwrn7R1k7ZQc2PthkqeW8MfigyiN7hZVkul9AAAA")
|
Destination dest1 = new Destination("KvwWPKMSAtzf7Yruj8TQaHi2jaQpSNsXJskbpmSBTxkcYlDB2GllH~QBu-cs4FSYdaRmKDUUx7793jjnYJgTMbrjqeIL5-BTORZ09n6PUfhSejDpJjdkUxaV1OHRatfYs70RNBv7rvdj1-nXUow5tMfOJtoWVocUoKefUGFQFbJLDDkBqjm1kFyKFZv6m6S6YqXxBgVB1qYicooy67cNQF5HLUFtP15pk5fMDNGz5eNCjPfC~2Gp8FF~OpSy92HT0XN7uAMJykPcbdnWfcvVwqD7eS0K4XEnsqnMPLEiMAhqsugEFiFqtB3Wmm7UHVc03lcAfRhr1e2uZBNFTtM2Uol4MD5sCCKRZVHGcH-WGPSEz0BM5YO~Xi~dQ~N3NVud32PVzhh8xoGcAlhTqMqAbRJndCv-H6NflX90pYmbirCTIDOaR9758mThrqX0d4CwCn4jFXer52l8Qe8CErGoLuB-4LL~Gwrn7R1k7ZQc2PthkqeW8MfigyiN7hZVkul9AAAA")
|
||||||
Destination dest2 = new Destination("KvwWPKMSAtzf7Yruj8TQaHi2jaQpSNsXJskbpmSBTxkcYlDB2GllH~QBu-cs4FSYdaRmKDUUx7793jjnYJgTMbrjqeIL5-BTORZ09n6PUfhSejDpJjdkUxaV1OHRatfYs70RNBv7rvdj1-nXUow5tMfOJtoWVocUoKefUGFQFbJLDDkBqjm1kFyKFZv6m6S6YqXxBgVB1qYicooy67cNQF5HLUFtP15pk5fMDNGz5eNCjPfC~2Gp8FF~OpSy92HT0XN7uAMJykPcbdnWfcvVwqD7eS0K4XEnsqnMPLEiMAhqsugEFiFqtB3Wmm7UHVc03lcAfRhr1e2uZBNFTtM2Uol4MD5sCCKRZVHGcH-WGPSEz0BM5YO~Xi~dQ~N3NVud32PVzhh8xoGcAlhTqMqAbRJndCv-H6NflX90pYmbirCTIDOaR9758mThrqX0d4CwCn4jFXer52l8Qe8CErGoLuB-4LL~Gwrn7R1k7ZQc2PthkqeW8MfigyiN7hZVkul8AAAA")
|
Destination dest2 = new Destination("KvwWPKMSAtzf7Yruj8TQaHi2jaQpSNsXJskbpmSBTxkcYlDB2GllH~QBu-cs4FSYdaRmKDUUx7793jjnYJgTMbrjqeIL5-BTORZ09n6PUfhSejDpJjdkUxaV1OHRatfYs70RNBv7rvdj1-nXUow5tMfOJtoWVocUoKefUGFQFbJLDDkBqjm1kFyKFZv6m6S6YqXxBgVB1qYicooy67cNQF5HLUFtP15pk5fMDNGz5eNCjPfC~2Gp8FF~OpSy92HT0XN7uAMJykPcbdnWfcvVwqD7eS0K4XEnsqnMPLEiMAhqsugEFiFqtB3Wmm7UHVc03lcAfRhr1e2uZBNFTtM2Uol4MD5sCCKRZVHGcH-WGPSEz0BM5YO~Xi~dQ~N3NVud32PVzhh8xoGcAlhTqMqAbRJndCv-H6NflX90pYmbirCTIDOaR9758mThrqX0d4CwCn4jFXer52l8Qe8CErGoLuB-4LL~Gwrn7R1k7ZQc2PthkqeW8MfigyiN7hZVkul8AAAA")
|
||||||
}
|
}
|
||||||
|
@@ -4,23 +4,23 @@ import org.junit.Test
|
|||||||
|
|
||||||
class EventBusTest {
|
class EventBusTest {
|
||||||
|
|
||||||
class FakeEvent extends Event {}
|
class FakeEvent extends Event {}
|
||||||
|
|
||||||
class FakeEventHandler {
|
class FakeEventHandler {
|
||||||
def onFakeEvent(FakeEvent e) {
|
def onFakeEvent(FakeEvent e) {
|
||||||
assert e == fakeEvent
|
assert e == fakeEvent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FakeEvent fakeEvent = new FakeEvent()
|
FakeEvent fakeEvent = new FakeEvent()
|
||||||
|
|
||||||
EventBus bus = new EventBus()
|
EventBus bus = new EventBus()
|
||||||
def handler = new FakeEventHandler()
|
def handler = new FakeEventHandler()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testDynamicEvent() {
|
void testDynamicEvent() {
|
||||||
bus.register(FakeEvent.class, handler)
|
bus.register(FakeEvent.class, handler)
|
||||||
bus.publish(fakeEvent)
|
bus.publish(fakeEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -6,11 +6,11 @@ import org.junit.Test
|
|||||||
|
|
||||||
class InfoHashTest {
|
class InfoHashTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testEmpty() {
|
void testEmpty() {
|
||||||
byte [] empty = new byte[0x1 << 6];
|
byte [] empty = new byte[0x1 << 6];
|
||||||
def ih = InfoHash.fromHashList(empty)
|
def ih = InfoHash.fromHashList(empty)
|
||||||
def ih2 = new InfoHash("6ws72qwrniqdaj4y55xngcmxtnbqapjdedm7b2hktay2sj2z7nfq");
|
def ih2 = new InfoHash("6ws72qwrniqdaj4y55xngcmxtnbqapjdedm7b2hktay2sj2z7nfq");
|
||||||
assertEquals(ih, ih2);
|
assertEquals(ih, ih2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -22,21 +22,21 @@ import groovy.mock.interceptor.MockFor
|
|||||||
|
|
||||||
class ConnectionAcceptorTest {
|
class ConnectionAcceptorTest {
|
||||||
|
|
||||||
EventBus eventBus
|
EventBus eventBus
|
||||||
final Destinations destinations = new Destinations()
|
final Destinations destinations = new Destinations()
|
||||||
def settings
|
def settings
|
||||||
|
|
||||||
def connectionManagerMock
|
def connectionManagerMock
|
||||||
UltrapeerConnectionManager connectionManager
|
UltrapeerConnectionManager connectionManager
|
||||||
|
|
||||||
def i2pAcceptorMock
|
def i2pAcceptorMock
|
||||||
I2PAcceptor i2pAcceptor
|
I2PAcceptor i2pAcceptor
|
||||||
|
|
||||||
def hostCacheMock
|
def hostCacheMock
|
||||||
HostCache hostCache
|
HostCache hostCache
|
||||||
|
|
||||||
def trustServiceMock
|
def trustServiceMock
|
||||||
TrustService trustService
|
TrustService trustService
|
||||||
|
|
||||||
def searchManagerMock
|
def searchManagerMock
|
||||||
SearchManager searchManager
|
SearchManager searchManager
|
||||||
@@ -47,361 +47,361 @@ class ConnectionAcceptorTest {
|
|||||||
def connectionEstablisherMock
|
def connectionEstablisherMock
|
||||||
ConnectionEstablisher connectionEstablisher
|
ConnectionEstablisher connectionEstablisher
|
||||||
|
|
||||||
ConnectionAcceptor acceptor
|
ConnectionAcceptor acceptor
|
||||||
List<ConnectionEvent> connectionEvents
|
List<ConnectionEvent> connectionEvents
|
||||||
InputStream inputStream
|
InputStream inputStream
|
||||||
OutputStream outputStream
|
OutputStream outputStream
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
void before() {
|
void before() {
|
||||||
connectionManagerMock = new MockFor(UltrapeerConnectionManager.class)
|
connectionManagerMock = new MockFor(UltrapeerConnectionManager.class)
|
||||||
i2pAcceptorMock = new MockFor(I2PAcceptor.class)
|
i2pAcceptorMock = new MockFor(I2PAcceptor.class)
|
||||||
hostCacheMock = new MockFor(HostCache.class)
|
hostCacheMock = new MockFor(HostCache.class)
|
||||||
trustServiceMock = new MockFor(TrustService.class)
|
trustServiceMock = new MockFor(TrustService.class)
|
||||||
searchManagerMock = new MockFor(SearchManager.class)
|
searchManagerMock = new MockFor(SearchManager.class)
|
||||||
uploadManagerMock = new MockFor(UploadManager.class)
|
uploadManagerMock = new MockFor(UploadManager.class)
|
||||||
connectionEstablisherMock = new MockFor(ConnectionEstablisher.class)
|
connectionEstablisherMock = new MockFor(ConnectionEstablisher.class)
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
void after() {
|
void after() {
|
||||||
acceptor?.stop()
|
acceptor?.stop()
|
||||||
connectionManagerMock.verify connectionManager
|
connectionManagerMock.verify connectionManager
|
||||||
i2pAcceptorMock.verify i2pAcceptor
|
i2pAcceptorMock.verify i2pAcceptor
|
||||||
hostCacheMock.verify hostCache
|
hostCacheMock.verify hostCache
|
||||||
trustServiceMock.verify trustService
|
trustServiceMock.verify trustService
|
||||||
searchManagerMock.verify searchManager
|
searchManagerMock.verify searchManager
|
||||||
uploadManagerMock.verify uploadManager
|
uploadManagerMock.verify uploadManager
|
||||||
connectionEstablisherMock.verify connectionEstablisher
|
connectionEstablisherMock.verify connectionEstablisher
|
||||||
Thread.sleep(100)
|
Thread.sleep(100)
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initMocks() {
|
private void initMocks() {
|
||||||
connectionEvents = new CopyOnWriteArrayList()
|
connectionEvents = new CopyOnWriteArrayList()
|
||||||
eventBus = new EventBus()
|
eventBus = new EventBus()
|
||||||
def listener = new Object() {
|
def listener = new Object() {
|
||||||
void onConnectionEvent(ConnectionEvent e) {
|
void onConnectionEvent(ConnectionEvent e) {
|
||||||
connectionEvents.add e
|
connectionEvents.add e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
eventBus.register(ConnectionEvent.class, listener)
|
eventBus.register(ConnectionEvent.class, listener)
|
||||||
|
|
||||||
connectionManager = connectionManagerMock.proxyInstance()
|
connectionManager = connectionManagerMock.proxyInstance()
|
||||||
i2pAcceptor = i2pAcceptorMock.proxyInstance()
|
i2pAcceptor = i2pAcceptorMock.proxyInstance()
|
||||||
hostCache = hostCacheMock.proxyInstance()
|
hostCache = hostCacheMock.proxyInstance()
|
||||||
trustService = trustServiceMock.proxyInstance()
|
trustService = trustServiceMock.proxyInstance()
|
||||||
searchManager = searchManagerMock.proxyInstance()
|
searchManager = searchManagerMock.proxyInstance()
|
||||||
uploadManager = uploadManagerMock.proxyInstance()
|
uploadManager = uploadManagerMock.proxyInstance()
|
||||||
connectionEstablisher = connectionEstablisherMock.proxyInstance()
|
connectionEstablisher = connectionEstablisherMock.proxyInstance()
|
||||||
|
|
||||||
acceptor = new ConnectionAcceptor(eventBus, connectionManager, settings, i2pAcceptor,
|
acceptor = new ConnectionAcceptor(eventBus, connectionManager, settings, i2pAcceptor,
|
||||||
hostCache, trustService, searchManager, uploadManager, connectionEstablisher)
|
hostCache, trustService, searchManager, uploadManager, connectionEstablisher)
|
||||||
acceptor.start()
|
acceptor.start()
|
||||||
Thread.sleep(100)
|
Thread.sleep(100)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSuccessfulLeaf() {
|
void testSuccessfulLeaf() {
|
||||||
settings = new MuWireSettings() {
|
settings = new MuWireSettings() {
|
||||||
boolean isLeaf() {
|
boolean isLeaf() {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept {
|
i2pAcceptorMock.demand.accept {
|
||||||
def is = new PipedInputStream()
|
def is = new PipedInputStream()
|
||||||
outputStream = new PipedOutputStream(is)
|
outputStream = new PipedOutputStream(is)
|
||||||
def os = new PipedOutputStream()
|
def os = new PipedOutputStream()
|
||||||
inputStream = new PipedInputStream(os)
|
inputStream = new PipedInputStream(os)
|
||||||
new Endpoint(destinations.dest1, is, os, null)
|
new Endpoint(destinations.dest1, is, os, null)
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||||
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
||||||
connectionManagerMock.demand.isConnected { dest ->
|
connectionManagerMock.demand.isConnected { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
connectionManagerMock.demand.hasLeafSlots() { true }
|
connectionManagerMock.demand.hasLeafSlots() { true }
|
||||||
trustServiceMock.demand.getLevel { dest ->
|
trustServiceMock.demand.getLevel { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
outputStream.write("MuWire leaf".bytes)
|
outputStream.write("MuWire leaf".bytes)
|
||||||
byte [] OK = new byte[2]
|
byte [] OK = new byte[2]
|
||||||
def dis = new DataInputStream(inputStream)
|
def dis = new DataInputStream(inputStream)
|
||||||
dis.readFully(OK)
|
dis.readFully(OK)
|
||||||
assert OK == "OK".bytes
|
assert OK == "OK".bytes
|
||||||
|
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
assert connectionEvents.size() == 1
|
assert connectionEvents.size() == 1
|
||||||
def event = connectionEvents[0]
|
def event = connectionEvents[0]
|
||||||
assert event.endpoint.destination == destinations.dest1
|
assert event.endpoint.destination == destinations.dest1
|
||||||
assert event.status == ConnectionAttemptStatus.SUCCESSFUL
|
assert event.status == ConnectionAttemptStatus.SUCCESSFUL
|
||||||
assert event.incoming == true
|
assert event.incoming == true
|
||||||
assert event.leaf == true
|
assert event.leaf == true
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSuccessfulPeer() {
|
void testSuccessfulPeer() {
|
||||||
settings = new MuWireSettings() {
|
settings = new MuWireSettings() {
|
||||||
boolean isLeaf() {
|
boolean isLeaf() {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept {
|
i2pAcceptorMock.demand.accept {
|
||||||
def is = new PipedInputStream()
|
def is = new PipedInputStream()
|
||||||
outputStream = new PipedOutputStream(is)
|
outputStream = new PipedOutputStream(is)
|
||||||
def os = new PipedOutputStream()
|
def os = new PipedOutputStream()
|
||||||
inputStream = new PipedInputStream(os)
|
inputStream = new PipedInputStream(os)
|
||||||
new Endpoint(destinations.dest1, is, os, null)
|
new Endpoint(destinations.dest1, is, os, null)
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||||
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
||||||
connectionManagerMock.demand.isConnected { dest ->
|
connectionManagerMock.demand.isConnected { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
connectionManagerMock.demand.hasPeerSlots() { true }
|
connectionManagerMock.demand.hasPeerSlots() { true }
|
||||||
trustServiceMock.demand.getLevel { dest ->
|
trustServiceMock.demand.getLevel { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
outputStream.write("MuWire peer".bytes)
|
outputStream.write("MuWire peer".bytes)
|
||||||
byte [] OK = new byte[2]
|
byte [] OK = new byte[2]
|
||||||
def dis = new DataInputStream(inputStream)
|
def dis = new DataInputStream(inputStream)
|
||||||
dis.readFully(OK)
|
dis.readFully(OK)
|
||||||
assert OK == "OK".bytes
|
assert OK == "OK".bytes
|
||||||
|
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
assert connectionEvents.size() == 1
|
assert connectionEvents.size() == 1
|
||||||
def event = connectionEvents[0]
|
def event = connectionEvents[0]
|
||||||
assert event.endpoint.destination == destinations.dest1
|
assert event.endpoint.destination == destinations.dest1
|
||||||
assert event.status == ConnectionAttemptStatus.SUCCESSFUL
|
assert event.status == ConnectionAttemptStatus.SUCCESSFUL
|
||||||
assert event.incoming == true
|
assert event.incoming == true
|
||||||
assert event.leaf == false
|
assert event.leaf == false
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testLeafRejectsLeaf() {
|
void testLeafRejectsLeaf() {
|
||||||
settings = new MuWireSettings() {
|
settings = new MuWireSettings() {
|
||||||
boolean isLeaf() {
|
boolean isLeaf() {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept {
|
i2pAcceptorMock.demand.accept {
|
||||||
def is = new PipedInputStream()
|
def is = new PipedInputStream()
|
||||||
outputStream = new PipedOutputStream(is)
|
outputStream = new PipedOutputStream(is)
|
||||||
def os = new PipedOutputStream()
|
def os = new PipedOutputStream()
|
||||||
inputStream = new PipedInputStream(os)
|
inputStream = new PipedInputStream(os)
|
||||||
new Endpoint(destinations.dest1, is, os, null)
|
new Endpoint(destinations.dest1, is, os, null)
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||||
trustServiceMock.demand.getLevel { dest ->
|
trustServiceMock.demand.getLevel { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
outputStream.write("MuWire leaf".bytes)
|
outputStream.write("MuWire leaf".bytes)
|
||||||
outputStream.flush()
|
outputStream.flush()
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
assert inputStream.read() == -1
|
assert inputStream.read() == -1
|
||||||
|
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
assert connectionEvents.size() == 1
|
assert connectionEvents.size() == 1
|
||||||
def event = connectionEvents[0]
|
def event = connectionEvents[0]
|
||||||
assert event.endpoint.destination == destinations.dest1
|
assert event.endpoint.destination == destinations.dest1
|
||||||
assert event.status == ConnectionAttemptStatus.FAILED
|
assert event.status == ConnectionAttemptStatus.FAILED
|
||||||
assert event.incoming == true
|
assert event.incoming == true
|
||||||
assert event.leaf == null
|
assert event.leaf == null
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testLeafRejectsPeer() {
|
void testLeafRejectsPeer() {
|
||||||
settings = new MuWireSettings() {
|
settings = new MuWireSettings() {
|
||||||
boolean isLeaf() {
|
boolean isLeaf() {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept {
|
i2pAcceptorMock.demand.accept {
|
||||||
def is = new PipedInputStream()
|
def is = new PipedInputStream()
|
||||||
outputStream = new PipedOutputStream(is)
|
outputStream = new PipedOutputStream(is)
|
||||||
def os = new PipedOutputStream()
|
def os = new PipedOutputStream()
|
||||||
inputStream = new PipedInputStream(os)
|
inputStream = new PipedInputStream(os)
|
||||||
new Endpoint(destinations.dest1, is, os, null)
|
new Endpoint(destinations.dest1, is, os, null)
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||||
trustServiceMock.demand.getLevel { dest ->
|
trustServiceMock.demand.getLevel { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
outputStream.write("MuWire peer".bytes)
|
outputStream.write("MuWire peer".bytes)
|
||||||
outputStream.flush()
|
outputStream.flush()
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
assert inputStream.read() == -1
|
assert inputStream.read() == -1
|
||||||
|
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
assert connectionEvents.size() == 1
|
assert connectionEvents.size() == 1
|
||||||
def event = connectionEvents[0]
|
def event = connectionEvents[0]
|
||||||
assert event.endpoint.destination == destinations.dest1
|
assert event.endpoint.destination == destinations.dest1
|
||||||
assert event.status == ConnectionAttemptStatus.FAILED
|
assert event.status == ConnectionAttemptStatus.FAILED
|
||||||
assert event.incoming == true
|
assert event.incoming == true
|
||||||
assert event.leaf == null
|
assert event.leaf == null
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testPeerRejectsPeerSlots() {
|
void testPeerRejectsPeerSlots() {
|
||||||
settings = new MuWireSettings() {
|
settings = new MuWireSettings() {
|
||||||
boolean isLeaf() {
|
boolean isLeaf() {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept {
|
i2pAcceptorMock.demand.accept {
|
||||||
def is = new PipedInputStream()
|
def is = new PipedInputStream()
|
||||||
outputStream = new PipedOutputStream(is)
|
outputStream = new PipedOutputStream(is)
|
||||||
def os = new PipedOutputStream()
|
def os = new PipedOutputStream()
|
||||||
inputStream = new PipedInputStream(os)
|
inputStream = new PipedInputStream(os)
|
||||||
new Endpoint(destinations.dest1, is, os, null)
|
new Endpoint(destinations.dest1, is, os, null)
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||||
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
||||||
connectionManagerMock.demand.isConnected { dest ->
|
connectionManagerMock.demand.isConnected { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
connectionManagerMock.demand.hasPeerSlots() { false }
|
connectionManagerMock.demand.hasPeerSlots() { false }
|
||||||
trustServiceMock.demand.getLevel { dest ->
|
trustServiceMock.demand.getLevel { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
hostCacheMock.ignore.getGoodHosts { n -> [] }
|
hostCacheMock.ignore.getGoodHosts { n -> [] }
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
outputStream.write("MuWire peer".bytes)
|
outputStream.write("MuWire peer".bytes)
|
||||||
byte [] OK = new byte[6]
|
byte [] OK = new byte[6]
|
||||||
def dis = new DataInputStream(inputStream)
|
def dis = new DataInputStream(inputStream)
|
||||||
dis.readFully(OK)
|
dis.readFully(OK)
|
||||||
assert OK == "REJECT".bytes
|
assert OK == "REJECT".bytes
|
||||||
|
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
assert dis.read() == -1
|
assert dis.read() == -1
|
||||||
|
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
assert connectionEvents.size() == 1
|
assert connectionEvents.size() == 1
|
||||||
def event = connectionEvents[0]
|
def event = connectionEvents[0]
|
||||||
assert event.endpoint.destination == destinations.dest1
|
assert event.endpoint.destination == destinations.dest1
|
||||||
assert event.status == ConnectionAttemptStatus.REJECTED
|
assert event.status == ConnectionAttemptStatus.REJECTED
|
||||||
assert event.incoming == true
|
assert event.incoming == true
|
||||||
assert event.leaf == false
|
assert event.leaf == false
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testPeerRejectsLeafSlots() {
|
void testPeerRejectsLeafSlots() {
|
||||||
settings = new MuWireSettings() {
|
settings = new MuWireSettings() {
|
||||||
boolean isLeaf() {
|
boolean isLeaf() {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept {
|
i2pAcceptorMock.demand.accept {
|
||||||
def is = new PipedInputStream()
|
def is = new PipedInputStream()
|
||||||
outputStream = new PipedOutputStream(is)
|
outputStream = new PipedOutputStream(is)
|
||||||
def os = new PipedOutputStream()
|
def os = new PipedOutputStream()
|
||||||
inputStream = new PipedInputStream(os)
|
inputStream = new PipedInputStream(os)
|
||||||
new Endpoint(destinations.dest1, is, os, null)
|
new Endpoint(destinations.dest1, is, os, null)
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||||
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
||||||
connectionManagerMock.demand.isConnected { dest ->
|
connectionManagerMock.demand.isConnected { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
connectionManagerMock.demand.hasLeafSlots() { false }
|
connectionManagerMock.demand.hasLeafSlots() { false }
|
||||||
trustServiceMock.demand.getLevel { dest ->
|
trustServiceMock.demand.getLevel { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
hostCacheMock.ignore.getGoodHosts { n -> [] }
|
hostCacheMock.ignore.getGoodHosts { n -> [] }
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
outputStream.write("MuWire leaf".bytes)
|
outputStream.write("MuWire leaf".bytes)
|
||||||
byte [] OK = new byte[6]
|
byte [] OK = new byte[6]
|
||||||
def dis = new DataInputStream(inputStream)
|
def dis = new DataInputStream(inputStream)
|
||||||
dis.readFully(OK)
|
dis.readFully(OK)
|
||||||
assert OK == "REJECT".bytes
|
assert OK == "REJECT".bytes
|
||||||
|
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
assert dis.read() == -1
|
assert dis.read() == -1
|
||||||
|
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
assert connectionEvents.size() == 1
|
assert connectionEvents.size() == 1
|
||||||
def event = connectionEvents[0]
|
def event = connectionEvents[0]
|
||||||
assert event.endpoint.destination == destinations.dest1
|
assert event.endpoint.destination == destinations.dest1
|
||||||
assert event.status == ConnectionAttemptStatus.REJECTED
|
assert event.status == ConnectionAttemptStatus.REJECTED
|
||||||
assert event.incoming == true
|
assert event.incoming == true
|
||||||
assert event.leaf == true
|
assert event.leaf == true
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testPeerRejectsPeerSuggests() {
|
void testPeerRejectsPeerSuggests() {
|
||||||
settings = new MuWireSettings() {
|
settings = new MuWireSettings() {
|
||||||
boolean isLeaf() {
|
boolean isLeaf() {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept {
|
i2pAcceptorMock.demand.accept {
|
||||||
def is = new PipedInputStream()
|
def is = new PipedInputStream()
|
||||||
outputStream = new PipedOutputStream(is)
|
outputStream = new PipedOutputStream(is)
|
||||||
def os = new PipedOutputStream()
|
def os = new PipedOutputStream()
|
||||||
inputStream = new PipedInputStream(os)
|
inputStream = new PipedInputStream(os)
|
||||||
new Endpoint(destinations.dest1, is, os, null)
|
new Endpoint(destinations.dest1, is, os, null)
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||||
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
||||||
connectionManagerMock.demand.isConnected { dest ->
|
connectionManagerMock.demand.isConnected { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
connectionManagerMock.demand.hasPeerSlots() { false }
|
connectionManagerMock.demand.hasPeerSlots() { false }
|
||||||
trustServiceMock.demand.getLevel { dest ->
|
trustServiceMock.demand.getLevel { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
hostCacheMock.ignore.getGoodHosts { n -> [destinations.dest2] }
|
hostCacheMock.ignore.getGoodHosts { n -> [destinations.dest2] }
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
outputStream.write("MuWire peer".bytes)
|
outputStream.write("MuWire peer".bytes)
|
||||||
byte [] OK = new byte[6]
|
byte [] OK = new byte[6]
|
||||||
def dis = new DataInputStream(inputStream)
|
def dis = new DataInputStream(inputStream)
|
||||||
dis.readFully(OK)
|
dis.readFully(OK)
|
||||||
assert OK == "REJECT".bytes
|
assert OK == "REJECT".bytes
|
||||||
|
|
||||||
short payloadSize = dis.readUnsignedShort()
|
short payloadSize = dis.readUnsignedShort()
|
||||||
byte[] payload = new byte[payloadSize]
|
byte[] payload = new byte[payloadSize]
|
||||||
dis.readFully(payload)
|
dis.readFully(payload)
|
||||||
assert dis.read() == -1
|
assert dis.read() == -1
|
||||||
|
|
||||||
def json = new JsonSlurper()
|
def json = new JsonSlurper()
|
||||||
json = json.parse(payload)
|
json = json.parse(payload)
|
||||||
assert json.tryHosts != null
|
assert json.tryHosts != null
|
||||||
assert json.tryHosts.size() == 1
|
assert json.tryHosts.size() == 1
|
||||||
assert json.tryHosts.contains(destinations.dest2.toBase64())
|
assert json.tryHosts.contains(destinations.dest2.toBase64())
|
||||||
|
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
assert connectionEvents.size() == 1
|
assert connectionEvents.size() == 1
|
||||||
def event = connectionEvents[0]
|
def event = connectionEvents[0]
|
||||||
assert event.endpoint.destination == destinations.dest1
|
assert event.endpoint.destination == destinations.dest1
|
||||||
assert event.status == ConnectionAttemptStatus.REJECTED
|
assert event.status == ConnectionAttemptStatus.REJECTED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -17,271 +17,271 @@ import groovy.mock.interceptor.MockFor
|
|||||||
|
|
||||||
class ConnectionEstablisherTest {
|
class ConnectionEstablisherTest {
|
||||||
|
|
||||||
EventBus eventBus
|
EventBus eventBus
|
||||||
final Destinations destinations = new Destinations()
|
final Destinations destinations = new Destinations()
|
||||||
List<ConnectionEvent> connectionEvents
|
List<ConnectionEvent> connectionEvents
|
||||||
List<HostDiscoveredEvent> discoveredEvents
|
List<HostDiscoveredEvent> discoveredEvents
|
||||||
DataInputStream inputStream
|
DataInputStream inputStream
|
||||||
DataOutputStream outputStream
|
DataOutputStream outputStream
|
||||||
|
|
||||||
def i2pConnectorMock
|
def i2pConnectorMock
|
||||||
I2PConnector i2pConnector
|
I2PConnector i2pConnector
|
||||||
|
|
||||||
MuWireSettings settings
|
MuWireSettings settings
|
||||||
|
|
||||||
def connectionManagerMock
|
def connectionManagerMock
|
||||||
ConnectionManager connectionManager
|
ConnectionManager connectionManager
|
||||||
|
|
||||||
def hostCacheMock
|
def hostCacheMock
|
||||||
HostCache hostCache
|
HostCache hostCache
|
||||||
|
|
||||||
ConnectionEstablisher establisher
|
ConnectionEstablisher establisher
|
||||||
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
void before() {
|
void before() {
|
||||||
connectionEvents = new CopyOnWriteArrayList()
|
connectionEvents = new CopyOnWriteArrayList()
|
||||||
discoveredEvents = new CopyOnWriteArrayList()
|
discoveredEvents = new CopyOnWriteArrayList()
|
||||||
def listener = new Object() {
|
def listener = new Object() {
|
||||||
void onConnectionEvent(ConnectionEvent e) {
|
void onConnectionEvent(ConnectionEvent e) {
|
||||||
connectionEvents.add(e)
|
connectionEvents.add(e)
|
||||||
}
|
}
|
||||||
void onHostDiscoveredEvent(HostDiscoveredEvent e) {
|
void onHostDiscoveredEvent(HostDiscoveredEvent e) {
|
||||||
discoveredEvents.add e
|
discoveredEvents.add e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
eventBus = new EventBus()
|
eventBus = new EventBus()
|
||||||
eventBus.register(ConnectionEvent.class, listener)
|
eventBus.register(ConnectionEvent.class, listener)
|
||||||
eventBus.register(HostDiscoveredEvent.class, listener)
|
eventBus.register(HostDiscoveredEvent.class, listener)
|
||||||
i2pConnectorMock = new MockFor(I2PConnector.class)
|
i2pConnectorMock = new MockFor(I2PConnector.class)
|
||||||
connectionManagerMock = new MockFor(ConnectionManager.class)
|
connectionManagerMock = new MockFor(ConnectionManager.class)
|
||||||
hostCacheMock = new MockFor(HostCache.class)
|
hostCacheMock = new MockFor(HostCache.class)
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
void after() {
|
void after() {
|
||||||
establisher?.stop()
|
establisher?.stop()
|
||||||
i2pConnectorMock.verify i2pConnector
|
i2pConnectorMock.verify i2pConnector
|
||||||
connectionManagerMock.verify connectionManager
|
connectionManagerMock.verify connectionManager
|
||||||
hostCacheMock.verify hostCache
|
hostCacheMock.verify hostCache
|
||||||
Thread.sleep(100)
|
Thread.sleep(100)
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initMocks() {
|
private void initMocks() {
|
||||||
i2pConnector = i2pConnectorMock.proxyInstance()
|
i2pConnector = i2pConnectorMock.proxyInstance()
|
||||||
connectionManager = connectionManagerMock.proxyInstance()
|
connectionManager = connectionManagerMock.proxyInstance()
|
||||||
hostCache = hostCacheMock.proxyInstance()
|
hostCache = hostCacheMock.proxyInstance()
|
||||||
establisher = new ConnectionEstablisher(eventBus, i2pConnector, settings, connectionManager, hostCache)
|
establisher = new ConnectionEstablisher(eventBus, i2pConnector, settings, connectionManager, hostCache)
|
||||||
establisher.start()
|
establisher.start()
|
||||||
Thread.sleep(250)
|
Thread.sleep(250)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testConnectFails() {
|
void testConnectFails() {
|
||||||
settings = new MuWireSettings()
|
settings = new MuWireSettings()
|
||||||
connectionManagerMock.ignore.needsConnections {
|
connectionManagerMock.ignore.needsConnections {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
hostCacheMock.ignore.getHosts { num ->
|
hostCacheMock.ignore.getHosts { num ->
|
||||||
assert num == 1
|
assert num == 1
|
||||||
[destinations.dest1]
|
[destinations.dest1]
|
||||||
}
|
}
|
||||||
connectionManagerMock.ignore.isConnected { dest ->
|
connectionManagerMock.ignore.isConnected { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
i2pConnectorMock.demand.connect { dest ->
|
i2pConnectorMock.demand.connect { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
throw new IOException()
|
throw new IOException()
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
assert connectionEvents.size() == 1
|
assert connectionEvents.size() == 1
|
||||||
def event = connectionEvents[0]
|
def event = connectionEvents[0]
|
||||||
assert event.endpoint.destination == destinations.dest1
|
assert event.endpoint.destination == destinations.dest1
|
||||||
assert event.incoming == false
|
assert event.incoming == false
|
||||||
assert event.status == ConnectionAttemptStatus.FAILED
|
assert event.status == ConnectionAttemptStatus.FAILED
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testConnectionSucceedsPeer() {
|
void testConnectionSucceedsPeer() {
|
||||||
settings = new MuWireSettings() {
|
settings = new MuWireSettings() {
|
||||||
boolean isLeaf() {false}
|
boolean isLeaf() {false}
|
||||||
}
|
}
|
||||||
connectionManagerMock.ignore.needsConnections {
|
connectionManagerMock.ignore.needsConnections {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
hostCacheMock.ignore.getHosts { num ->
|
hostCacheMock.ignore.getHosts { num ->
|
||||||
assert num == 1
|
assert num == 1
|
||||||
[destinations.dest1]
|
[destinations.dest1]
|
||||||
}
|
}
|
||||||
connectionManagerMock.ignore.isConnected { dest ->
|
connectionManagerMock.ignore.isConnected { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
i2pConnectorMock.demand.connect { dest ->
|
i2pConnectorMock.demand.connect { dest ->
|
||||||
PipedOutputStream os = new PipedOutputStream()
|
PipedOutputStream os = new PipedOutputStream()
|
||||||
inputStream = new DataInputStream(new PipedInputStream(os))
|
inputStream = new DataInputStream(new PipedInputStream(os))
|
||||||
PipedInputStream is = new PipedInputStream()
|
PipedInputStream is = new PipedInputStream()
|
||||||
outputStream = new DataOutputStream(new PipedOutputStream(is))
|
outputStream = new DataOutputStream(new PipedOutputStream(is))
|
||||||
new Endpoint(dest, is, os, null)
|
new Endpoint(dest, is, os, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
byte [] header = new byte[11]
|
byte [] header = new byte[11]
|
||||||
inputStream.readFully(header)
|
inputStream.readFully(header)
|
||||||
assert header == "MuWire peer".bytes
|
assert header == "MuWire peer".bytes
|
||||||
|
|
||||||
outputStream.write("OK".bytes)
|
outputStream.write("OK".bytes)
|
||||||
outputStream.flush()
|
outputStream.flush()
|
||||||
|
|
||||||
Thread.sleep(100)
|
Thread.sleep(100)
|
||||||
|
|
||||||
assert connectionEvents.size() == 1
|
assert connectionEvents.size() == 1
|
||||||
def event = connectionEvents[0]
|
def event = connectionEvents[0]
|
||||||
assert event.endpoint.destination == destinations.dest1
|
assert event.endpoint.destination == destinations.dest1
|
||||||
assert event.incoming == false
|
assert event.incoming == false
|
||||||
assert event.status == ConnectionAttemptStatus.SUCCESSFUL
|
assert event.status == ConnectionAttemptStatus.SUCCESSFUL
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testConnectionSucceedsLeaf() {
|
void testConnectionSucceedsLeaf() {
|
||||||
settings = new MuWireSettings() {
|
settings = new MuWireSettings() {
|
||||||
boolean isLeaf() {true}
|
boolean isLeaf() {true}
|
||||||
}
|
}
|
||||||
connectionManagerMock.ignore.needsConnections {
|
connectionManagerMock.ignore.needsConnections {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
hostCacheMock.ignore.getHosts { num ->
|
hostCacheMock.ignore.getHosts { num ->
|
||||||
assert num == 1
|
assert num == 1
|
||||||
[destinations.dest1]
|
[destinations.dest1]
|
||||||
}
|
}
|
||||||
connectionManagerMock.ignore.isConnected { dest ->
|
connectionManagerMock.ignore.isConnected { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
i2pConnectorMock.demand.connect { dest ->
|
i2pConnectorMock.demand.connect { dest ->
|
||||||
PipedOutputStream os = new PipedOutputStream()
|
PipedOutputStream os = new PipedOutputStream()
|
||||||
inputStream = new DataInputStream(new PipedInputStream(os))
|
inputStream = new DataInputStream(new PipedInputStream(os))
|
||||||
PipedInputStream is = new PipedInputStream()
|
PipedInputStream is = new PipedInputStream()
|
||||||
outputStream = new DataOutputStream(new PipedOutputStream(is))
|
outputStream = new DataOutputStream(new PipedOutputStream(is))
|
||||||
new Endpoint(dest, is, os, null)
|
new Endpoint(dest, is, os, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
byte [] header = new byte[11]
|
byte [] header = new byte[11]
|
||||||
inputStream.readFully(header)
|
inputStream.readFully(header)
|
||||||
assert header == "MuWire leaf".bytes
|
assert header == "MuWire leaf".bytes
|
||||||
|
|
||||||
outputStream.write("OK".bytes)
|
outputStream.write("OK".bytes)
|
||||||
outputStream.flush()
|
outputStream.flush()
|
||||||
|
|
||||||
Thread.sleep(100)
|
Thread.sleep(100)
|
||||||
|
|
||||||
assert connectionEvents.size() == 1
|
assert connectionEvents.size() == 1
|
||||||
def event = connectionEvents[0]
|
def event = connectionEvents[0]
|
||||||
assert event.endpoint.destination == destinations.dest1
|
assert event.endpoint.destination == destinations.dest1
|
||||||
assert event.incoming == false
|
assert event.incoming == false
|
||||||
assert event.status == ConnectionAttemptStatus.SUCCESSFUL
|
assert event.status == ConnectionAttemptStatus.SUCCESSFUL
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testConnectionRejected() {
|
void testConnectionRejected() {
|
||||||
settings = new MuWireSettings() {
|
settings = new MuWireSettings() {
|
||||||
boolean isLeaf() {false}
|
boolean isLeaf() {false}
|
||||||
}
|
}
|
||||||
connectionManagerMock.ignore.needsConnections {
|
connectionManagerMock.ignore.needsConnections {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
hostCacheMock.ignore.getHosts { num ->
|
hostCacheMock.ignore.getHosts { num ->
|
||||||
assert num == 1
|
assert num == 1
|
||||||
[destinations.dest1]
|
[destinations.dest1]
|
||||||
}
|
}
|
||||||
connectionManagerMock.ignore.isConnected { dest ->
|
connectionManagerMock.ignore.isConnected { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
i2pConnectorMock.demand.connect { dest ->
|
i2pConnectorMock.demand.connect { dest ->
|
||||||
PipedOutputStream os = new PipedOutputStream()
|
PipedOutputStream os = new PipedOutputStream()
|
||||||
inputStream = new DataInputStream(new PipedInputStream(os))
|
inputStream = new DataInputStream(new PipedInputStream(os))
|
||||||
PipedInputStream is = new PipedInputStream()
|
PipedInputStream is = new PipedInputStream()
|
||||||
outputStream = new DataOutputStream(new PipedOutputStream(is))
|
outputStream = new DataOutputStream(new PipedOutputStream(is))
|
||||||
new Endpoint(dest, is, os, null)
|
new Endpoint(dest, is, os, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
byte [] header = new byte[11]
|
byte [] header = new byte[11]
|
||||||
inputStream.readFully(header)
|
inputStream.readFully(header)
|
||||||
assert header == "MuWire peer".bytes
|
assert header == "MuWire peer".bytes
|
||||||
|
|
||||||
outputStream.write("REJECT".bytes)
|
outputStream.write("REJECT".bytes)
|
||||||
outputStream.flush()
|
outputStream.flush()
|
||||||
|
|
||||||
Thread.sleep(100)
|
Thread.sleep(100)
|
||||||
|
|
||||||
assert connectionEvents.size() == 1
|
assert connectionEvents.size() == 1
|
||||||
def event = connectionEvents[0]
|
def event = connectionEvents[0]
|
||||||
assert event.endpoint.destination == destinations.dest1
|
assert event.endpoint.destination == destinations.dest1
|
||||||
assert event.incoming == false
|
assert event.incoming == false
|
||||||
assert event.status == ConnectionAttemptStatus.REJECTED
|
assert event.status == ConnectionAttemptStatus.REJECTED
|
||||||
assert discoveredEvents.isEmpty()
|
assert discoveredEvents.isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testConnectionRejectedSuggestions() {
|
void testConnectionRejectedSuggestions() {
|
||||||
settings = new MuWireSettings() {
|
settings = new MuWireSettings() {
|
||||||
boolean isLeaf() {false}
|
boolean isLeaf() {false}
|
||||||
}
|
}
|
||||||
connectionManagerMock.ignore.needsConnections {
|
connectionManagerMock.ignore.needsConnections {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
hostCacheMock.ignore.getHosts { num ->
|
hostCacheMock.ignore.getHosts { num ->
|
||||||
assert num == 1
|
assert num == 1
|
||||||
[destinations.dest1]
|
[destinations.dest1]
|
||||||
}
|
}
|
||||||
connectionManagerMock.ignore.isConnected { dest ->
|
connectionManagerMock.ignore.isConnected { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
i2pConnectorMock.demand.connect { dest ->
|
i2pConnectorMock.demand.connect { dest ->
|
||||||
PipedOutputStream os = new PipedOutputStream()
|
PipedOutputStream os = new PipedOutputStream()
|
||||||
inputStream = new DataInputStream(new PipedInputStream(os))
|
inputStream = new DataInputStream(new PipedInputStream(os))
|
||||||
PipedInputStream is = new PipedInputStream()
|
PipedInputStream is = new PipedInputStream()
|
||||||
outputStream = new DataOutputStream(new PipedOutputStream(is))
|
outputStream = new DataOutputStream(new PipedOutputStream(is))
|
||||||
new Endpoint(dest, is, os, null)
|
new Endpoint(dest, is, os, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
byte [] header = new byte[11]
|
byte [] header = new byte[11]
|
||||||
inputStream.readFully(header)
|
inputStream.readFully(header)
|
||||||
assert header == "MuWire peer".bytes
|
assert header == "MuWire peer".bytes
|
||||||
|
|
||||||
outputStream.write("REJECT".bytes)
|
outputStream.write("REJECT".bytes)
|
||||||
outputStream.flush()
|
outputStream.flush()
|
||||||
|
|
||||||
def json = [:]
|
def json = [:]
|
||||||
json.tryHosts = [destinations.dest2.toBase64()]
|
json.tryHosts = [destinations.dest2.toBase64()]
|
||||||
json = JsonOutput.toJson(json)
|
json = JsonOutput.toJson(json)
|
||||||
outputStream.writeShort(json.bytes.length)
|
outputStream.writeShort(json.bytes.length)
|
||||||
outputStream.write(json.bytes)
|
outputStream.write(json.bytes)
|
||||||
outputStream.flush()
|
outputStream.flush()
|
||||||
Thread.sleep(100)
|
Thread.sleep(100)
|
||||||
|
|
||||||
assert connectionEvents.size() == 1
|
assert connectionEvents.size() == 1
|
||||||
def event = connectionEvents[0]
|
def event = connectionEvents[0]
|
||||||
assert event.endpoint.destination == destinations.dest1
|
assert event.endpoint.destination == destinations.dest1
|
||||||
assert event.incoming == false
|
assert event.incoming == false
|
||||||
assert event.status == ConnectionAttemptStatus.REJECTED
|
assert event.status == ConnectionAttemptStatus.REJECTED
|
||||||
|
|
||||||
assert discoveredEvents.size() == 1
|
assert discoveredEvents.size() == 1
|
||||||
event = discoveredEvents[0]
|
event = discoveredEvents[0]
|
||||||
assert event.destination == destinations.dest2
|
assert event.destination == destinations.dest2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,76 +8,76 @@ import org.junit.Test
|
|||||||
|
|
||||||
class FileHasherTest extends GroovyTestCase {
|
class FileHasherTest extends GroovyTestCase {
|
||||||
|
|
||||||
def hasher = new FileHasher()
|
def hasher = new FileHasher()
|
||||||
File tmp
|
File tmp
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
void setUp() {
|
void setUp() {
|
||||||
tmp = File.createTempFile("testFile", "test")
|
tmp = File.createTempFile("testFile", "test")
|
||||||
tmp.deleteOnExit()
|
tmp.deleteOnExit()
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
void tearDown() {
|
void tearDown() {
|
||||||
tmp?.delete()
|
tmp?.delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testPieceSize() {
|
void testPieceSize() {
|
||||||
assert 17 == FileHasher.getPieceSize(1000000)
|
assert 17 == FileHasher.getPieceSize(1000000)
|
||||||
assert 17 == FileHasher.getPieceSize(100000000)
|
assert 17 == FileHasher.getPieceSize(100000000)
|
||||||
assert 24 == FileHasher.getPieceSize(FileHasher.MAX_SIZE)
|
assert 24 == FileHasher.getPieceSize(FileHasher.MAX_SIZE)
|
||||||
shouldFail IllegalArgumentException, {
|
shouldFail IllegalArgumentException, {
|
||||||
FileHasher.getPieceSize(Long.MAX_VALUE)
|
FileHasher.getPieceSize(Long.MAX_VALUE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testHash1Byte() {
|
void testHash1Byte() {
|
||||||
def fos = new FileOutputStream(tmp)
|
def fos = new FileOutputStream(tmp)
|
||||||
fos.write(0)
|
fos.write(0)
|
||||||
fos.close()
|
fos.close()
|
||||||
def ih = hasher.hashFile(tmp)
|
def ih = hasher.hashFile(tmp)
|
||||||
assert ih.getHashList().length == 32
|
assert ih.getHashList().length == 32
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testHash1PieceExact() {
|
void testHash1PieceExact() {
|
||||||
def fos = new FileOutputStream(tmp)
|
def fos = new FileOutputStream(tmp)
|
||||||
byte [] b = new byte[ 0x1 << 18]
|
byte [] b = new byte[ 0x1 << 18]
|
||||||
fos.write b
|
fos.write b
|
||||||
fos.close()
|
fos.close()
|
||||||
def ih = hasher.hashFile tmp
|
def ih = hasher.hashFile tmp
|
||||||
assert ih.getHashList().length == 64
|
assert ih.getHashList().length == 64
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testHash1Piece1Byte() {
|
void testHash1Piece1Byte() {
|
||||||
def fos = new FileOutputStream(tmp)
|
def fos = new FileOutputStream(tmp)
|
||||||
byte [] b = new byte[ (0x1 << 18) + 1]
|
byte [] b = new byte[ (0x1 << 18) + 1]
|
||||||
fos.write b
|
fos.write b
|
||||||
fos.close()
|
fos.close()
|
||||||
def ih = hasher.hashFile tmp
|
def ih = hasher.hashFile tmp
|
||||||
assert ih.getHashList().length == 96
|
assert ih.getHashList().length == 96
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testHash2Pieces() {
|
void testHash2Pieces() {
|
||||||
def fos = new FileOutputStream(tmp)
|
def fos = new FileOutputStream(tmp)
|
||||||
byte [] b = new byte[ (0x1 << 19)]
|
byte [] b = new byte[ (0x1 << 19)]
|
||||||
fos.write b
|
fos.write b
|
||||||
fos.close()
|
fos.close()
|
||||||
def ih = hasher.hashFile tmp
|
def ih = hasher.hashFile tmp
|
||||||
assert ih.getHashList().length == 128
|
assert ih.getHashList().length == 128
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testHash2Pieces2Bytes() {
|
void testHash2Pieces2Bytes() {
|
||||||
def fos = new FileOutputStream(tmp)
|
def fos = new FileOutputStream(tmp)
|
||||||
byte [] b = new byte[ (0x1 << 19) + 2]
|
byte [] b = new byte[ (0x1 << 19) + 2]
|
||||||
fos.write b
|
fos.write b
|
||||||
fos.close()
|
fos.close()
|
||||||
def ih = hasher.hashFile tmp
|
def ih = hasher.hashFile tmp
|
||||||
assert ih.getHashList().length == 160
|
assert ih.getHashList().length == 160
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -12,177 +12,177 @@ import com.muwire.core.search.SearchEvent
|
|||||||
|
|
||||||
class FileManagerTest {
|
class FileManagerTest {
|
||||||
|
|
||||||
EventBus eventBus
|
EventBus eventBus
|
||||||
|
|
||||||
FileManager manager
|
FileManager manager
|
||||||
volatile ResultsEvent results
|
volatile ResultsEvent results
|
||||||
|
|
||||||
def listener = new Object() {
|
def listener = new Object() {
|
||||||
void onResultsEvent(ResultsEvent e) {
|
void onResultsEvent(ResultsEvent e) {
|
||||||
results = e
|
results = e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
void before() {
|
void before() {
|
||||||
eventBus = new EventBus()
|
eventBus = new EventBus()
|
||||||
eventBus.register(ResultsEvent.class, listener)
|
eventBus.register(ResultsEvent.class, listener)
|
||||||
manager = new FileManager(eventBus, new MuWireSettings())
|
manager = new FileManager(eventBus, new MuWireSettings())
|
||||||
results = null
|
results = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testHash1Result() {
|
void testHash1Result() {
|
||||||
File f = new File("a b.c")
|
File f = new File("a b.c")
|
||||||
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
||||||
SharedFile sf = new SharedFile(f,ih, 0)
|
SharedFile sf = new SharedFile(f,ih, 0)
|
||||||
FileHashedEvent fhe = new FileHashedEvent(sharedFile: sf)
|
FileHashedEvent fhe = new FileHashedEvent(sharedFile: sf)
|
||||||
manager.onFileHashedEvent(fhe)
|
manager.onFileHashedEvent(fhe)
|
||||||
|
|
||||||
UUID uuid = UUID.randomUUID()
|
UUID uuid = UUID.randomUUID()
|
||||||
SearchEvent se = new SearchEvent(searchHash: ih.getRoot(), uuid: uuid)
|
SearchEvent se = new SearchEvent(searchHash: ih.getRoot(), uuid: uuid)
|
||||||
|
|
||||||
manager.onSearchEvent(se)
|
manager.onSearchEvent(se)
|
||||||
Thread.sleep(20)
|
Thread.sleep(20)
|
||||||
|
|
||||||
assert results != null
|
assert results != null
|
||||||
assert results.results.size() == 1
|
assert results.results.size() == 1
|
||||||
assert results.results.contains(sf)
|
assert results.results.contains(sf)
|
||||||
assert results.uuid == uuid
|
assert results.uuid == uuid
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testHash2Results() {
|
void testHash2Results() {
|
||||||
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
||||||
SharedFile sf1 = new SharedFile(new File("a b.c"), ih, 0)
|
SharedFile sf1 = new SharedFile(new File("a b.c"), ih, 0)
|
||||||
SharedFile sf2 = new SharedFile(new File("d e.f"), ih, 0)
|
SharedFile sf2 = new SharedFile(new File("d e.f"), ih, 0)
|
||||||
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile : sf1)
|
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile : sf1)
|
||||||
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile : sf2)
|
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile : sf2)
|
||||||
|
|
||||||
UUID uuid = UUID.randomUUID()
|
UUID uuid = UUID.randomUUID()
|
||||||
SearchEvent se = new SearchEvent(searchHash: ih.getRoot(), uuid: uuid)
|
SearchEvent se = new SearchEvent(searchHash: ih.getRoot(), uuid: uuid)
|
||||||
|
|
||||||
manager.onSearchEvent(se)
|
manager.onSearchEvent(se)
|
||||||
Thread.sleep(20)
|
Thread.sleep(20)
|
||||||
|
|
||||||
assert results != null
|
assert results != null
|
||||||
assert results.results.size() == 2
|
assert results.results.size() == 2
|
||||||
assert results.results.contains(sf1)
|
assert results.results.contains(sf1)
|
||||||
assert results.results.contains(sf2)
|
assert results.results.contains(sf2)
|
||||||
assert results.uuid == uuid
|
assert results.uuid == uuid
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testHash0Results() {
|
void testHash0Results() {
|
||||||
File f = new File("a b.c")
|
File f = new File("a b.c")
|
||||||
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
||||||
SharedFile sf = new SharedFile(f,ih, 0)
|
SharedFile sf = new SharedFile(f,ih, 0)
|
||||||
FileHashedEvent fhe = new FileHashedEvent(sharedFile: sf)
|
FileHashedEvent fhe = new FileHashedEvent(sharedFile: sf)
|
||||||
manager.onFileHashedEvent(fhe)
|
manager.onFileHashedEvent(fhe)
|
||||||
|
|
||||||
manager.onSearchEvent new SearchEvent(searchHash: new byte[32], uuid: UUID.randomUUID())
|
manager.onSearchEvent new SearchEvent(searchHash: new byte[32], uuid: UUID.randomUUID())
|
||||||
Thread.sleep(20)
|
Thread.sleep(20)
|
||||||
|
|
||||||
assert results == null
|
assert results == null
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testKeyword1Result() {
|
void testKeyword1Result() {
|
||||||
File f = new File("a b.c")
|
File f = new File("a b.c")
|
||||||
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
||||||
SharedFile sf = new SharedFile(f,ih,0)
|
SharedFile sf = new SharedFile(f,ih,0)
|
||||||
FileHashedEvent fhe = new FileHashedEvent(sharedFile: sf)
|
FileHashedEvent fhe = new FileHashedEvent(sharedFile: sf)
|
||||||
manager.onFileHashedEvent(fhe)
|
manager.onFileHashedEvent(fhe)
|
||||||
|
|
||||||
UUID uuid = UUID.randomUUID()
|
UUID uuid = UUID.randomUUID()
|
||||||
manager.onSearchEvent new SearchEvent(searchTerms: ["a"], uuid:uuid)
|
manager.onSearchEvent new SearchEvent(searchTerms: ["a"], uuid:uuid)
|
||||||
Thread.sleep(20)
|
Thread.sleep(20)
|
||||||
|
|
||||||
assert results != null
|
assert results != null
|
||||||
assert results.results.size() == 1
|
assert results.results.size() == 1
|
||||||
assert results.results.contains(sf)
|
assert results.results.contains(sf)
|
||||||
assert results.uuid == uuid
|
assert results.uuid == uuid
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testKeyword2Results() {
|
void testKeyword2Results() {
|
||||||
File f1 = new File("a b.c")
|
File f1 = new File("a b.c")
|
||||||
InfoHash ih1 = InfoHash.fromHashList(new byte[32])
|
InfoHash ih1 = InfoHash.fromHashList(new byte[32])
|
||||||
SharedFile sf1 = new SharedFile(f1, ih1, 0)
|
SharedFile sf1 = new SharedFile(f1, ih1, 0)
|
||||||
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile: sf1)
|
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile: sf1)
|
||||||
|
|
||||||
File f2 = new File("c d.e")
|
File f2 = new File("c d.e")
|
||||||
InfoHash ih2 = InfoHash.fromHashList(new byte[64])
|
InfoHash ih2 = InfoHash.fromHashList(new byte[64])
|
||||||
SharedFile sf2 = new SharedFile(f2, ih2, 0)
|
SharedFile sf2 = new SharedFile(f2, ih2, 0)
|
||||||
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile: sf2)
|
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile: sf2)
|
||||||
|
|
||||||
UUID uuid = UUID.randomUUID()
|
UUID uuid = UUID.randomUUID()
|
||||||
manager.onSearchEvent new SearchEvent(searchTerms: ["c"], uuid:uuid)
|
manager.onSearchEvent new SearchEvent(searchTerms: ["c"], uuid:uuid)
|
||||||
Thread.sleep(20)
|
Thread.sleep(20)
|
||||||
|
|
||||||
assert results != null
|
assert results != null
|
||||||
assert results.results.size() == 2
|
assert results.results.size() == 2
|
||||||
assert results.results.contains(sf1)
|
assert results.results.contains(sf1)
|
||||||
assert results.results.contains(sf2)
|
assert results.results.contains(sf2)
|
||||||
assert results.uuid == uuid
|
assert results.uuid == uuid
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testKeyword0Results() {
|
void testKeyword0Results() {
|
||||||
File f = new File("a b.c")
|
File f = new File("a b.c")
|
||||||
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
||||||
SharedFile sf = new SharedFile(f,ih,0)
|
SharedFile sf = new SharedFile(f,ih,0)
|
||||||
FileHashedEvent fhe = new FileHashedEvent(sharedFile: sf)
|
FileHashedEvent fhe = new FileHashedEvent(sharedFile: sf)
|
||||||
manager.onFileHashedEvent(fhe)
|
manager.onFileHashedEvent(fhe)
|
||||||
|
|
||||||
manager.onSearchEvent new SearchEvent(searchTerms: ["d"], uuid: UUID.randomUUID())
|
manager.onSearchEvent new SearchEvent(searchTerms: ["d"], uuid: UUID.randomUUID())
|
||||||
Thread.sleep(20)
|
Thread.sleep(20)
|
||||||
|
|
||||||
assert results == null
|
assert results == null
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testRemoveFileExistingHash() {
|
void testRemoveFileExistingHash() {
|
||||||
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
||||||
SharedFile sf1 = new SharedFile(new File("a b.c"), ih, 0)
|
SharedFile sf1 = new SharedFile(new File("a b.c"), ih, 0)
|
||||||
SharedFile sf2 = new SharedFile(new File("d e.f"), ih, 0)
|
SharedFile sf2 = new SharedFile(new File("d e.f"), ih, 0)
|
||||||
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile : sf1)
|
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile : sf1)
|
||||||
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile : sf2)
|
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile : sf2)
|
||||||
|
|
||||||
manager.onFileUnsharedEvent new FileUnsharedEvent(unsharedFile: sf2)
|
manager.onFileUnsharedEvent new FileUnsharedEvent(unsharedFile: sf2)
|
||||||
|
|
||||||
manager.onSearchEvent new SearchEvent(searchHash : ih.getRoot())
|
manager.onSearchEvent new SearchEvent(searchHash : ih.getRoot())
|
||||||
Thread.sleep(20)
|
Thread.sleep(20)
|
||||||
assert results != null
|
assert results != null
|
||||||
assert results.results.size() == 1
|
assert results.results.size() == 1
|
||||||
assert results.results.contains(sf1)
|
assert results.results.contains(sf1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testRemoveFile() {
|
void testRemoveFile() {
|
||||||
File f1 = new File("a b.c")
|
File f1 = new File("a b.c")
|
||||||
InfoHash ih1 = InfoHash.fromHashList(new byte[32])
|
InfoHash ih1 = InfoHash.fromHashList(new byte[32])
|
||||||
SharedFile sf1 = new SharedFile(f1, ih1, 0)
|
SharedFile sf1 = new SharedFile(f1, ih1, 0)
|
||||||
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile: sf1)
|
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile: sf1)
|
||||||
|
|
||||||
File f2 = new File("c d.e")
|
File f2 = new File("c d.e")
|
||||||
InfoHash ih2 = InfoHash.fromHashList(new byte[64])
|
InfoHash ih2 = InfoHash.fromHashList(new byte[64])
|
||||||
SharedFile sf2 = new SharedFile(f2, ih2, 0)
|
SharedFile sf2 = new SharedFile(f2, ih2, 0)
|
||||||
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile: sf2)
|
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile: sf2)
|
||||||
|
|
||||||
manager.onFileUnsharedEvent new FileUnsharedEvent(unsharedFile: sf2)
|
manager.onFileUnsharedEvent new FileUnsharedEvent(unsharedFile: sf2)
|
||||||
|
|
||||||
// 1 match left
|
// 1 match left
|
||||||
manager.onSearchEvent new SearchEvent(searchTerms: ["c"])
|
manager.onSearchEvent new SearchEvent(searchTerms: ["c"])
|
||||||
Thread.sleep(20)
|
Thread.sleep(20)
|
||||||
assert results != null
|
assert results != null
|
||||||
assert results.results.size() == 1
|
assert results.results.size() == 1
|
||||||
assert results.results.contains(sf1)
|
assert results.results.contains(sf1)
|
||||||
|
|
||||||
// no match
|
// no match
|
||||||
results = null
|
results = null
|
||||||
manager.onSearchEvent new SearchEvent(searchTerms: ["d"])
|
manager.onSearchEvent new SearchEvent(searchTerms: ["d"])
|
||||||
assert results == null
|
assert results == null
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -12,54 +12,54 @@ import com.muwire.core.MuWireSettings
|
|||||||
|
|
||||||
class HasherServiceTest {
|
class HasherServiceTest {
|
||||||
|
|
||||||
HasherService service
|
HasherService service
|
||||||
FileHasher hasher
|
FileHasher hasher
|
||||||
EventBus eventBus
|
EventBus eventBus
|
||||||
def listener = new ArrayBlockingQueue(100) {
|
def listener = new ArrayBlockingQueue(100) {
|
||||||
void onFileHashedEvent(FileHashedEvent evt) {
|
void onFileHashedEvent(FileHashedEvent evt) {
|
||||||
offer evt
|
offer evt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
void before() {
|
void before() {
|
||||||
eventBus = new EventBus()
|
eventBus = new EventBus()
|
||||||
hasher = new FileHasher()
|
hasher = new FileHasher()
|
||||||
service = new HasherService(hasher, eventBus, new FileManager(eventBus, new MuWireSettings()))
|
service = new HasherService(hasher, eventBus, new FileManager(eventBus, new MuWireSettings()))
|
||||||
eventBus.register(FileHashedEvent.class, listener)
|
eventBus.register(FileHashedEvent.class, listener)
|
||||||
eventBus.register(FileSharedEvent.class, service)
|
eventBus.register(FileSharedEvent.class, service)
|
||||||
service.start()
|
service.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
void after() {
|
void after() {
|
||||||
listener.clear()
|
listener.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSingleFile() {
|
void testSingleFile() {
|
||||||
File f = new File("build.gradle")
|
File f = new File("build.gradle")
|
||||||
service.onFileSharedEvent new FileSharedEvent(file: f)
|
service.onFileSharedEvent new FileSharedEvent(file: f)
|
||||||
Thread.sleep(100)
|
Thread.sleep(100)
|
||||||
def hashed = listener.poll()
|
def hashed = listener.poll()
|
||||||
assert hashed instanceof FileHashedEvent
|
assert hashed instanceof FileHashedEvent
|
||||||
assert hashed.sharedFile.file == f.getCanonicalFile()
|
assert hashed.sharedFile.file == f.getCanonicalFile()
|
||||||
assert hashed.sharedFile.infoHash != null
|
assert hashed.sharedFile.infoHash != null
|
||||||
assert listener.isEmpty()
|
assert listener.isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testDirectory() {
|
void testDirectory() {
|
||||||
File f = new File(".")
|
File f = new File(".")
|
||||||
service.onFileSharedEvent new FileSharedEvent(file: f)
|
service.onFileSharedEvent new FileSharedEvent(file: f)
|
||||||
Set<String> fileNames = new HashSet<>()
|
Set<String> fileNames = new HashSet<>()
|
||||||
while (true) {
|
while (true) {
|
||||||
def hashed = listener.poll(1000, TimeUnit.MILLISECONDS)
|
def hashed = listener.poll(1000, TimeUnit.MILLISECONDS)
|
||||||
if (hashed == null)
|
if (hashed == null)
|
||||||
break
|
break
|
||||||
fileNames.add(hashed.sharedFile?.file?.getName())
|
fileNames.add(hashed.sharedFile?.file?.getName())
|
||||||
}
|
}
|
||||||
assert fileNames.contains("build.gradle")
|
assert fileNames.contains("build.gradle")
|
||||||
assert fileNames.contains("HasherServiceTest.groovy")
|
assert fileNames.contains("HasherServiceTest.groovy")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -17,193 +17,193 @@ import net.i2p.data.Base64
|
|||||||
|
|
||||||
class PersisterServiceLoadingTest {
|
class PersisterServiceLoadingTest {
|
||||||
|
|
||||||
class Listener {
|
class Listener {
|
||||||
def publishedFiles = []
|
def publishedFiles = []
|
||||||
def onFileLoadedEvent(FileLoadedEvent e) {
|
def onFileLoadedEvent(FileLoadedEvent e) {
|
||||||
publishedFiles.add(e.loadedFile)
|
publishedFiles.add(e.loadedFile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EventBus eventBus
|
EventBus eventBus
|
||||||
Listener listener
|
Listener listener
|
||||||
File sharedDir
|
File sharedDir
|
||||||
File sharedFile1, sharedFile2
|
File sharedFile1, sharedFile2
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
void setup() {
|
void setup() {
|
||||||
eventBus = new EventBus()
|
eventBus = new EventBus()
|
||||||
listener = new Listener()
|
listener = new Listener()
|
||||||
eventBus.register(FileLoadedEvent.class, listener)
|
eventBus.register(FileLoadedEvent.class, listener)
|
||||||
|
|
||||||
sharedDir = new File("sharedDir")
|
sharedDir = new File("sharedDir")
|
||||||
sharedDir.mkdir()
|
sharedDir.mkdir()
|
||||||
sharedDir.deleteOnExit()
|
sharedDir.deleteOnExit()
|
||||||
|
|
||||||
sharedFile1 = new File(sharedDir,"file1")
|
sharedFile1 = new File(sharedDir,"file1")
|
||||||
sharedFile1.deleteOnExit()
|
sharedFile1.deleteOnExit()
|
||||||
|
|
||||||
sharedFile2 = new File(sharedDir,"file2")
|
sharedFile2 = new File(sharedDir,"file2")
|
||||||
sharedFile2.deleteOnExit()
|
sharedFile2.deleteOnExit()
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeToSharedFile(File file, int size) {
|
private void writeToSharedFile(File file, int size) {
|
||||||
FileOutputStream fos = new FileOutputStream(file);
|
FileOutputStream fos = new FileOutputStream(file);
|
||||||
fos.write new byte[size]
|
fos.write new byte[size]
|
||||||
fos.close()
|
fos.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
private File initPersisted() {
|
private File initPersisted() {
|
||||||
File persisted = new File("persisted")
|
File persisted = new File("persisted")
|
||||||
if (persisted.exists())
|
if (persisted.exists())
|
||||||
persisted.delete()
|
persisted.delete()
|
||||||
persisted.deleteOnExit()
|
persisted.deleteOnExit()
|
||||||
persisted
|
persisted
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void test1SharedFile1Piece() {
|
void test1SharedFile1Piece() {
|
||||||
writeToSharedFile(sharedFile1, 1)
|
writeToSharedFile(sharedFile1, 1)
|
||||||
FileHasher fh = new FileHasher()
|
FileHasher fh = new FileHasher()
|
||||||
InfoHash ih1 = fh.hashFile(sharedFile1)
|
InfoHash ih1 = fh.hashFile(sharedFile1)
|
||||||
|
|
||||||
def json = [:]
|
def json = [:]
|
||||||
json.file = getSharedFileJsonName(sharedFile1)
|
json.file = getSharedFileJsonName(sharedFile1)
|
||||||
json.length = 1
|
json.length = 1
|
||||||
json.infoHash = Base64.encode(ih1.getRoot())
|
json.infoHash = Base64.encode(ih1.getRoot())
|
||||||
json.hashList = [Base64.encode(ih1.getHashList())]
|
json.hashList = [Base64.encode(ih1.getHashList())]
|
||||||
|
|
||||||
json = JsonOutput.toJson(json)
|
json = JsonOutput.toJson(json)
|
||||||
|
|
||||||
File persisted = initPersisted()
|
File persisted = initPersisted()
|
||||||
persisted.write json
|
persisted.write json
|
||||||
|
|
||||||
PersisterService ps = new PersisterService(persisted, eventBus, 100, null)
|
PersisterService ps = new PersisterService(persisted, eventBus, 100, null)
|
||||||
ps.onUILoadedEvent(null)
|
ps.onUILoadedEvent(null)
|
||||||
Thread.sleep(2000)
|
Thread.sleep(2000)
|
||||||
|
|
||||||
assert listener.publishedFiles.size() == 1
|
assert listener.publishedFiles.size() == 1
|
||||||
def loadedFile = listener.publishedFiles[0]
|
def loadedFile = listener.publishedFiles[0]
|
||||||
assert loadedFile != null
|
assert loadedFile != null
|
||||||
assert loadedFile.file == sharedFile1.getCanonicalFile()
|
assert loadedFile.file == sharedFile1.getCanonicalFile()
|
||||||
assert loadedFile.infoHash == ih1
|
assert loadedFile.infoHash == ih1
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getSharedFileJsonName(File sharedFile) {
|
private static String getSharedFileJsonName(File sharedFile) {
|
||||||
def encoded = DataUtil.encodei18nString(sharedFile.getCanonicalFile().toString())
|
def encoded = DataUtil.encodei18nString(sharedFile.getCanonicalFile().toString())
|
||||||
Base64.encode(encoded)
|
Base64.encode(encoded)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test1SharedFile2Pieces() {
|
public void test1SharedFile2Pieces() {
|
||||||
writeToSharedFile(sharedFile1, (0x1 << 18) + 1)
|
writeToSharedFile(sharedFile1, (0x1 << 18) + 1)
|
||||||
FileHasher fh = new FileHasher()
|
FileHasher fh = new FileHasher()
|
||||||
InfoHash ih1 = fh.hashFile(sharedFile1)
|
InfoHash ih1 = fh.hashFile(sharedFile1)
|
||||||
|
|
||||||
assert ih1.getHashList().length == 96
|
assert ih1.getHashList().length == 96
|
||||||
|
|
||||||
def json = [:]
|
def json = [:]
|
||||||
json.file = getSharedFileJsonName(sharedFile1)
|
json.file = getSharedFileJsonName(sharedFile1)
|
||||||
json.length = sharedFile1.length()
|
json.length = sharedFile1.length()
|
||||||
json.infoHash = Base64.encode ih1.getRoot()
|
json.infoHash = Base64.encode ih1.getRoot()
|
||||||
|
|
||||||
byte [] tmp = new byte[32]
|
byte [] tmp = new byte[32]
|
||||||
System.arraycopy(ih1.getHashList(), 0, tmp, 0, 32)
|
System.arraycopy(ih1.getHashList(), 0, tmp, 0, 32)
|
||||||
String hash1 = Base64.encode(tmp)
|
String hash1 = Base64.encode(tmp)
|
||||||
System.arraycopy(ih1.getHashList(), 32, tmp, 0, 32)
|
System.arraycopy(ih1.getHashList(), 32, tmp, 0, 32)
|
||||||
String hash2 = Base64.encode(tmp)
|
String hash2 = Base64.encode(tmp)
|
||||||
System.arraycopy(ih1.getHashList(), 64, tmp, 0, 32)
|
System.arraycopy(ih1.getHashList(), 64, tmp, 0, 32)
|
||||||
String hash3 = Base64.encode(tmp)
|
String hash3 = Base64.encode(tmp)
|
||||||
json.hashList = [hash1, hash2, hash3]
|
json.hashList = [hash1, hash2, hash3]
|
||||||
|
|
||||||
json = JsonOutput.toJson(json)
|
json = JsonOutput.toJson(json)
|
||||||
|
|
||||||
File persisted = initPersisted()
|
File persisted = initPersisted()
|
||||||
persisted.write json
|
persisted.write json
|
||||||
|
|
||||||
PersisterService ps = new PersisterService(persisted, eventBus, 100, null)
|
PersisterService ps = new PersisterService(persisted, eventBus, 100, null)
|
||||||
ps.onUILoadedEvent(null)
|
ps.onUILoadedEvent(null)
|
||||||
Thread.sleep(2000)
|
Thread.sleep(2000)
|
||||||
|
|
||||||
assert listener.publishedFiles.size() == 1
|
assert listener.publishedFiles.size() == 1
|
||||||
def loadedFile = listener.publishedFiles[0]
|
def loadedFile = listener.publishedFiles[0]
|
||||||
assert loadedFile != null
|
assert loadedFile != null
|
||||||
assert loadedFile.file == sharedFile1.getCanonicalFile()
|
assert loadedFile.file == sharedFile1.getCanonicalFile()
|
||||||
assert loadedFile.infoHash == ih1
|
assert loadedFile.infoHash == ih1
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void test2SharedFiles() {
|
void test2SharedFiles() {
|
||||||
writeToSharedFile(sharedFile1, 1)
|
writeToSharedFile(sharedFile1, 1)
|
||||||
writeToSharedFile(sharedFile2, 2)
|
writeToSharedFile(sharedFile2, 2)
|
||||||
FileHasher fh = new FileHasher()
|
FileHasher fh = new FileHasher()
|
||||||
InfoHash ih1 = fh.hashFile(sharedFile1)
|
InfoHash ih1 = fh.hashFile(sharedFile1)
|
||||||
InfoHash ih2 = fh.hashFile(sharedFile2)
|
InfoHash ih2 = fh.hashFile(sharedFile2)
|
||||||
|
|
||||||
assert ih1 != ih2
|
assert ih1 != ih2
|
||||||
|
|
||||||
File persisted = initPersisted()
|
File persisted = initPersisted()
|
||||||
|
|
||||||
def json1 = [:]
|
def json1 = [:]
|
||||||
json1.file = getSharedFileJsonName(sharedFile1)
|
json1.file = getSharedFileJsonName(sharedFile1)
|
||||||
json1.length = 1
|
json1.length = 1
|
||||||
json1.infoHash = Base64.encode(ih1.getRoot())
|
json1.infoHash = Base64.encode(ih1.getRoot())
|
||||||
json1.hashList = [Base64.encode(ih1.getHashList())]
|
json1.hashList = [Base64.encode(ih1.getHashList())]
|
||||||
|
|
||||||
json1 = JsonOutput.toJson(json1)
|
json1 = JsonOutput.toJson(json1)
|
||||||
|
|
||||||
def json2 = [:]
|
def json2 = [:]
|
||||||
json2.file = getSharedFileJsonName(sharedFile2)
|
json2.file = getSharedFileJsonName(sharedFile2)
|
||||||
json2.length = 2
|
json2.length = 2
|
||||||
json2.infoHash = Base64.encode(ih2.getRoot())
|
json2.infoHash = Base64.encode(ih2.getRoot())
|
||||||
json2.hashList = [Base64.encode(ih2.getHashList())]
|
json2.hashList = [Base64.encode(ih2.getHashList())]
|
||||||
|
|
||||||
json2 = JsonOutput.toJson(json2)
|
json2 = JsonOutput.toJson(json2)
|
||||||
|
|
||||||
persisted.append "$json1\n"
|
persisted.append "$json1\n"
|
||||||
persisted.append "$json2\n"
|
persisted.append "$json2\n"
|
||||||
|
|
||||||
PersisterService ps = new PersisterService(persisted, eventBus, 100, null)
|
PersisterService ps = new PersisterService(persisted, eventBus, 100, null)
|
||||||
ps.onUILoadedEvent(null)
|
ps.onUILoadedEvent(null)
|
||||||
Thread.sleep(2000)
|
Thread.sleep(2000)
|
||||||
|
|
||||||
assert listener.publishedFiles.size() == 2
|
assert listener.publishedFiles.size() == 2
|
||||||
def loadedFile1 = listener.publishedFiles[0]
|
def loadedFile1 = listener.publishedFiles[0]
|
||||||
assert loadedFile1.file == sharedFile1.getCanonicalFile()
|
assert loadedFile1.file == sharedFile1.getCanonicalFile()
|
||||||
assert loadedFile1.infoHash == ih1
|
assert loadedFile1.infoHash == ih1
|
||||||
def loadedFile2 = listener.publishedFiles[1]
|
def loadedFile2 = listener.publishedFiles[1]
|
||||||
assert loadedFile2.file == sharedFile2.getCanonicalFile()
|
assert loadedFile2.file == sharedFile2.getCanonicalFile()
|
||||||
assert loadedFile2.infoHash == ih2
|
assert loadedFile2.infoHash == ih2
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testDownloadedFile() {
|
void testDownloadedFile() {
|
||||||
writeToSharedFile(sharedFile1, 1)
|
writeToSharedFile(sharedFile1, 1)
|
||||||
FileHasher fh = new FileHasher()
|
FileHasher fh = new FileHasher()
|
||||||
InfoHash ih1 = fh.hashFile(sharedFile1)
|
InfoHash ih1 = fh.hashFile(sharedFile1)
|
||||||
|
|
||||||
File persisted = initPersisted()
|
File persisted = initPersisted()
|
||||||
|
|
||||||
Destinations dests = new Destinations()
|
Destinations dests = new Destinations()
|
||||||
def json1 = [:]
|
def json1 = [:]
|
||||||
json1.file = getSharedFileJsonName(sharedFile1)
|
json1.file = getSharedFileJsonName(sharedFile1)
|
||||||
json1.length = 1
|
json1.length = 1
|
||||||
json1.infoHash = Base64.encode(ih1.getRoot())
|
json1.infoHash = Base64.encode(ih1.getRoot())
|
||||||
json1.hashList = [Base64.encode(ih1.getHashList())]
|
json1.hashList = [Base64.encode(ih1.getHashList())]
|
||||||
json1.sources = [ dests.dest1.toBase64(), dests.dest2.toBase64()]
|
json1.sources = [ dests.dest1.toBase64(), dests.dest2.toBase64()]
|
||||||
|
|
||||||
json1 = JsonOutput.toJson(json1)
|
json1 = JsonOutput.toJson(json1)
|
||||||
persisted.write json1
|
persisted.write json1
|
||||||
|
|
||||||
PersisterService ps = new PersisterService(persisted, eventBus, 100, null)
|
PersisterService ps = new PersisterService(persisted, eventBus, 100, null)
|
||||||
ps.onUILoadedEvent(null)
|
ps.onUILoadedEvent(null)
|
||||||
Thread.sleep(2000)
|
Thread.sleep(2000)
|
||||||
|
|
||||||
assert listener.publishedFiles.size() == 1
|
assert listener.publishedFiles.size() == 1
|
||||||
def loadedFile1 = listener.publishedFiles[0]
|
def loadedFile1 = listener.publishedFiles[0]
|
||||||
assert loadedFile1 instanceof DownloadedFile
|
assert loadedFile1 instanceof DownloadedFile
|
||||||
assert loadedFile1.sources.size() == 2
|
assert loadedFile1.sources.size() == 2
|
||||||
assert loadedFile1.sources.contains(dests.dest1)
|
assert loadedFile1.sources.contains(dests.dest1)
|
||||||
assert loadedFile1.sources.contains(dests.dest2)
|
assert loadedFile1.sources.contains(dests.dest2)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -18,79 +18,79 @@ import net.i2p.data.Base64
|
|||||||
|
|
||||||
class PersisterServiceSavingTest {
|
class PersisterServiceSavingTest {
|
||||||
|
|
||||||
File f
|
File f
|
||||||
FileHasher fh = new FileHasher()
|
FileHasher fh = new FileHasher()
|
||||||
InfoHash ih
|
InfoHash ih
|
||||||
SharedFile sf
|
SharedFile sf
|
||||||
def fileSource
|
def fileSource
|
||||||
EventBus eventBus = new EventBus()
|
EventBus eventBus = new EventBus()
|
||||||
File persisted
|
File persisted
|
||||||
PersisterService ps
|
PersisterService ps
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
void before() {
|
void before() {
|
||||||
f = new File("build.gradle")
|
f = new File("build.gradle")
|
||||||
f = f.getCanonicalFile()
|
f = f.getCanonicalFile()
|
||||||
ih = fh.hashFile(f)
|
ih = fh.hashFile(f)
|
||||||
fileSource = new FileManager(eventBus, new MuWireSettings()) {
|
fileSource = new FileManager(eventBus, new MuWireSettings()) {
|
||||||
Map<File, SharedFile> getSharedFiles() {
|
Map<File, SharedFile> getSharedFiles() {
|
||||||
Map<File, SharedFile> rv = new HashMap<>()
|
Map<File, SharedFile> rv = new HashMap<>()
|
||||||
rv.put(f, sf)
|
rv.put(f, sf)
|
||||||
rv
|
rv
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
persisted = new File("persisted")
|
persisted = new File("persisted")
|
||||||
persisted.delete()
|
persisted.delete()
|
||||||
persisted.deleteOnExit()
|
persisted.deleteOnExit()
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
void after() {
|
void after() {
|
||||||
ps?.stop()
|
ps?.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String fromB64(String text) {
|
private static String fromB64(String text) {
|
||||||
DataUtil.readi18nString(Base64.decode(text))
|
DataUtil.readi18nString(Base64.decode(text))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSavingSharedFile() {
|
void testSavingSharedFile() {
|
||||||
sf = new SharedFile(f, ih, 0)
|
sf = new SharedFile(f, ih, 0)
|
||||||
|
|
||||||
ps = new PersisterService(persisted, eventBus, 100, fileSource)
|
ps = new PersisterService(persisted, eventBus, 100, fileSource)
|
||||||
ps.onUILoadedEvent(null)
|
ps.onUILoadedEvent(null)
|
||||||
Thread.sleep(1500)
|
Thread.sleep(1500)
|
||||||
|
|
||||||
JsonSlurper jsonSlurper = new JsonSlurper()
|
JsonSlurper jsonSlurper = new JsonSlurper()
|
||||||
persisted.eachLine {
|
persisted.eachLine {
|
||||||
def json = jsonSlurper.parseText(it)
|
def json = jsonSlurper.parseText(it)
|
||||||
assert fromB64(json.file) == f.toString()
|
assert fromB64(json.file) == f.toString()
|
||||||
assert json.length == f.length()
|
assert json.length == f.length()
|
||||||
assert json.infoHash == Base64.encode(ih.getRoot())
|
assert json.infoHash == Base64.encode(ih.getRoot())
|
||||||
assert json.hashList == [Base64.encode(ih.getHashList())]
|
assert json.hashList == [Base64.encode(ih.getHashList())]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSavingDownloadedFile() {
|
void testSavingDownloadedFile() {
|
||||||
Destinations dests = new Destinations()
|
Destinations dests = new Destinations()
|
||||||
sf = new DownloadedFile(f, ih, 0, new HashSet([dests.dest1, dests.dest2]))
|
sf = new DownloadedFile(f, ih, 0, new HashSet([dests.dest1, dests.dest2]))
|
||||||
|
|
||||||
ps = new PersisterService(persisted, eventBus, 100, fileSource)
|
ps = new PersisterService(persisted, eventBus, 100, fileSource)
|
||||||
ps.onUILoadedEvent(null)
|
ps.onUILoadedEvent(null)
|
||||||
Thread.sleep(1500)
|
Thread.sleep(1500)
|
||||||
|
|
||||||
JsonSlurper jsonSlurper = new JsonSlurper()
|
JsonSlurper jsonSlurper = new JsonSlurper()
|
||||||
persisted.eachLine {
|
persisted.eachLine {
|
||||||
def json = jsonSlurper.parseText(it)
|
def json = jsonSlurper.parseText(it)
|
||||||
assert fromB64(json.file) == f.toString()
|
assert fromB64(json.file) == f.toString()
|
||||||
assert json.length == f.length()
|
assert json.length == f.length()
|
||||||
assert json.infoHash == Base64.encode(ih.getRoot())
|
assert json.infoHash == Base64.encode(ih.getRoot())
|
||||||
assert json.hashList == [Base64.encode(ih.getHashList())]
|
assert json.hashList == [Base64.encode(ih.getHashList())]
|
||||||
assert json.sources.size() == 2
|
assert json.sources.size() == 2
|
||||||
assert json.sources.contains(dests.dest1.toBase64())
|
assert json.sources.contains(dests.dest1.toBase64())
|
||||||
assert json.sources.contains(dests.dest2.toBase64())
|
assert json.sources.contains(dests.dest2.toBase64())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -21,250 +21,250 @@ import net.i2p.data.Destination
|
|||||||
class HostCacheTest {
|
class HostCacheTest {
|
||||||
|
|
||||||
|
|
||||||
File persist
|
File persist
|
||||||
HostCache cache
|
HostCache cache
|
||||||
|
|
||||||
def trustMock
|
def trustMock
|
||||||
TrustService trust
|
TrustService trust
|
||||||
|
|
||||||
def settingsMock
|
def settingsMock
|
||||||
MuWireSettings settings
|
MuWireSettings settings
|
||||||
|
|
||||||
Destinations destinations = new Destinations()
|
Destinations destinations = new Destinations()
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
void before() {
|
void before() {
|
||||||
persist = new File("hostPersist")
|
persist = new File("hostPersist")
|
||||||
persist.delete()
|
persist.delete()
|
||||||
persist.deleteOnExit()
|
persist.deleteOnExit()
|
||||||
|
|
||||||
trustMock = new MockFor(TrustService.class)
|
trustMock = new MockFor(TrustService.class)
|
||||||
settingsMock = new MockFor(MuWireSettings.class)
|
settingsMock = new MockFor(MuWireSettings.class)
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
void after() {
|
void after() {
|
||||||
cache?.stop()
|
cache?.stop()
|
||||||
trustMock.verify trust
|
trustMock.verify trust
|
||||||
settingsMock.verify settings
|
settingsMock.verify settings
|
||||||
Thread.sleep(150)
|
Thread.sleep(150)
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initMocks() {
|
private void initMocks() {
|
||||||
trust = trustMock.proxyInstance()
|
trust = trustMock.proxyInstance()
|
||||||
settings = settingsMock.proxyInstance()
|
settings = settingsMock.proxyInstance()
|
||||||
cache = new HostCache(trust, persist, 100, settings, new Destination())
|
cache = new HostCache(trust, persist, 100, settings, new Destination())
|
||||||
cache.start()
|
cache.start()
|
||||||
Thread.sleep(150)
|
Thread.sleep(150)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testEmpty() {
|
void testEmpty() {
|
||||||
initMocks()
|
initMocks()
|
||||||
assert cache.getHosts(5).size() == 0
|
assert cache.getHosts(5).size() == 0
|
||||||
assert cache.getGoodHosts(5).size() == 0
|
assert cache.getGoodHosts(5).size() == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testOnDiscoveredEvent() {
|
void testOnDiscoveredEvent() {
|
||||||
trustMock.ignore.getLevel { d ->
|
trustMock.ignore.getLevel { d ->
|
||||||
assert d == destinations.dest1
|
assert d == destinations.dest1
|
||||||
TrustLevel.NEUTRAL
|
TrustLevel.NEUTRAL
|
||||||
}
|
}
|
||||||
settingsMock.ignore.allowUntrusted { true }
|
settingsMock.ignore.allowUntrusted { true }
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||||
|
|
||||||
def rv = cache.getHosts(5)
|
def rv = cache.getHosts(5)
|
||||||
assert rv.size() == 1
|
assert rv.size() == 1
|
||||||
assert rv.contains(destinations.dest1)
|
assert rv.contains(destinations.dest1)
|
||||||
|
|
||||||
assert cache.getGoodHosts(5).size() == 0
|
assert cache.getGoodHosts(5).size() == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testOnDiscoveredUntrustedHost() {
|
void testOnDiscoveredUntrustedHost() {
|
||||||
trustMock.demand.getLevel { d ->
|
trustMock.demand.getLevel { d ->
|
||||||
assert d == destinations.dest1
|
assert d == destinations.dest1
|
||||||
TrustLevel.DISTRUSTED
|
TrustLevel.DISTRUSTED
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||||
assert cache.getHosts(5).size() == 0
|
assert cache.getHosts(5).size() == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testOnDiscoverNeutralHostsProhibited() {
|
void testOnDiscoverNeutralHostsProhibited() {
|
||||||
trustMock.ignore.getLevel { d ->
|
trustMock.ignore.getLevel { d ->
|
||||||
assert d == destinations.dest1
|
assert d == destinations.dest1
|
||||||
TrustLevel.NEUTRAL
|
TrustLevel.NEUTRAL
|
||||||
}
|
}
|
||||||
settingsMock.ignore.allowUntrusted { false }
|
settingsMock.ignore.allowUntrusted { false }
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||||
assert cache.getHosts(5).size() == 0
|
assert cache.getHosts(5).size() == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void test2DiscoveredGoodHosts() {
|
void test2DiscoveredGoodHosts() {
|
||||||
trustMock.demand.getLevel { d ->
|
trustMock.demand.getLevel { d ->
|
||||||
assert d == destinations.dest1
|
assert d == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
trustMock.demand.getLevel { d ->
|
trustMock.demand.getLevel { d ->
|
||||||
assert d == destinations.dest2
|
assert d == destinations.dest2
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
trustMock.demand.getLevel{ d -> TrustLevel.TRUSTED }
|
trustMock.demand.getLevel{ d -> TrustLevel.TRUSTED }
|
||||||
trustMock.demand.getLevel{ d -> TrustLevel.TRUSTED }
|
trustMock.demand.getLevel{ d -> TrustLevel.TRUSTED }
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest2))
|
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest2))
|
||||||
|
|
||||||
def rv = cache.getHosts(1)
|
def rv = cache.getHosts(1)
|
||||||
assert rv.size() == 1
|
assert rv.size() == 1
|
||||||
assert rv.contains(destinations.dest1) || rv.contains(destinations.dest2)
|
assert rv.contains(destinations.dest1) || rv.contains(destinations.dest2)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testHostFailed() {
|
void testHostFailed() {
|
||||||
trustMock.demand.getLevel { d ->
|
trustMock.demand.getLevel { d ->
|
||||||
assert d == destinations.dest1
|
assert d == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||||
|
|
||||||
def endpoint = new Endpoint(destinations.dest1, null, null, null)
|
def endpoint = new Endpoint(destinations.dest1, null, null, null)
|
||||||
cache.onConnectionEvent( new ConnectionEvent(endpoint: endpoint, status: ConnectionAttemptStatus.FAILED))
|
cache.onConnectionEvent( new ConnectionEvent(endpoint: endpoint, status: ConnectionAttemptStatus.FAILED))
|
||||||
cache.onConnectionEvent( new ConnectionEvent(endpoint: endpoint, status: ConnectionAttemptStatus.FAILED))
|
cache.onConnectionEvent( new ConnectionEvent(endpoint: endpoint, status: ConnectionAttemptStatus.FAILED))
|
||||||
cache.onConnectionEvent( new ConnectionEvent(endpoint: endpoint, status: ConnectionAttemptStatus.FAILED))
|
cache.onConnectionEvent( new ConnectionEvent(endpoint: endpoint, status: ConnectionAttemptStatus.FAILED))
|
||||||
|
|
||||||
assert cache.getHosts(5).size() == 0
|
assert cache.getHosts(5).size() == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testFailedHostSuceeds() {
|
void testFailedHostSuceeds() {
|
||||||
trustMock.ignore.getLevel { d ->
|
trustMock.ignore.getLevel { d ->
|
||||||
assert d == destinations.dest1
|
assert d == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||||
|
|
||||||
def endpoint = new Endpoint(destinations.dest1, null, null, null)
|
def endpoint = new Endpoint(destinations.dest1, null, null, null)
|
||||||
cache.onConnectionEvent( new ConnectionEvent(endpoint: endpoint, status: ConnectionAttemptStatus.FAILED))
|
cache.onConnectionEvent( new ConnectionEvent(endpoint: endpoint, status: ConnectionAttemptStatus.FAILED))
|
||||||
cache.onConnectionEvent( new ConnectionEvent(endpoint: endpoint, status: ConnectionAttemptStatus.FAILED))
|
cache.onConnectionEvent( new ConnectionEvent(endpoint: endpoint, status: ConnectionAttemptStatus.FAILED))
|
||||||
cache.onConnectionEvent( new ConnectionEvent(endpoint: endpoint, status: ConnectionAttemptStatus.FAILED))
|
cache.onConnectionEvent( new ConnectionEvent(endpoint: endpoint, status: ConnectionAttemptStatus.FAILED))
|
||||||
cache.onConnectionEvent( new ConnectionEvent(endpoint: endpoint, status: ConnectionAttemptStatus.SUCCESSFUL))
|
cache.onConnectionEvent( new ConnectionEvent(endpoint: endpoint, status: ConnectionAttemptStatus.SUCCESSFUL))
|
||||||
|
|
||||||
def rv = cache.getHosts(5)
|
def rv = cache.getHosts(5)
|
||||||
assert rv.size() == 1
|
assert rv.size() == 1
|
||||||
assert rv.contains(destinations.dest1)
|
assert rv.contains(destinations.dest1)
|
||||||
|
|
||||||
rv = cache.getGoodHosts(5)
|
rv = cache.getGoodHosts(5)
|
||||||
assert rv.size() == 1
|
assert rv.size() == 1
|
||||||
assert rv.contains(destinations.dest1)
|
assert rv.contains(destinations.dest1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testFailedOnceNoLongerGood() {
|
void testFailedOnceNoLongerGood() {
|
||||||
trustMock.ignore.getLevel { d ->
|
trustMock.ignore.getLevel { d ->
|
||||||
assert d == destinations.dest1
|
assert d == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||||
|
|
||||||
def endpoint = new Endpoint(destinations.dest1, null, null, null)
|
def endpoint = new Endpoint(destinations.dest1, null, null, null)
|
||||||
cache.onConnectionEvent( new ConnectionEvent(endpoint: endpoint, status: ConnectionAttemptStatus.SUCCESSFUL))
|
cache.onConnectionEvent( new ConnectionEvent(endpoint: endpoint, status: ConnectionAttemptStatus.SUCCESSFUL))
|
||||||
|
|
||||||
def rv = cache.getHosts(5)
|
def rv = cache.getHosts(5)
|
||||||
def rv2 = cache.getGoodHosts(5)
|
def rv2 = cache.getGoodHosts(5)
|
||||||
assert rv.size() == 1
|
assert rv.size() == 1
|
||||||
assert rv.contains(destinations.dest1)
|
assert rv.contains(destinations.dest1)
|
||||||
assert rv == rv2
|
assert rv == rv2
|
||||||
|
|
||||||
cache.onConnectionEvent( new ConnectionEvent(endpoint: endpoint, status: ConnectionAttemptStatus.FAILED))
|
cache.onConnectionEvent( new ConnectionEvent(endpoint: endpoint, status: ConnectionAttemptStatus.FAILED))
|
||||||
|
|
||||||
rv = cache.getHosts(5)
|
rv = cache.getHosts(5)
|
||||||
assert rv.size() == 1
|
assert rv.size() == 1
|
||||||
assert rv.contains(destinations.dest1)
|
assert rv.contains(destinations.dest1)
|
||||||
assert cache.getGoodHosts(5).size() == 0
|
assert cache.getGoodHosts(5).size() == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testDuplicateHostDiscovered() {
|
void testDuplicateHostDiscovered() {
|
||||||
trustMock.demand.getLevel { d ->
|
trustMock.demand.getLevel { d ->
|
||||||
assert d == destinations.dest1
|
assert d == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
trustMock.demand.getLevel { d ->
|
trustMock.demand.getLevel { d ->
|
||||||
assert d == destinations.dest1
|
assert d == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||||
|
|
||||||
def rv = cache.getHosts(5)
|
def rv = cache.getHosts(5)
|
||||||
assert rv.size() == 1
|
assert rv.size() == 1
|
||||||
assert rv.contains(destinations.dest1)
|
assert rv.contains(destinations.dest1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSaving() {
|
void testSaving() {
|
||||||
trustMock.ignore.getLevel { d ->
|
trustMock.ignore.getLevel { d ->
|
||||||
assert d == destinations.dest1
|
assert d == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
initMocks()
|
initMocks()
|
||||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||||
Thread.sleep(150)
|
Thread.sleep(150)
|
||||||
|
|
||||||
assert persist.exists()
|
assert persist.exists()
|
||||||
int lines = 0
|
int lines = 0
|
||||||
persist.eachLine {
|
persist.eachLine {
|
||||||
lines++
|
lines++
|
||||||
JsonSlurper slurper = new JsonSlurper()
|
JsonSlurper slurper = new JsonSlurper()
|
||||||
def json = slurper.parseText(it)
|
def json = slurper.parseText(it)
|
||||||
assert json.destination == destinations.dest1.toBase64()
|
assert json.destination == destinations.dest1.toBase64()
|
||||||
assert json.failures == 0
|
assert json.failures == 0
|
||||||
assert json.successes == 0
|
assert json.successes == 0
|
||||||
}
|
}
|
||||||
assert lines == 1
|
assert lines == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testLoading() {
|
void testLoading() {
|
||||||
def json = [:]
|
def json = [:]
|
||||||
json.destination = destinations.dest1.toBase64()
|
json.destination = destinations.dest1.toBase64()
|
||||||
json.failures = 0
|
json.failures = 0
|
||||||
json.successes = 1
|
json.successes = 1
|
||||||
json = JsonOutput.toJson(json)
|
json = JsonOutput.toJson(json)
|
||||||
persist.append("${json}\n")
|
persist.append("${json}\n")
|
||||||
|
|
||||||
trustMock.ignore.getLevel { d ->
|
trustMock.ignore.getLevel { d ->
|
||||||
assert d == destinations.dest1
|
assert d == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
def rv = cache.getHosts(5)
|
def rv = cache.getHosts(5)
|
||||||
assert rv.size() == 1
|
assert rv.size() == 1
|
||||||
assert rv.contains(destinations.dest1)
|
assert rv.contains(destinations.dest1)
|
||||||
|
|
||||||
assert cache.getGoodHosts(5) == rv
|
assert cache.getGoodHosts(5) == rv
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,34 +4,34 @@ import org.junit.Test
|
|||||||
|
|
||||||
class SearchIndexTest {
|
class SearchIndexTest {
|
||||||
|
|
||||||
SearchIndex index
|
SearchIndex index
|
||||||
|
|
||||||
private void initIndex(List<String> entries) {
|
private void initIndex(List<String> entries) {
|
||||||
index = new SearchIndex()
|
index = new SearchIndex()
|
||||||
entries.each {
|
entries.each {
|
||||||
index.add(it)
|
index.add(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSingleTerm() {
|
void testSingleTerm() {
|
||||||
initIndex(["a b.c", "d e.f"])
|
initIndex(["a b.c", "d e.f"])
|
||||||
|
|
||||||
def found = index.search(["a"])
|
def found = index.search(["a"])
|
||||||
assert found.size() == 1
|
assert found.size() == 1
|
||||||
assert found.contains("a b.c")
|
assert found.contains("a b.c")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSingleTermOverlap() {
|
void testSingleTermOverlap() {
|
||||||
initIndex(["a b.c", "c d.e"])
|
initIndex(["a b.c", "c d.e"])
|
||||||
|
|
||||||
def found = index.search(["c"])
|
def found = index.search(["c"])
|
||||||
assert found.size() == 2
|
assert found.size() == 2
|
||||||
assert found.contains("a b.c")
|
assert found.contains("a b.c")
|
||||||
assert found.contains("c d.e")
|
assert found.contains("c d.e")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDrillDownDoesNotModifyIndex() {
|
public void testDrillDownDoesNotModifyIndex() {
|
||||||
@@ -43,46 +43,46 @@ class SearchIndexTest {
|
|||||||
assert found.contains("c d.e")
|
assert found.contains("c d.e")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testDrillDown() {
|
void testDrillDown() {
|
||||||
initIndex(["a b.c", "c d.e"])
|
initIndex(["a b.c", "c d.e"])
|
||||||
|
|
||||||
def found = index.search(["c", "e"])
|
def found = index.search(["c", "e"])
|
||||||
assert found.size() == 1
|
assert found.size() == 1
|
||||||
assert found.contains("c d.e")
|
assert found.contains("c d.e")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testNotFound() {
|
void testNotFound() {
|
||||||
initIndex(["a b.c"])
|
initIndex(["a b.c"])
|
||||||
def found = index.search(["d"])
|
def found = index.search(["d"])
|
||||||
assert found.size() == 0
|
assert found.size() == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSomeNotFound() {
|
void testSomeNotFound() {
|
||||||
initIndex(["a b.c"])
|
initIndex(["a b.c"])
|
||||||
def found = index.search(["a","d"])
|
def found = index.search(["a","d"])
|
||||||
assert found.size() == 0
|
assert found.size() == 0
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testRemove() {
|
void testRemove() {
|
||||||
initIndex(["a b.c"])
|
initIndex(["a b.c"])
|
||||||
index.remove("a b.c")
|
index.remove("a b.c")
|
||||||
def found = index.search(["a"])
|
def found = index.search(["a"])
|
||||||
assert found.size() == 0
|
assert found.size() == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testRemoveOverlap() {
|
void testRemoveOverlap() {
|
||||||
initIndex(["a b.c", "b c.d"])
|
initIndex(["a b.c", "b c.d"])
|
||||||
index.remove("a b.c")
|
index.remove("a b.c")
|
||||||
def found = index.search(["b"])
|
def found = index.search(["b"])
|
||||||
assert found.size() == 1
|
assert found.size() == 1
|
||||||
assert found.contains("b c.d")
|
assert found.contains("b c.d")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testDuplicateTerm() {
|
void testDuplicateTerm() {
|
||||||
|
@@ -13,73 +13,73 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
class TrustServiceTest {
|
class TrustServiceTest {
|
||||||
|
|
||||||
TrustService service
|
TrustService service
|
||||||
File persistGood, persistBad
|
File persistGood, persistBad
|
||||||
Personas personas = new Personas()
|
Personas personas = new Personas()
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
void before() {
|
void before() {
|
||||||
persistGood = new File("good.trust")
|
persistGood = new File("good.trust")
|
||||||
persistBad = new File("bad.trust")
|
persistBad = new File("bad.trust")
|
||||||
persistGood.delete()
|
persistGood.delete()
|
||||||
persistBad.delete()
|
persistBad.delete()
|
||||||
persistGood.deleteOnExit()
|
persistGood.deleteOnExit()
|
||||||
persistBad.deleteOnExit()
|
persistBad.deleteOnExit()
|
||||||
service = new TrustService(persistGood, persistBad, 100)
|
service = new TrustService(persistGood, persistBad, 100)
|
||||||
service.start()
|
service.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
void after() {
|
void after() {
|
||||||
service.stop()
|
service.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testEmpty() {
|
void testEmpty() {
|
||||||
assert TrustLevel.NEUTRAL == service.getLevel(personas.persona1.destination)
|
assert TrustLevel.NEUTRAL == service.getLevel(personas.persona1.destination)
|
||||||
assert TrustLevel.NEUTRAL == service.getLevel(personas.persona2.destination)
|
assert TrustLevel.NEUTRAL == service.getLevel(personas.persona2.destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testOnEvent() {
|
void testOnEvent() {
|
||||||
service.onTrustEvent new TrustEvent(level: TrustLevel.TRUSTED, persona: personas.persona1)
|
service.onTrustEvent new TrustEvent(level: TrustLevel.TRUSTED, persona: personas.persona1)
|
||||||
service.onTrustEvent new TrustEvent(level: TrustLevel.DISTRUSTED, persona: personas.persona2)
|
service.onTrustEvent new TrustEvent(level: TrustLevel.DISTRUSTED, persona: personas.persona2)
|
||||||
|
|
||||||
assert TrustLevel.TRUSTED == service.getLevel(personas.persona1.destination)
|
assert TrustLevel.TRUSTED == service.getLevel(personas.persona1.destination)
|
||||||
assert TrustLevel.DISTRUSTED == service.getLevel(personas.persona2.destination)
|
assert TrustLevel.DISTRUSTED == service.getLevel(personas.persona2.destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testPersist() {
|
void testPersist() {
|
||||||
service.onTrustEvent new TrustEvent(level: TrustLevel.TRUSTED, persona: personas.persona1)
|
service.onTrustEvent new TrustEvent(level: TrustLevel.TRUSTED, persona: personas.persona1)
|
||||||
service.onTrustEvent new TrustEvent(level: TrustLevel.DISTRUSTED, persona: personas.persona2)
|
service.onTrustEvent new TrustEvent(level: TrustLevel.DISTRUSTED, persona: personas.persona2)
|
||||||
|
|
||||||
Thread.sleep(250)
|
Thread.sleep(250)
|
||||||
def trusted = new HashSet<>()
|
def trusted = new HashSet<>()
|
||||||
persistGood.eachLine {
|
persistGood.eachLine {
|
||||||
trusted.add(new Persona(new ByteArrayInputStream(Base64.decode(it))))
|
trusted.add(new Persona(new ByteArrayInputStream(Base64.decode(it))))
|
||||||
}
|
}
|
||||||
def distrusted = new HashSet<>()
|
def distrusted = new HashSet<>()
|
||||||
persistBad.eachLine {
|
persistBad.eachLine {
|
||||||
distrusted.add(new Persona(new ByteArrayInputStream(Base64.decode(it))))
|
distrusted.add(new Persona(new ByteArrayInputStream(Base64.decode(it))))
|
||||||
}
|
}
|
||||||
|
|
||||||
assert trusted.size() == 1
|
assert trusted.size() == 1
|
||||||
assert trusted.contains(personas.persona1)
|
assert trusted.contains(personas.persona1)
|
||||||
assert distrusted.size() == 1
|
assert distrusted.size() == 1
|
||||||
assert distrusted.contains(personas.persona2)
|
assert distrusted.contains(personas.persona2)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testLoad() {
|
void testLoad() {
|
||||||
service.stop()
|
service.stop()
|
||||||
persistGood.append("${personas.persona1.toBase64()}\n")
|
persistGood.append("${personas.persona1.toBase64()}\n")
|
||||||
persistBad.append("${personas.persona2.toBase64()}\n")
|
persistBad.append("${personas.persona2.toBase64()}\n")
|
||||||
service = new TrustService(persistGood, persistBad, 100)
|
service = new TrustService(persistGood, persistBad, 100)
|
||||||
service.start()
|
service.start()
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
|
|
||||||
assert TrustLevel.TRUSTED == service.getLevel(personas.persona1.destination)
|
assert TrustLevel.TRUSTED == service.getLevel(personas.persona1.destination)
|
||||||
assert TrustLevel.DISTRUSTED == service.getLevel(personas.persona2.destination)
|
assert TrustLevel.DISTRUSTED == service.getLevel(personas.persona2.destination)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,41 +7,41 @@ import org.junit.Test
|
|||||||
class DataUtilTest {
|
class DataUtilTest {
|
||||||
|
|
||||||
|
|
||||||
private static void usVal(int value) {
|
private static void usVal(int value) {
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
||||||
DataUtil.writeUnsignedShort(value, baos)
|
DataUtil.writeUnsignedShort(value, baos)
|
||||||
def is = new DataInputStream(new ByteArrayInputStream(baos.toByteArray()))
|
def is = new DataInputStream(new ByteArrayInputStream(baos.toByteArray()))
|
||||||
assert is.readUnsignedShort() == value
|
assert is.readUnsignedShort() == value
|
||||||
}
|
}
|
||||||
@Test
|
@Test
|
||||||
void testUnsignedShort() {
|
void testUnsignedShort() {
|
||||||
usVal(0)
|
usVal(0)
|
||||||
usVal(20)
|
usVal(20)
|
||||||
usVal(Short.MAX_VALUE)
|
usVal(Short.MAX_VALUE)
|
||||||
usVal(Short.MAX_VALUE + 1)
|
usVal(Short.MAX_VALUE + 1)
|
||||||
usVal(0xFFFF)
|
usVal(0xFFFF)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
usVal(0xFFFF + 1)
|
usVal(0xFFFF + 1)
|
||||||
fail()
|
fail()
|
||||||
} catch (IllegalArgumentException expected) {}
|
} catch (IllegalArgumentException expected) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static header(int value) {
|
private static header(int value) {
|
||||||
byte [] header = new byte[3]
|
byte [] header = new byte[3]
|
||||||
DataUtil.packHeader(value, header)
|
DataUtil.packHeader(value, header)
|
||||||
assert value == DataUtil.readLength(header)
|
assert value == DataUtil.readLength(header)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testHeader() {
|
void testHeader() {
|
||||||
header(0)
|
header(0)
|
||||||
header(1)
|
header(1)
|
||||||
header(556)
|
header(556)
|
||||||
header(8 * 1024 * 1024 - 1)
|
header(8 * 1024 * 1024 - 1)
|
||||||
try {
|
try {
|
||||||
header(8 * 1024 * 1024)
|
header(8 * 1024 * 1024)
|
||||||
fail()
|
fail()
|
||||||
} catch (IllegalArgumentException expected) {}
|
} catch (IllegalArgumentException expected) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
group = com.muwire
|
group = com.muwire
|
||||||
version = 0.3.6
|
version = 0.4.8
|
||||||
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
|
||||||
|
34
gradlew
vendored
34
gradlew
vendored
@@ -11,21 +11,21 @@
|
|||||||
PRG="$0"
|
PRG="$0"
|
||||||
# Need this for relative symlinks.
|
# Need this for relative symlinks.
|
||||||
while [ -h "$PRG" ] ; do
|
while [ -h "$PRG" ] ; do
|
||||||
ls=`ls -ld "$PRG"`
|
ls=$(ls -ld "$PRG")
|
||||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
link=$(expr "$ls" : '.*-> \(.*\)$')
|
||||||
if expr "$link" : '/.*' > /dev/null; then
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
PRG="$link"
|
PRG="$link"
|
||||||
else
|
else
|
||||||
PRG=`dirname "$PRG"`"/$link"
|
PRG=$(dirname "$PRG")"/$link"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
SAVED="`pwd`"
|
SAVED="$(pwd)"
|
||||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
cd "$(dirname "$PRG")/" >/dev/null
|
||||||
APP_HOME="`pwd -P`"
|
APP_HOME="$(pwd -P)"
|
||||||
cd "$SAVED" >/dev/null
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
APP_NAME="Gradle"
|
APP_NAME="Gradle"
|
||||||
APP_BASE_NAME=`basename "$0"`
|
APP_BASE_NAME=$(basename "$0")
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
DEFAULT_JVM_OPTS=""
|
DEFAULT_JVM_OPTS=""
|
||||||
@@ -49,7 +49,7 @@ cygwin=false
|
|||||||
msys=false
|
msys=false
|
||||||
darwin=false
|
darwin=false
|
||||||
nonstop=false
|
nonstop=false
|
||||||
case "`uname`" in
|
case "$(uname)" in
|
||||||
CYGWIN* )
|
CYGWIN* )
|
||||||
cygwin=true
|
cygwin=true
|
||||||
;;
|
;;
|
||||||
@@ -90,7 +90,7 @@ fi
|
|||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
MAX_FD_LIMIT=`ulimit -H -n`
|
MAX_FD_LIMIT=$(ulimit -H -n)
|
||||||
if [ $? -eq 0 ] ; then
|
if [ $? -eq 0 ] ; then
|
||||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
MAX_FD="$MAX_FD_LIMIT"
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
@@ -111,12 +111,12 @@ fi
|
|||||||
|
|
||||||
# For Cygwin, switch paths to Windows format before running java
|
# For Cygwin, switch paths to Windows format before running java
|
||||||
if $cygwin ; then
|
if $cygwin ; then
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
APP_HOME=$(cygpath --path --mixed "$APP_HOME")
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
CLASSPATH=$(cygpath --path --mixed "$CLASSPATH")
|
||||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
JAVACMD=$(cygpath --unix "$JAVACMD")
|
||||||
|
|
||||||
# We build the pattern for arguments to be converted via cygpath
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
ROOTDIRSRAW=$(find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null)
|
||||||
SEP=""
|
SEP=""
|
||||||
for dir in $ROOTDIRSRAW ; do
|
for dir in $ROOTDIRSRAW ; do
|
||||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
@@ -130,13 +130,13 @@ if $cygwin ; then
|
|||||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
i=0
|
i=0
|
||||||
for arg in "$@" ; do
|
for arg in "$@" ; do
|
||||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
CHECK=$(echo "$arg"|egrep -c "$OURCYGPATTERN" -)
|
||||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
CHECK2=$(echo "$arg"|egrep -c "^-") ### Determine if an option
|
||||||
|
|
||||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
eval $(echo args$i)=$(cygpath --path --ignore --mixed "$arg")
|
||||||
else
|
else
|
||||||
eval `echo args$i`="\"$arg\""
|
eval $(echo args$i)="\"$arg\""
|
||||||
fi
|
fi
|
||||||
i=$((i+1))
|
i=$((i+1))
|
||||||
done
|
done
|
||||||
|
@@ -26,4 +26,24 @@ mvcGroups {
|
|||||||
view = 'com.muwire.gui.OptionsView'
|
view = 'com.muwire.gui.OptionsView'
|
||||||
controller = 'com.muwire.gui.OptionsController'
|
controller = 'com.muwire.gui.OptionsController'
|
||||||
}
|
}
|
||||||
|
"mu-wire-status" {
|
||||||
|
model = 'com.muwire.gui.MuWireStatusModel'
|
||||||
|
view = 'com.muwire.gui.MuWireStatusView'
|
||||||
|
controller = 'com.muwire.gui.MuWireStatusController'
|
||||||
|
}
|
||||||
|
'i-2-p-status' {
|
||||||
|
model = 'com.muwire.gui.I2PStatusModel'
|
||||||
|
view = 'com.muwire.gui.I2PStatusView'
|
||||||
|
controller = 'com.muwire.gui.I2PStatusController'
|
||||||
|
}
|
||||||
|
'trust-list' {
|
||||||
|
model = 'com.muwire.gui.TrustListModel'
|
||||||
|
view = 'com.muwire.gui.TrustListView'
|
||||||
|
controller = 'com.muwire.gui.TrustListController'
|
||||||
|
}
|
||||||
|
'content-panel' {
|
||||||
|
model = 'com.muwire.gui.ContentPanelModel'
|
||||||
|
view = 'com.muwire.gui.ContentPanelView'
|
||||||
|
controller = 'com.muwire.gui.ContentPanelController'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,95 @@
|
|||||||
|
package com.muwire.gui
|
||||||
|
|
||||||
|
import griffon.core.artifact.GriffonController
|
||||||
|
import griffon.core.controller.ControllerAction
|
||||||
|
import griffon.inject.MVCMember
|
||||||
|
import griffon.metadata.ArtifactProviderFor
|
||||||
|
import javax.annotation.Nonnull
|
||||||
|
|
||||||
|
import com.muwire.core.Core
|
||||||
|
import com.muwire.core.EventBus
|
||||||
|
import com.muwire.core.content.ContentControlEvent
|
||||||
|
import com.muwire.core.content.Match
|
||||||
|
import com.muwire.core.content.Matcher
|
||||||
|
import com.muwire.core.content.RegexMatcher
|
||||||
|
import com.muwire.core.trust.TrustEvent
|
||||||
|
import com.muwire.core.trust.TrustLevel
|
||||||
|
|
||||||
|
@ArtifactProviderFor(GriffonController)
|
||||||
|
class ContentPanelController {
|
||||||
|
@MVCMember @Nonnull
|
||||||
|
ContentPanelModel model
|
||||||
|
@MVCMember @Nonnull
|
||||||
|
ContentPanelView view
|
||||||
|
|
||||||
|
Core core
|
||||||
|
|
||||||
|
@ControllerAction
|
||||||
|
void addRule() {
|
||||||
|
def term = view.ruleTextField.text
|
||||||
|
|
||||||
|
if (model.regex)
|
||||||
|
core.muOptions.watchedRegexes.add(term)
|
||||||
|
else
|
||||||
|
core.muOptions.watchedKeywords.add(term)
|
||||||
|
saveMuWireSettings()
|
||||||
|
|
||||||
|
core.eventBus.publish(new ContentControlEvent(term : term, regex : model.regex, add:true))
|
||||||
|
}
|
||||||
|
|
||||||
|
@ControllerAction
|
||||||
|
void deleteRule() {
|
||||||
|
int rule = view.getSelectedRule()
|
||||||
|
if (rule < 0)
|
||||||
|
return
|
||||||
|
Matcher matcher = model.rules[rule]
|
||||||
|
String term = matcher.getTerm()
|
||||||
|
if (matcher instanceof RegexMatcher)
|
||||||
|
core.muOptions.watchedRegexes.remove(term)
|
||||||
|
else
|
||||||
|
core.muOptions.watchedKeywords.remove(term)
|
||||||
|
saveMuWireSettings()
|
||||||
|
|
||||||
|
core.eventBus.publish(new ContentControlEvent(term : term, regex : (matcher instanceof RegexMatcher), add: false))
|
||||||
|
}
|
||||||
|
|
||||||
|
@ControllerAction
|
||||||
|
void keyword() {
|
||||||
|
model.regex = false
|
||||||
|
}
|
||||||
|
|
||||||
|
@ControllerAction
|
||||||
|
void regex() {
|
||||||
|
model.regex = true
|
||||||
|
}
|
||||||
|
|
||||||
|
@ControllerAction
|
||||||
|
void refresh() {
|
||||||
|
model.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
@ControllerAction
|
||||||
|
void trust() {
|
||||||
|
int selectedHit = view.getSelectedHit()
|
||||||
|
if (selectedHit < 0)
|
||||||
|
return
|
||||||
|
Match m = model.hits[selectedHit]
|
||||||
|
core.eventBus.publish(new TrustEvent(persona : m.persona, level : TrustLevel.TRUSTED))
|
||||||
|
}
|
||||||
|
|
||||||
|
@ControllerAction
|
||||||
|
void distrust() {
|
||||||
|
int selectedHit = view.getSelectedHit()
|
||||||
|
if (selectedHit < 0)
|
||||||
|
return
|
||||||
|
Match m = model.hits[selectedHit]
|
||||||
|
core.eventBus.publish(new TrustEvent(persona : m.persona, level : TrustLevel.DISTRUSTED))
|
||||||
|
}
|
||||||
|
|
||||||
|
void saveMuWireSettings() {
|
||||||
|
File f = new File(core.home, "MuWire.properties")
|
||||||
|
f.withOutputStream {
|
||||||
|
core.muOptions.write(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user