Compare commits
1 Commits
muwire-0.5
...
fix-possib
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7718dc0821 |
28
README.md
28
README.md
@@ -4,38 +4,30 @@ MuWire is an easy to use file-sharing program which offers anonymity using [I2P
|
|||||||
|
|
||||||
It is inspired by the LimeWire Gnutella client and developped by a former LimeWire developer.
|
It is inspired by the LimeWire Gnutella client and developped by a former LimeWire developer.
|
||||||
|
|
||||||
The current stable release - 0.4.15 is avaiable for download at https://muwire.com. You can find technical documentation in the "doc" folder.
|
The current stable release - 0.1.5 is avaiable for download at http://muwire.com. You can find technical documentation in the "doc" folder.
|
||||||
|
|
||||||
### Building
|
### Building
|
||||||
|
|
||||||
You need JDK 8 or newer. After installing that and setting up the appropriate paths, just type
|
You need JRE 8 or newer. After installing that and setting up the appropriate paths, just type
|
||||||
|
|
||||||
```
|
```
|
||||||
./gradlew clean assemble
|
./gradlew assemble
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want to run the unit tests, type
|
If you want to run the unit tests, type
|
||||||
```
|
```
|
||||||
./gradlew clean build
|
./gradlew build
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want to build binary bundles that do not depend on Java or I2P, see the https://github.com/zlatinb/muwire-pkg project
|
Some of the UI tests will fail because they haven't been written yet :-/
|
||||||
|
|
||||||
### Running
|
### Running
|
||||||
|
|
||||||
After you build the application, look inside `gui/build/distributions`. Untar/unzip one of the `shadow` files and then run the jar contained inside by typing `java -jar gui-x.y.z.jar` in a terminal or command prompt.
|
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.post=<port>" in there.
|
||||||
|
|
||||||
If you have an I2P router running on the same machine that is all you need to do. If you use a custom I2CP host and port, create a file `i2p.properties` and put `i2cp.tcp.host=<host>` and `i2cp.tcp.port=<port>` in there. On Windows that file should go into `%HOME%\AppData\Roaming\MuWire`, on Mac into `$HOME/Library/Application Support/MuWire` and on Linux `$HOME/.MuWire`
|
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.
|
||||||
|
|
||||||
[Default I2CP port]\: `7654`
|
|
||||||
|
|
||||||
### GPG Fingerprint
|
|
||||||
|
|
||||||
```
|
|
||||||
471B 9FD4 5517 A5ED 101F C57D A728 3207 2D52 5E41
|
|
||||||
```
|
|
||||||
|
|
||||||
You can find the full key at https://keybase.io/zlatinb
|
|
||||||
|
|
||||||
|
|
||||||
[Default I2CP port]: https://geti2p.net/en/docs/ports
|
### Known bugs and limitations
|
||||||
|
|
||||||
|
* Many UI features you would expect are not there yet
|
||||||
|
26
TODO.md
26
TODO.md
@@ -4,6 +4,10 @@ Not in any particular order yet
|
|||||||
|
|
||||||
### Big Items
|
### Big Items
|
||||||
|
|
||||||
|
##### Alternate Locations
|
||||||
|
|
||||||
|
This helps peers discover new sources for a file while the download is in progress. Also makes sharing of partial files possible.
|
||||||
|
|
||||||
##### Bloom Filters
|
##### Bloom Filters
|
||||||
|
|
||||||
This reduces query traffic by not sending last hop queries to peers that definitely do not have the file
|
This reduces query traffic by not sending last hop queries to peers that definitely do not have the file
|
||||||
@@ -12,15 +16,27 @@ 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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
##### Metadata editing and search
|
|
||||||
|
|
||||||
To enable parsing of metadata from known file types and the user editing it or adding manual metadata
|
|
||||||
|
|
||||||
### Small Items
|
### Small Items
|
||||||
|
|
||||||
|
* Detect if router is dead and show warning or exit
|
||||||
* Wrapper of some kind for in-place upgrades
|
* Wrapper of some kind for in-place upgrades
|
||||||
* Automatic adjustment of number of I2P tunnels
|
* Download file sequentially
|
||||||
|
* Unsharing of files
|
||||||
|
* Multiple-selection download, Ctrl-A
|
||||||
|
* Automatic sharing of new files in shared directories (more like medium item)
|
||||||
|
@@ -2,7 +2,7 @@ subprojects {
|
|||||||
apply plugin: 'groovy'
|
apply plugin: 'groovy'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile "net.i2p:i2p:${i2pVersion}"
|
compile 'net.i2p:i2p:0.9.40'
|
||||||
compile 'org.codehaus.groovy:groovy-all:2.4.15'
|
compile 'org.codehaus.groovy:groovy-all:2.4.15'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -4,7 +4,6 @@ import java.util.concurrent.CountDownLatch
|
|||||||
|
|
||||||
import com.muwire.core.Core
|
import com.muwire.core.Core
|
||||||
import com.muwire.core.MuWireSettings
|
import com.muwire.core.MuWireSettings
|
||||||
import com.muwire.core.UILoadedEvent
|
|
||||||
import com.muwire.core.connection.ConnectionAttemptStatus
|
import com.muwire.core.connection.ConnectionAttemptStatus
|
||||||
import com.muwire.core.connection.ConnectionEvent
|
import com.muwire.core.connection.ConnectionEvent
|
||||||
import com.muwire.core.connection.DisconnectionEvent
|
import com.muwire.core.connection.DisconnectionEvent
|
||||||
@@ -35,7 +34,7 @@ class Cli {
|
|||||||
|
|
||||||
Core core
|
Core core
|
||||||
try {
|
try {
|
||||||
core = new Core(props, home, "0.5.2")
|
core = new Core(props, home, "0.2.1")
|
||||||
} 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"
|
||||||
@@ -73,7 +72,7 @@ class Cli {
|
|||||||
|
|
||||||
Timer timer = new Timer("status-printer", true)
|
Timer timer = new Timer("status-printer", true)
|
||||||
timer.schedule({
|
timer.schedule({
|
||||||
println String.valueOf(new Date()) + " Connections $connectionsListener.connections Uploads $uploadsListener.uploads Shared $sharedListener.shared"
|
println "Connections $connectionsListener.connections Uploads $uploadsListener.uploads Shared $sharedListener.shared"
|
||||||
} as TimerTask, 60000, 60000)
|
} as TimerTask, 60000, 60000)
|
||||||
|
|
||||||
def latch = new CountDownLatch(1)
|
def latch = new CountDownLatch(1)
|
||||||
@@ -85,7 +84,6 @@ class Cli {
|
|||||||
core.eventBus.register(AllFilesLoadedEvent.class, fileLoader)
|
core.eventBus.register(AllFilesLoadedEvent.class, fileLoader)
|
||||||
core.startServices()
|
core.startServices()
|
||||||
|
|
||||||
core.eventBus.publish(new UILoadedEvent())
|
|
||||||
println "waiting for files to load"
|
println "waiting for files to load"
|
||||||
latch.await()
|
latch.await()
|
||||||
// now we begin
|
// now we begin
|
||||||
@@ -119,11 +117,11 @@ class Cli {
|
|||||||
volatile int uploads
|
volatile int uploads
|
||||||
public void onUploadEvent(UploadEvent e) {
|
public void onUploadEvent(UploadEvent e) {
|
||||||
uploads++
|
uploads++
|
||||||
println String.valueOf(new Date()) + " Starting upload of ${e.uploader.file.getName()} to ${e.uploader.request.downloader.getHumanReadableName()}"
|
println "Starting upload of ${e.uploader.file.getName()} to ${e.uploader.request.downloader.getHumanReadableName()}"
|
||||||
}
|
}
|
||||||
public void onUploadFinishedEvent(UploadFinishedEvent e) {
|
public void onUploadFinishedEvent(UploadFinishedEvent e) {
|
||||||
uploads--
|
uploads--
|
||||||
println String.valueOf(new Date()) + " Finished upload of ${e.uploader.file.getName()} to ${e.uploader.request.downloader.getHumanReadableName()}"
|
println "Finished upload of ${e.uploader.file.getName()} to ${e.uploader.request.downloader.getHumanReadableName()}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -53,7 +53,7 @@ class CliDownloader {
|
|||||||
|
|
||||||
Core core
|
Core core
|
||||||
try {
|
try {
|
||||||
core = new Core(props, home, "0.5.2")
|
core = new Core(props, home, "0.2.1")
|
||||||
} 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"
|
||||||
|
@@ -1,23 +0,0 @@
|
|||||||
package com.muwire.cli
|
|
||||||
|
|
||||||
import com.muwire.core.util.DataUtil
|
|
||||||
|
|
||||||
import groovy.json.JsonSlurper
|
|
||||||
import net.i2p.data.Base64
|
|
||||||
|
|
||||||
class FileList {
|
|
||||||
public static void main(String [] args) {
|
|
||||||
if (args.length < 1) {
|
|
||||||
println "pass files.json as argument"
|
|
||||||
System.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
def slurper = new JsonSlurper()
|
|
||||||
File filesJson = new File(args[0])
|
|
||||||
filesJson.eachLine {
|
|
||||||
def json = slurper.parseText(it)
|
|
||||||
String name = DataUtil.readi18nString(Base64.decode(json.file))
|
|
||||||
println "$name,$json.length,$json.pieceSize,$json.infoHash"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -2,9 +2,8 @@ apply plugin : 'application'
|
|||||||
mainClassName = 'com.muwire.core.Core'
|
mainClassName = 'com.muwire.core.Core'
|
||||||
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties']
|
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties']
|
||||||
dependencies {
|
dependencies {
|
||||||
compile "net.i2p:router:${i2pVersion}"
|
compile 'net.i2p.client:mstreaming:0.9.40'
|
||||||
compile "net.i2p.client:mstreaming:${i2pVersion}"
|
compile 'net.i2p.client:streaming:0.9.40'
|
||||||
compile "net.i2p.client:streaming:${i2pVersion}"
|
|
||||||
|
|
||||||
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'
|
||||||
|
15
core/src/main/groovy/com/muwire/core/Constants.groovy
Normal file
15
core/src/main/groovy/com/muwire/core/Constants.groovy
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package com.muwire.core
|
||||||
|
|
||||||
|
import net.i2p.crypto.SigType
|
||||||
|
|
||||||
|
class Constants {
|
||||||
|
public static final byte PERSONA_VERSION = (byte)1
|
||||||
|
public static final SigType SIG_TYPE = SigType.EdDSA_SHA512_Ed25519
|
||||||
|
|
||||||
|
public static final int MAX_HEADER_SIZE = 0x1 << 14
|
||||||
|
public static final int MAX_HEADERS = 16
|
||||||
|
|
||||||
|
public static final float DOWNLOAD_SEQUENTIAL_RATIO = 0.8f
|
||||||
|
|
||||||
|
public static final String SPLIT_PATTERN = "[\\.,_-]"
|
||||||
|
}
|
@@ -1,7 +1,6 @@
|
|||||||
package com.muwire.core
|
package com.muwire.core
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
|
||||||
|
|
||||||
import com.muwire.core.connection.ConnectionAcceptor
|
import com.muwire.core.connection.ConnectionAcceptor
|
||||||
import com.muwire.core.connection.ConnectionEstablisher
|
import com.muwire.core.connection.ConnectionEstablisher
|
||||||
@@ -13,14 +12,10 @@ import com.muwire.core.connection.I2PConnector
|
|||||||
import com.muwire.core.connection.LeafConnectionManager
|
import com.muwire.core.connection.LeafConnectionManager
|
||||||
import com.muwire.core.connection.UltrapeerConnectionManager
|
import com.muwire.core.connection.UltrapeerConnectionManager
|
||||||
import com.muwire.core.download.DownloadManager
|
import com.muwire.core.download.DownloadManager
|
||||||
import com.muwire.core.download.SourceDiscoveredEvent
|
|
||||||
import com.muwire.core.download.UIDownloadCancelledEvent
|
import com.muwire.core.download.UIDownloadCancelledEvent
|
||||||
import com.muwire.core.download.UIDownloadEvent
|
import com.muwire.core.download.UIDownloadEvent
|
||||||
import com.muwire.core.download.UIDownloadPausedEvent
|
|
||||||
import com.muwire.core.download.UIDownloadResumedEvent
|
|
||||||
import com.muwire.core.files.FileDownloadedEvent
|
import com.muwire.core.files.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
|
||||||
@@ -28,32 +23,19 @@ 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.UICommentEvent
|
|
||||||
import com.muwire.core.files.UIPersistFilesEvent
|
|
||||||
import com.muwire.core.files.AllFilesLoadedEvent
|
|
||||||
import com.muwire.core.files.DirectoryUnsharedEvent
|
|
||||||
import com.muwire.core.files.DirectoryWatcher
|
|
||||||
import com.muwire.core.hostcache.CacheClient
|
import com.muwire.core.hostcache.CacheClient
|
||||||
import com.muwire.core.hostcache.HostCache
|
import com.muwire.core.hostcache.HostCache
|
||||||
import com.muwire.core.hostcache.HostDiscoveredEvent
|
import com.muwire.core.hostcache.HostDiscoveredEvent
|
||||||
import com.muwire.core.mesh.MeshManager
|
|
||||||
import com.muwire.core.search.BrowseManager
|
|
||||||
import com.muwire.core.search.QueryEvent
|
import com.muwire.core.search.QueryEvent
|
||||||
import com.muwire.core.search.ResultsEvent
|
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.UIBrowseEvent
|
|
||||||
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
|
||||||
@@ -62,7 +44,6 @@ import net.i2p.client.I2PSession
|
|||||||
import net.i2p.client.streaming.I2PSocketManager
|
import net.i2p.client.streaming.I2PSocketManager
|
||||||
import net.i2p.client.streaming.I2PSocketManagerFactory
|
import net.i2p.client.streaming.I2PSocketManagerFactory
|
||||||
import net.i2p.client.streaming.I2PSocketOptions
|
import net.i2p.client.streaming.I2PSocketOptions
|
||||||
import net.i2p.client.streaming.I2PSocketManager.DisconnectListener
|
|
||||||
import net.i2p.crypto.DSAEngine
|
import net.i2p.crypto.DSAEngine
|
||||||
import net.i2p.crypto.SigType
|
import net.i2p.crypto.SigType
|
||||||
import net.i2p.data.Destination
|
import net.i2p.data.Destination
|
||||||
@@ -70,9 +51,6 @@ 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 {
|
||||||
|
|
||||||
@@ -83,7 +61,6 @@ 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
|
||||||
@@ -93,18 +70,23 @@ public class Core {
|
|||||||
private final ConnectionEstablisher connectionEstablisher
|
private final ConnectionEstablisher connectionEstablisher
|
||||||
private final HasherService hasherService
|
private final HasherService hasherService
|
||||||
private final DownloadManager downloadManager
|
private final DownloadManager downloadManager
|
||||||
private final DirectoryWatcher directoryWatcher
|
|
||||||
final FileManager fileManager
|
|
||||||
final UploadManager uploadManager
|
|
||||||
final ContentManager contentManager
|
|
||||||
|
|
||||||
private final Router router
|
|
||||||
|
|
||||||
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")
|
||||||
@@ -119,53 +101,13 @@ public class Core {
|
|||||||
i2pOptions["inbound.nickname"] = "MuWire"
|
i2pOptions["inbound.nickname"] = "MuWire"
|
||||||
i2pOptions["outbound.nickname"] = "MuWire"
|
i2pOptions["outbound.nickname"] = "MuWire"
|
||||||
i2pOptions["inbound.length"] = "3"
|
i2pOptions["inbound.length"] = "3"
|
||||||
i2pOptions["inbound.quantity"] = "4"
|
i2pOptions["inbound.quantity"] = "2"
|
||||||
i2pOptions["outbound.length"] = "3"
|
i2pOptions["outbound.length"] = "3"
|
||||||
i2pOptions["outbound.quantity"] = "4"
|
i2pOptions["outbound.quantity"] = "2"
|
||||||
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.base", home.getAbsolutePath())
|
|
||||||
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
|
||||||
@@ -174,7 +116,6 @@ public class Core {
|
|||||||
}
|
}
|
||||||
socketManager.getDefaultOptions().setReadTimeout(60000)
|
socketManager.getDefaultOptions().setReadTimeout(60000)
|
||||||
socketManager.getDefaultOptions().setConnectTimeout(30000)
|
socketManager.getDefaultOptions().setConnectTimeout(30000)
|
||||||
socketManager.addDisconnectListener({eventBus.publish(new RouterDisconnectedEvent())} as DisconnectListener)
|
|
||||||
i2pSession = socketManager.getSession()
|
i2pSession = socketManager.getSession()
|
||||||
|
|
||||||
def destination = new Destination()
|
def destination = new Destination()
|
||||||
@@ -212,23 +153,15 @@ public class Core {
|
|||||||
|
|
||||||
|
|
||||||
log.info "initializing file manager"
|
log.info "initializing file manager"
|
||||||
fileManager = new FileManager(eventBus, props)
|
FileManager fileManager = new FileManager(eventBus, props)
|
||||||
eventBus.register(FileHashedEvent.class, fileManager)
|
eventBus.register(FileHashedEvent.class, fileManager)
|
||||||
eventBus.register(FileLoadedEvent.class, fileManager)
|
eventBus.register(FileLoadedEvent.class, fileManager)
|
||||||
eventBus.register(FileDownloadedEvent.class, fileManager)
|
eventBus.register(FileDownloadedEvent.class, fileManager)
|
||||||
eventBus.register(FileUnsharedEvent.class, fileManager)
|
eventBus.register(FileUnsharedEvent.class, fileManager)
|
||||||
eventBus.register(SearchEvent.class, fileManager)
|
eventBus.register(SearchEvent.class, fileManager)
|
||||||
eventBus.register(DirectoryUnsharedEvent.class, fileManager)
|
|
||||||
eventBus.register(UICommentEvent.class, fileManager)
|
|
||||||
|
|
||||||
log.info("initializing mesh manager")
|
|
||||||
MeshManager meshManager = new MeshManager(fileManager, home, props)
|
|
||||||
eventBus.register(SourceDiscoveredEvent.class, meshManager)
|
|
||||||
|
|
||||||
log.info "initializing persistence service"
|
log.info "initializing persistence service"
|
||||||
persisterService = new PersisterService(new File(home, "files.json"), eventBus, 60000, fileManager)
|
persisterService = new PersisterService(new File(home, "files.json"), eventBus, 15000, fileManager)
|
||||||
eventBus.register(UILoadedEvent.class, persisterService)
|
|
||||||
eventBus.register(UIPersistFilesEvent.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")
|
||||||
@@ -249,15 +182,13 @@ public class Core {
|
|||||||
cacheClient = new CacheClient(eventBus,hostCache, connectionManager, i2pSession, props, 10000)
|
cacheClient = new CacheClient(eventBus,hostCache, connectionManager, i2pSession, props, 10000)
|
||||||
|
|
||||||
log.info("initializing update client")
|
log.info("initializing update client")
|
||||||
updateClient = new UpdateClient(eventBus, i2pSession, myVersion, props, fileManager, me)
|
updateClient = new UpdateClient(eventBus, i2pSession, myVersion, props)
|
||||||
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, props)
|
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)
|
||||||
@@ -265,17 +196,14 @@ public class Core {
|
|||||||
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, i2pConnector, home, me)
|
||||||
eventBus.register(UIDownloadEvent.class, downloadManager)
|
eventBus.register(UIDownloadEvent.class, downloadManager)
|
||||||
eventBus.register(UILoadedEvent.class, downloadManager)
|
eventBus.register(UILoadedEvent.class, downloadManager)
|
||||||
eventBus.register(FileDownloadedEvent.class, downloadManager)
|
eventBus.register(FileDownloadedEvent.class, downloadManager)
|
||||||
eventBus.register(UIDownloadCancelledEvent.class, downloadManager)
|
eventBus.register(UIDownloadCancelledEvent.class, downloadManager)
|
||||||
eventBus.register(SourceDiscoveredEvent.class, downloadManager)
|
|
||||||
eventBus.register(UIDownloadPausedEvent.class, downloadManager)
|
|
||||||
eventBus.register(UIDownloadResumedEvent.class, downloadManager)
|
|
||||||
|
|
||||||
log.info("initializing upload manager")
|
log.info("initializing upload manager")
|
||||||
uploadManager = new UploadManager(eventBus, fileManager, meshManager, downloadManager)
|
UploadManager uploadManager = new UploadManager(eventBus, fileManager)
|
||||||
|
|
||||||
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)
|
||||||
@@ -283,40 +211,19 @@ public class Core {
|
|||||||
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, fileManager, connectionEstablisher)
|
i2pAcceptor, hostCache, trustService, searchManager, uploadManager, connectionEstablisher)
|
||||||
|
|
||||||
log.info("initializing directory watcher")
|
|
||||||
directoryWatcher = new DirectoryWatcher(eventBus, fileManager, home, props)
|
|
||||||
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, props)
|
hasherService = new HasherService(new FileHasher(), eventBus, fileManager)
|
||||||
eventBus.register(FileSharedEvent.class, hasherService)
|
eventBus.register(FileSharedEvent.class, hasherService)
|
||||||
eventBus.register(FileUnsharedEvent.class, hasherService)
|
|
||||||
eventBus.register(DirectoryUnsharedEvent.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)
|
|
||||||
|
|
||||||
log.info("initializing browse manager")
|
|
||||||
BrowseManager browseManager = new BrowseManager(i2pConnector, eventBus)
|
|
||||||
eventBus.register(UIBrowseEvent.class, browseManager)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startServices() {
|
public void startServices() {
|
||||||
hasherService.start()
|
hasherService.start()
|
||||||
trustService.start()
|
trustService.start()
|
||||||
trustService.waitForLoad()
|
trustService.waitForLoad()
|
||||||
|
persisterService.start()
|
||||||
hostCache.start()
|
hostCache.start()
|
||||||
connectionManager.start()
|
connectionManager.start()
|
||||||
cacheClient.start()
|
cacheClient.start()
|
||||||
@@ -327,28 +234,14 @@ public class Core {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
if (!shutdown.compareAndSet(false, true)) {
|
|
||||||
log.info("already shutting down")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.info("shutting down trust subscriber")
|
|
||||||
trustSubscriber.stop()
|
|
||||||
log.info("shutting down download manageer")
|
log.info("shutting down download manageer")
|
||||||
downloadManager.shutdown()
|
downloadManager.shutdown()
|
||||||
log.info("shutting down connection acceeptor")
|
log.info("shutting down connection acceeptor")
|
||||||
connectionAcceptor.stop()
|
connectionAcceptor.stop()
|
||||||
log.info("shutting down connection establisher")
|
log.info("shutting down connection establisher")
|
||||||
connectionEstablisher.stop()
|
connectionEstablisher.stop()
|
||||||
log.info("shutting down directory watcher")
|
|
||||||
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) {
|
||||||
@@ -375,7 +268,7 @@ public class Core {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Core core = new Core(props, home, "0.5.2")
|
Core core = new Core(props, home, "0.2.1")
|
||||||
core.startServices()
|
core.startServices()
|
||||||
|
|
||||||
// ... at the end, sleep or execute script
|
// ... at the end, sleep or execute script
|
||||||
|
@@ -48,9 +48,4 @@ class EventBus {
|
|||||||
}
|
}
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1,42 +1,19 @@
|
|||||||
package com.muwire.core
|
package com.muwire.core
|
||||||
|
|
||||||
import java.util.stream.Collectors
|
|
||||||
|
|
||||||
import com.muwire.core.hostcache.CrawlerResponse
|
import com.muwire.core.hostcache.CrawlerResponse
|
||||||
import com.muwire.core.util.DataUtil
|
|
||||||
|
|
||||||
import net.i2p.data.Base64
|
|
||||||
import net.i2p.util.ConcurrentHashSet
|
|
||||||
|
|
||||||
class MuWireSettings {
|
class MuWireSettings {
|
||||||
|
|
||||||
final boolean isLeaf
|
final boolean isLeaf
|
||||||
boolean allowUntrusted
|
boolean allowUntrusted
|
||||||
boolean searchExtraHop
|
|
||||||
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
|
||||||
File incompleteLocation
|
String sharedFiles
|
||||||
CrawlerResponse crawlerResponse
|
CrawlerResponse crawlerResponse
|
||||||
boolean shareDownloadedFiles
|
boolean shareDownloadedFiles
|
||||||
boolean shareHiddenFiles
|
boolean watchSharedDirectories
|
||||||
boolean searchComments
|
|
||||||
boolean browseFiles
|
|
||||||
Set<String> watchedDirectories
|
|
||||||
float downloadSequentialRatio
|
|
||||||
int hostClearInterval, hostHopelessInterval, hostRejectInterval
|
|
||||||
int meshExpiration
|
|
||||||
int speedSmoothSeconds
|
|
||||||
boolean embeddedRouter
|
|
||||||
int inBw, outBw
|
|
||||||
Set<String> watchedKeywords
|
|
||||||
Set<String> watchedRegexes
|
|
||||||
|
|
||||||
MuWireSettings() {
|
MuWireSettings() {
|
||||||
this(new Properties())
|
this(new Properties())
|
||||||
@@ -44,111 +21,34 @@ class MuWireSettings {
|
|||||||
|
|
||||||
MuWireSettings(Properties props) {
|
MuWireSettings(Properties props) {
|
||||||
isLeaf = Boolean.valueOf(props.get("leaf","false"))
|
isLeaf = Boolean.valueOf(props.get("leaf","false"))
|
||||||
allowUntrusted = Boolean.valueOf(props.getProperty("allowUntrusted","true"))
|
allowUntrusted = Boolean.valueOf(props.get("allowUntrusted","true"))
|
||||||
searchExtraHop = Boolean.valueOf(props.getProperty("searchExtraHop","false"))
|
|
||||||
allowTrustLists = Boolean.valueOf(props.getProperty("allowTrustLists","true"))
|
|
||||||
trustListInterval = Integer.valueOf(props.getProperty("trustListInterval","1"))
|
|
||||||
crawlerResponse = CrawlerResponse.valueOf(props.get("crawlerResponse","REGISTERED"))
|
crawlerResponse = CrawlerResponse.valueOf(props.get("crawlerResponse","REGISTERED"))
|
||||||
nickname = props.getProperty("nickname","MuWireUser")
|
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")))
|
||||||
String incompleteLocationProp = props.getProperty("incompleteLocation")
|
sharedFiles = props.getProperty("sharedFiles")
|
||||||
if (incompleteLocationProp != null)
|
downloadRetryInterval = Integer.parseInt(props.getProperty("downloadRetryInterval","15"))
|
||||||
incompleteLocation = new File(incompleteLocationProp)
|
updateCheckInterval = Integer.parseInt(props.getProperty("updateCheckInterval","36"))
|
||||||
downloadRetryInterval = Integer.parseInt(props.getProperty("downloadRetryInterval","60"))
|
|
||||||
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"))
|
||||||
shareHiddenFiles = Boolean.parseBoolean(props.getProperty("shareHiddenFiles","false"))
|
watchSharedDirectories = Boolean.parseBoolean(props.getProperty("watchSharedDirectories","true"))
|
||||||
downloadSequentialRatio = Float.valueOf(props.getProperty("downloadSequentialRatio","0.8"))
|
|
||||||
hostClearInterval = Integer.valueOf(props.getProperty("hostClearInterval","15"))
|
|
||||||
hostHopelessInterval = Integer.valueOf(props.getProperty("hostHopelessInterval", "1440"))
|
|
||||||
hostRejectInterval = Integer.valueOf(props.getProperty("hostRejectInterval", "1"))
|
|
||||||
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"))
|
|
||||||
searchComments = Boolean.valueOf(props.getProperty("searchComments","true"))
|
|
||||||
browseFiles = Boolean.valueOf(props.getProperty("browseFiles","true"))
|
|
||||||
speedSmoothSeconds = Integer.valueOf(props.getProperty("speedSmoothSeconds","60"))
|
|
||||||
|
|
||||||
watchedDirectories = readEncodedSet(props, "watchedDirectories")
|
|
||||||
watchedKeywords = readEncodedSet(props, "watchedKeywords")
|
|
||||||
watchedRegexes = readEncodedSet(props, "watchedRegexes")
|
|
||||||
|
|
||||||
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("searchExtraHop", String.valueOf(searchExtraHop))
|
|
||||||
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())
|
||||||
if (incompleteLocation != null)
|
|
||||||
props.setProperty("incompleteLocation", incompleteLocation.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("shareHiddenFiles", String.valueOf(shareHiddenFiles))
|
props.setProperty("watchSharedDirectories", String.valueOf(watchSharedDirectories))
|
||||||
props.setProperty("downloadSequentialRatio", String.valueOf(downloadSequentialRatio))
|
if (sharedFiles != null)
|
||||||
props.setProperty("hostClearInterval", String.valueOf(hostClearInterval))
|
props.setProperty("sharedFiles", sharedFiles)
|
||||||
props.setProperty("hostHopelessInterval", String.valueOf(hostHopelessInterval))
|
|
||||||
props.setProperty("hostRejectInterval", String.valueOf(hostRejectInterval))
|
|
||||||
props.setProperty("meshExpiration", String.valueOf(meshExpiration))
|
|
||||||
props.setProperty("embeddedRouter", String.valueOf(embeddedRouter))
|
|
||||||
props.setProperty("inBw", String.valueOf(inBw))
|
|
||||||
props.setProperty("outBw", String.valueOf(outBw))
|
|
||||||
props.setProperty("searchComments", String.valueOf(searchComments))
|
|
||||||
props.setProperty("browseFiles", String.valueOf(browseFiles))
|
|
||||||
props.setProperty("speedSmoothSeconds", String.valueOf(speedSmoothSeconds))
|
|
||||||
|
|
||||||
writeEncodedSet(watchedDirectories, "watchedDirectories", props)
|
|
||||||
writeEncodedSet(watchedKeywords, "watchedKeywords", props)
|
|
||||||
writeEncodedSet(watchedRegexes, "watchedRegexes", props)
|
|
||||||
|
|
||||||
if (!trustSubscriptions.isEmpty()) {
|
|
||||||
String encoded = trustSubscriptions.stream().
|
|
||||||
map({it.toBase64()}).
|
|
||||||
collect(Collectors.joining(","))
|
|
||||||
props.setProperty("trustSubscriptions", encoded)
|
|
||||||
}
|
|
||||||
|
|
||||||
props.store(out, "")
|
props.store(out, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Set<String> readEncodedSet(Properties props, String property) {
|
|
||||||
Set<String> rv = new ConcurrentHashSet<>()
|
|
||||||
if (props.containsKey(property)) {
|
|
||||||
String[] encoded = props.getProperty(property).split(",")
|
|
||||||
encoded.each { rv << DataUtil.readi18nString(Base64.decode(it)) }
|
|
||||||
}
|
|
||||||
rv
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void writeEncodedSet(Set<String> set, String property, Properties props) {
|
|
||||||
if (set.isEmpty())
|
|
||||||
return
|
|
||||||
String encoded = set.stream().
|
|
||||||
map({Base64.encode(DataUtil.encodei18nString(it))}).
|
|
||||||
collect(Collectors.joining(","))
|
|
||||||
props.setProperty(property, encoded)
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isLeaf() {
|
boolean isLeaf() {
|
||||||
isLeaf
|
isLeaf
|
||||||
}
|
}
|
||||||
|
@@ -82,13 +82,4 @@ 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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +0,0 @@
|
|||||||
package com.muwire.core
|
|
||||||
|
|
||||||
class RouterDisconnectedEvent extends Event {
|
|
||||||
}
|
|
@@ -1,7 +0,0 @@
|
|||||||
package com.muwire.core
|
|
||||||
|
|
||||||
class SplitPattern {
|
|
||||||
|
|
||||||
public static final String SPLIT_PATTERN = "[\\*\\+\\-,\\.:;\\(\\)=_/\\\\\\!\\\"\\\'\\\$%\\|\\[\\]\\{\\}\\?]";
|
|
||||||
|
|
||||||
}
|
|
@@ -22,9 +22,6 @@ import net.i2p.data.Destination
|
|||||||
@Log
|
@Log
|
||||||
abstract class Connection implements Closeable {
|
abstract class Connection implements Closeable {
|
||||||
|
|
||||||
private static final int SEARCHES = 10
|
|
||||||
private static final long INTERVAL = 1000
|
|
||||||
|
|
||||||
final EventBus eventBus
|
final EventBus eventBus
|
||||||
final Endpoint endpoint
|
final Endpoint endpoint
|
||||||
final boolean incoming
|
final boolean incoming
|
||||||
@@ -35,7 +32,6 @@ abstract class Connection implements Closeable {
|
|||||||
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
|
||||||
|
|
||||||
@@ -132,8 +128,6 @@ abstract class Connection implements Closeable {
|
|||||||
query.firstHop = e.firstHop
|
query.firstHop = e.firstHop
|
||||||
query.keywords = e.searchEvent.getSearchTerms()
|
query.keywords = e.searchEvent.getSearchTerms()
|
||||||
query.oobInfohash = e.searchEvent.oobInfohash
|
query.oobInfohash = e.searchEvent.oobInfohash
|
||||||
query.searchComments = e.searchEvent.searchComments
|
|
||||||
query.compressedResults = e.searchEvent.compressedResults
|
|
||||||
if (e.searchEvent.searchHash != null)
|
if (e.searchEvent.searchHash != null)
|
||||||
query.infohash = Base64.encode(e.searchEvent.searchHash)
|
query.infohash = Base64.encode(e.searchEvent.searchHash)
|
||||||
query.replyTo = e.replyTo.toBase64()
|
query.replyTo = e.replyTo.toBase64()
|
||||||
@@ -162,25 +156,7 @@ abstract class Connection implements Closeable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
@@ -211,19 +187,11 @@ abstract class Connection implements Closeable {
|
|||||||
boolean oob = false
|
boolean oob = false
|
||||||
if (search.oobInfohash != null)
|
if (search.oobInfohash != null)
|
||||||
oob = search.oobInfohash
|
oob = search.oobInfohash
|
||||||
boolean searchComments = false
|
|
||||||
if (search.searchComments != null)
|
|
||||||
searchComments = search.searchComments
|
|
||||||
boolean compressedResults = false
|
|
||||||
if (search.compressedResults != null)
|
|
||||||
compressedResults = search.compressedResults
|
|
||||||
|
|
||||||
SearchEvent searchEvent = new SearchEvent(searchTerms : search.keywords,
|
SearchEvent searchEvent = new SearchEvent(searchTerms : search.keywords,
|
||||||
searchHash : infohash,
|
searchHash : infohash,
|
||||||
uuid : uuid,
|
uuid : uuid,
|
||||||
oobInfohash : oob,
|
oobInfohash : oob)
|
||||||
searchComments : searchComments,
|
|
||||||
compressedResults : compressedResults)
|
|
||||||
QueryEvent event = new QueryEvent ( searchEvent : searchEvent,
|
QueryEvent event = new QueryEvent ( searchEvent : searchEvent,
|
||||||
replyTo : replyTo,
|
replyTo : replyTo,
|
||||||
originator : originator,
|
originator : originator,
|
||||||
|
@@ -5,32 +5,23 @@ import java.util.concurrent.ExecutorService
|
|||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.logging.Level
|
import java.util.logging.Level
|
||||||
import java.util.zip.DeflaterOutputStream
|
import java.util.zip.DeflaterOutputStream
|
||||||
import java.util.zip.GZIPInputStream
|
|
||||||
import java.util.zip.GZIPOutputStream
|
|
||||||
import java.util.zip.InflaterInputStream
|
import java.util.zip.InflaterInputStream
|
||||||
|
|
||||||
import com.muwire.core.Constants
|
|
||||||
import com.muwire.core.EventBus
|
import com.muwire.core.EventBus
|
||||||
import com.muwire.core.MuWireSettings
|
import com.muwire.core.MuWireSettings
|
||||||
import com.muwire.core.Persona
|
import com.muwire.core.Persona
|
||||||
import com.muwire.core.files.FileManager
|
|
||||||
import com.muwire.core.hostcache.HostCache
|
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.ResultsSender
|
|
||||||
import com.muwire.core.search.SearchManager
|
import com.muwire.core.search.SearchManager
|
||||||
import com.muwire.core.search.UIResultBatchEvent
|
|
||||||
import com.muwire.core.search.UIResultEvent
|
|
||||||
import com.muwire.core.search.UnexpectedResultsException
|
import com.muwire.core.search.UnexpectedResultsException
|
||||||
|
|
||||||
import groovy.json.JsonOutput
|
import groovy.json.JsonOutput
|
||||||
import groovy.json.JsonSlurper
|
import groovy.json.JsonSlurper
|
||||||
import groovy.util.logging.Log
|
import groovy.util.logging.Log
|
||||||
import net.i2p.data.Base64
|
|
||||||
|
|
||||||
@Log
|
@Log
|
||||||
class ConnectionAcceptor {
|
class ConnectionAcceptor {
|
||||||
@@ -43,7 +34,6 @@ class ConnectionAcceptor {
|
|||||||
final TrustService trustService
|
final TrustService trustService
|
||||||
final SearchManager searchManager
|
final SearchManager searchManager
|
||||||
final UploadManager uploadManager
|
final UploadManager uploadManager
|
||||||
final FileManager fileManager
|
|
||||||
final ConnectionEstablisher establisher
|
final ConnectionEstablisher establisher
|
||||||
|
|
||||||
final ExecutorService acceptorThread
|
final ExecutorService acceptorThread
|
||||||
@@ -54,7 +44,7 @@ class ConnectionAcceptor {
|
|||||||
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,
|
||||||
FileManager fileManager, ConnectionEstablisher establisher) {
|
ConnectionEstablisher establisher) {
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.manager = manager
|
this.manager = manager
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
@@ -62,7 +52,6 @@ class ConnectionAcceptor {
|
|||||||
this.hostCache = hostCache
|
this.hostCache = hostCache
|
||||||
this.trustService = trustService
|
this.trustService = trustService
|
||||||
this.searchManager = searchManager
|
this.searchManager = searchManager
|
||||||
this.fileManager = fileManager
|
|
||||||
this.uploadManager = uploadManager
|
this.uploadManager = uploadManager
|
||||||
this.establisher = establisher
|
this.establisher = establisher
|
||||||
|
|
||||||
@@ -134,15 +123,6 @@ class ConnectionAcceptor {
|
|||||||
case (byte)'P':
|
case (byte)'P':
|
||||||
processPOST(e)
|
processPOST(e)
|
||||||
break
|
break
|
||||||
case (byte)'R':
|
|
||||||
processRESULTS(e)
|
|
||||||
break
|
|
||||||
case (byte)'T':
|
|
||||||
processTRUST(e)
|
|
||||||
break
|
|
||||||
case (byte)'B':
|
|
||||||
processBROWSE(e)
|
|
||||||
break
|
|
||||||
default:
|
default:
|
||||||
throw new Exception("Invalid read $read")
|
throw new Exception("Invalid read $read")
|
||||||
}
|
}
|
||||||
@@ -243,17 +223,15 @@ class ConnectionAcceptor {
|
|||||||
|
|
||||||
Persona sender = new Persona(dis)
|
Persona sender = new Persona(dis)
|
||||||
if (sender.destination != e.getDestination())
|
if (sender.destination != e.getDestination())
|
||||||
throw new IOException("Sender destination mismatch expected ${e.getDestination()}, got $sender.destination")
|
throw new IOException("Sender destination mismatch expected $e.getDestination(), got $sender.destination")
|
||||||
int nResults = dis.readUnsignedShort()
|
int nResults = dis.readUnsignedShort()
|
||||||
UIResultEvent[] results = new UIResultEvent[nResults]
|
|
||||||
for (int i = 0; i < nResults; i++) {
|
for (int i = 0; i < nResults; i++) {
|
||||||
int jsonSize = dis.readUnsignedShort()
|
int jsonSize = dis.readUnsignedShort()
|
||||||
byte [] payload = new byte[jsonSize]
|
byte [] payload = new byte[jsonSize]
|
||||||
dis.readFully(payload)
|
dis.readFully(payload)
|
||||||
def json = slurper.parse(payload)
|
def json = slurper.parse(payload)
|
||||||
results[i] = ResultsParser.parse(sender, resultsUUID, json)
|
eventBus.publish(ResultsParser.parse(sender, resultsUUID, json))
|
||||||
}
|
}
|
||||||
eventBus.publish(new UIResultBatchEvent(uuid: resultsUUID, results: results))
|
|
||||||
} catch (IOException | UnexpectedResultsException | InvalidSearchResultException bad) {
|
} catch (IOException | UnexpectedResultsException | InvalidSearchResultException bad) {
|
||||||
log.log(Level.WARNING, "failed to process POST", bad)
|
log.log(Level.WARNING, "failed to process POST", bad)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -261,146 +239,4 @@ class ConnectionAcceptor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processRESULTS(Endpoint e) {
|
|
||||||
InputStream is = e.getInputStream()
|
|
||||||
DataInputStream dis = new DataInputStream(is)
|
|
||||||
byte[] esults = new byte[7]
|
|
||||||
dis.readFully(esults)
|
|
||||||
if (esults != "ESULTS ".getBytes(StandardCharsets.US_ASCII))
|
|
||||||
throw new IOException("Invalid RESULTS connection")
|
|
||||||
|
|
||||||
JsonSlurper slurper = new JsonSlurper()
|
|
||||||
try {
|
|
||||||
String uuid = DataUtil.readTillRN(dis)
|
|
||||||
UUID resultsUUID = UUID.fromString(uuid)
|
|
||||||
if (!searchManager.hasLocalSearch(resultsUUID))
|
|
||||||
throw new UnexpectedResultsException(resultsUUID.toString())
|
|
||||||
|
|
||||||
|
|
||||||
// parse all headers
|
|
||||||
Map<String,String> headers = new HashMap<>()
|
|
||||||
String header
|
|
||||||
while((header = DataUtil.readTillRN(is)) != "" && headers.size() < Constants.MAX_HEADERS) {
|
|
||||||
int colon = header.indexOf(':')
|
|
||||||
if (colon == -1 || colon == header.length() - 1)
|
|
||||||
throw new IOException("invalid header $header")
|
|
||||||
String key = header.substring(0, colon)
|
|
||||||
String value = header.substring(colon + 1)
|
|
||||||
headers[key] = value.trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!headers.containsKey("Sender"))
|
|
||||||
throw new IOException("No Sender header")
|
|
||||||
if (!headers.containsKey("Count"))
|
|
||||||
throw new IOException("No Count header")
|
|
||||||
|
|
||||||
byte [] personaBytes = Base64.decode(headers['Sender'])
|
|
||||||
Persona sender = new Persona(new ByteArrayInputStream(personaBytes))
|
|
||||||
if (sender.destination != e.getDestination())
|
|
||||||
throw new IOException("Sender destination mismatch expected ${e.getDestination()}, got $sender.destination")
|
|
||||||
|
|
||||||
int nResults = Integer.parseInt(headers['Count'])
|
|
||||||
if (nResults > Constants.MAX_RESULTS)
|
|
||||||
throw new IOException("too many results $nResults")
|
|
||||||
|
|
||||||
dis = new DataInputStream(new GZIPInputStream(dis))
|
|
||||||
UIResultEvent[] results = new UIResultEvent[nResults]
|
|
||||||
for (int i = 0; i < nResults; i++) {
|
|
||||||
int jsonSize = dis.readUnsignedShort()
|
|
||||||
byte [] payload = new byte[jsonSize]
|
|
||||||
dis.readFully(payload)
|
|
||||||
def json = slurper.parse(payload)
|
|
||||||
results[i] = ResultsParser.parse(sender, resultsUUID, json)
|
|
||||||
}
|
|
||||||
eventBus.publish(new UIResultBatchEvent(uuid: resultsUUID, results: results))
|
|
||||||
} catch (IOException bad) {
|
|
||||||
log.log(Level.WARNING, "failed to process RESULTS", bad)
|
|
||||||
} finally {
|
|
||||||
e.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processBROWSE(Endpoint e) {
|
|
||||||
try {
|
|
||||||
byte [] rowse = new byte[7]
|
|
||||||
DataInputStream dis = new DataInputStream(e.getInputStream())
|
|
||||||
dis.readFully(rowse)
|
|
||||||
if (rowse != "ROWSE\r\n".getBytes(StandardCharsets.US_ASCII))
|
|
||||||
throw new IOException("Invalid BROWSE connection")
|
|
||||||
String header
|
|
||||||
while ((header = DataUtil.readTillRN(dis)) != ""); // ignore headers for now
|
|
||||||
|
|
||||||
OutputStream os = e.getOutputStream()
|
|
||||||
if (!settings.browseFiles) {
|
|
||||||
os.write("403 Not Allowed\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
|
||||||
os.flush()
|
|
||||||
e.close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
os.write("200 OK\r\n".getBytes(StandardCharsets.US_ASCII))
|
|
||||||
|
|
||||||
def sharedFiles = fileManager.getSharedFiles().values()
|
|
||||||
|
|
||||||
os.write("Count: ${sharedFiles.size()}\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
|
||||||
|
|
||||||
DataOutputStream dos = new DataOutputStream(new GZIPOutputStream(os))
|
|
||||||
JsonOutput jsonOutput = new JsonOutput()
|
|
||||||
sharedFiles.each {
|
|
||||||
def obj = ResultsSender.sharedFileToObj(it, false)
|
|
||||||
def json = jsonOutput.toJson(obj)
|
|
||||||
dos.writeShort((short)json.length())
|
|
||||||
dos.write(json.getBytes(StandardCharsets.US_ASCII))
|
|
||||||
}
|
|
||||||
dos.flush()
|
|
||||||
dos.close()
|
|
||||||
} finally {
|
|
||||||
e.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processTRUST(Endpoint e) {
|
|
||||||
try {
|
|
||||||
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()
|
|
||||||
} finally {
|
|
||||||
e.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -31,7 +31,7 @@ class ConnectionEstablisher {
|
|||||||
final HostCache hostCache
|
final HostCache hostCache
|
||||||
|
|
||||||
final Timer timer
|
final Timer timer
|
||||||
final ExecutorService executor, closer
|
final ExecutorService executor
|
||||||
|
|
||||||
final Set inProgress = new ConcurrentHashSet()
|
final Set inProgress = new ConcurrentHashSet()
|
||||||
|
|
||||||
@@ -51,8 +51,6 @@ class ConnectionEstablisher {
|
|||||||
rv.setName("connector-${System.currentTimeMillis()}")
|
rv.setName("connector-${System.currentTimeMillis()}")
|
||||||
rv
|
rv
|
||||||
} as ThreadFactory)
|
} as ThreadFactory)
|
||||||
|
|
||||||
closer = Executors.newSingleThreadExecutor()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void start() {
|
void start() {
|
||||||
@@ -62,7 +60,6 @@ class ConnectionEstablisher {
|
|||||||
void stop() {
|
void stop() {
|
||||||
timer.cancel()
|
timer.cancel()
|
||||||
executor.shutdownNow()
|
executor.shutdownNow()
|
||||||
closer.shutdown()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void connectIfNeeded() {
|
private void connectIfNeeded() {
|
||||||
@@ -123,10 +120,8 @@ class ConnectionEstablisher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void fail(Endpoint endpoint) {
|
private void fail(Endpoint endpoint) {
|
||||||
closer.execute {
|
|
||||||
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))
|
||||||
} as Runnable
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readK(Endpoint e) {
|
private void readK(Endpoint e) {
|
||||||
@@ -180,7 +175,7 @@ class ConnectionEstablisher {
|
|||||||
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
|
||||||
closer.execute({e.close()} as Runnable)
|
e.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,6 +2,7 @@ package com.muwire.core.connection
|
|||||||
|
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
|
||||||
import com.muwire.core.EventBus
|
import com.muwire.core.EventBus
|
||||||
import com.muwire.core.MuWireSettings
|
import com.muwire.core.MuWireSettings
|
||||||
@@ -66,7 +67,7 @@ class PeerConnection extends Connection {
|
|||||||
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).getBytes(StandardCharsets.UTF_8)
|
||||||
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
|
||||||
|
@@ -1,9 +0,0 @@
|
|||||||
package com.muwire.core.content
|
|
||||||
|
|
||||||
import com.muwire.core.Event
|
|
||||||
|
|
||||||
class ContentControlEvent extends Event {
|
|
||||||
String term
|
|
||||||
boolean regex
|
|
||||||
boolean add
|
|
||||||
}
|
|
@@ -1,30 +0,0 @@
|
|||||||
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) }
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,36 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,9 +0,0 @@
|
|||||||
package com.muwire.core.content
|
|
||||||
|
|
||||||
import com.muwire.core.Persona
|
|
||||||
|
|
||||||
class Match {
|
|
||||||
Persona persona
|
|
||||||
String [] keywords
|
|
||||||
long timestamp
|
|
||||||
}
|
|
@@ -1,20 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,35 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
@@ -3,10 +3,6 @@ package com.muwire.core.download
|
|||||||
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.files.FileHasher
|
import com.muwire.core.files.FileHasher
|
||||||
import com.muwire.core.mesh.Mesh
|
|
||||||
import com.muwire.core.mesh.MeshManager
|
|
||||||
import com.muwire.core.trust.TrustLevel
|
|
||||||
import com.muwire.core.trust.TrustService
|
|
||||||
import com.muwire.core.util.DataUtil
|
import com.muwire.core.util.DataUtil
|
||||||
|
|
||||||
import groovy.json.JsonBuilder
|
import groovy.json.JsonBuilder
|
||||||
@@ -18,37 +14,31 @@ import net.i2p.util.ConcurrentHashSet
|
|||||||
|
|
||||||
import com.muwire.core.EventBus
|
import com.muwire.core.EventBus
|
||||||
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.UILoadedEvent
|
import com.muwire.core.UILoadedEvent
|
||||||
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
import java.util.concurrent.Executor
|
import java.util.concurrent.Executor
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
public class DownloadManager {
|
public class DownloadManager {
|
||||||
|
|
||||||
private final EventBus eventBus
|
private final EventBus eventBus
|
||||||
private final TrustService trustService
|
|
||||||
private final MeshManager meshManager
|
|
||||||
private final MuWireSettings muSettings
|
|
||||||
private final I2PConnector connector
|
private final I2PConnector connector
|
||||||
private final Executor executor
|
private final Executor executor
|
||||||
private final File home
|
private final File incompletes, home
|
||||||
private final Persona me
|
private final Persona me
|
||||||
|
|
||||||
private final Map<InfoHash, Downloader> downloaders = new ConcurrentHashMap<>()
|
private final Set<Downloader> downloaders = new ConcurrentHashSet<>()
|
||||||
|
|
||||||
public DownloadManager(EventBus eventBus, TrustService trustService, MeshManager meshManager, MuWireSettings muSettings,
|
public DownloadManager(EventBus eventBus, I2PConnector connector, File home, Persona me) {
|
||||||
I2PConnector connector, File home, Persona me) {
|
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.trustService = trustService
|
|
||||||
this.meshManager = meshManager
|
|
||||||
this.muSettings = muSettings
|
|
||||||
this.connector = connector
|
this.connector = connector
|
||||||
|
this.incompletes = new File(home,"incompletes")
|
||||||
this.home = home
|
this.home = home
|
||||||
this.me = me
|
this.me = me
|
||||||
|
|
||||||
|
incompletes.mkdir()
|
||||||
|
|
||||||
this.executor = Executors.newCachedThreadPool({ r ->
|
this.executor = Executors.newCachedThreadPool({ r ->
|
||||||
Thread rv = new Thread(r)
|
Thread rv = new Thread(r)
|
||||||
rv.setName("download-worker")
|
rv.setName("download-worker")
|
||||||
@@ -60,11 +50,6 @@ public class DownloadManager {
|
|||||||
|
|
||||||
public void onUIDownloadEvent(UIDownloadEvent e) {
|
public void onUIDownloadEvent(UIDownloadEvent e) {
|
||||||
|
|
||||||
File incompletes = muSettings.incompleteLocation
|
|
||||||
if (incompletes == null)
|
|
||||||
incompletes = new File(home, "incompletes")
|
|
||||||
incompletes.mkdirs()
|
|
||||||
|
|
||||||
def size = e.result[0].size
|
def size = e.result[0].size
|
||||||
def infohash = e.result[0].infohash
|
def infohash = e.result[0].infohash
|
||||||
def pieceSize = e.result[0].pieceSize
|
def pieceSize = e.result[0].pieceSize
|
||||||
@@ -73,30 +58,18 @@ public class DownloadManager {
|
|||||||
e.result.each {
|
e.result.each {
|
||||||
destinations.add(it.sender.destination)
|
destinations.add(it.sender.destination)
|
||||||
}
|
}
|
||||||
destinations.addAll(e.sources)
|
|
||||||
destinations.remove(me.destination)
|
|
||||||
|
|
||||||
Pieces pieces = getPieces(infohash, size, pieceSize, e.sequential)
|
|
||||||
|
|
||||||
def downloader = new Downloader(eventBus, this, me, e.target, size,
|
def downloader = new Downloader(eventBus, this, me, e.target, size,
|
||||||
infohash, pieceSize, connector, destinations,
|
infohash, pieceSize, connector, destinations,
|
||||||
incompletes, pieces)
|
incompletes)
|
||||||
downloaders.put(infohash, downloader)
|
downloaders.add(downloader)
|
||||||
persistDownloaders()
|
persistDownloaders()
|
||||||
executor.execute({downloader.download()} as Runnable)
|
executor.execute({downloader.download()} as Runnable)
|
||||||
eventBus.publish(new DownloadStartedEvent(downloader : downloader))
|
eventBus.publish(new DownloadStartedEvent(downloader : downloader))
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onUIDownloadCancelledEvent(UIDownloadCancelledEvent e) {
|
public void onUIDownloadCancelledEvent(UIDownloadCancelledEvent e) {
|
||||||
downloaders.remove(e.downloader.infoHash)
|
downloaders.remove(e.downloader)
|
||||||
persistDownloaders()
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onUIDownloadPausedEvent(UIDownloadPausedEvent e) {
|
|
||||||
persistDownloaders()
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onUIDownloadResumedEvent(UIDownloadResumedEvent e) {
|
|
||||||
persistDownloaders()
|
persistDownloaders()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,64 +97,23 @@ public class DownloadManager {
|
|||||||
byte [] root = Base64.decode(json.hashRoot)
|
byte [] root = Base64.decode(json.hashRoot)
|
||||||
infoHash = new InfoHash(root)
|
infoHash = new InfoHash(root)
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean sequential = false
|
|
||||||
if (json.sequential != null)
|
|
||||||
sequential = json.sequential
|
|
||||||
|
|
||||||
File incompletes
|
|
||||||
if (json.incompletes != null)
|
|
||||||
incompletes = new File(DataUtil.readi18nString(Base64.decode(json.incompletes)))
|
|
||||||
else
|
|
||||||
incompletes = new File(home, "incompletes")
|
|
||||||
|
|
||||||
Pieces pieces = getPieces(infoHash, (long)json.length, json.pieceSizePow2, sequential)
|
|
||||||
|
|
||||||
def downloader = new Downloader(eventBus, this, me, file, (long)json.length,
|
def downloader = new Downloader(eventBus, this, me, file, (long)json.length,
|
||||||
infoHash, json.pieceSizePow2, connector, destinations, incompletes, pieces)
|
infoHash, json.pieceSizePow2, connector, destinations, incompletes)
|
||||||
if (json.paused != null)
|
downloaders.add(downloader)
|
||||||
downloader.paused = json.paused
|
|
||||||
downloaders.put(infoHash, downloader)
|
|
||||||
downloader.readPieces()
|
|
||||||
if (!downloader.paused)
|
|
||||||
downloader.download()
|
downloader.download()
|
||||||
eventBus.publish(new DownloadStartedEvent(downloader : downloader))
|
eventBus.publish(new DownloadStartedEvent(downloader : downloader))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Pieces getPieces(InfoHash infoHash, long length, int pieceSizePow2, boolean sequential) {
|
|
||||||
int pieceSize = 0x1 << pieceSizePow2
|
|
||||||
int nPieces = (int)(length / pieceSize)
|
|
||||||
if (length % pieceSize != 0)
|
|
||||||
nPieces++
|
|
||||||
Mesh mesh = meshManager.getOrCreate(infoHash, nPieces, sequential)
|
|
||||||
mesh.pieces
|
|
||||||
}
|
|
||||||
|
|
||||||
void onSourceDiscoveredEvent(SourceDiscoveredEvent e) {
|
|
||||||
Downloader downloader = downloaders.get(e.infoHash)
|
|
||||||
if (downloader == null)
|
|
||||||
return
|
|
||||||
boolean ok = false
|
|
||||||
switch(trustService.getLevel(e.source.destination)) {
|
|
||||||
case TrustLevel.TRUSTED: ok = true; break
|
|
||||||
case TrustLevel.NEUTRAL: ok = muSettings.allowUntrusted; break
|
|
||||||
case TrustLevel.DISTRUSTED: ok = false; break
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ok)
|
|
||||||
downloader.addSource(e.source.destination)
|
|
||||||
}
|
|
||||||
|
|
||||||
void onFileDownloadedEvent(FileDownloadedEvent e) {
|
void onFileDownloadedEvent(FileDownloadedEvent e) {
|
||||||
downloaders.remove(e.downloader.infoHash)
|
downloaders.remove(e.downloader)
|
||||||
persistDownloaders()
|
persistDownloaders()
|
||||||
}
|
}
|
||||||
|
|
||||||
private void persistDownloaders() {
|
private void persistDownloaders() {
|
||||||
File downloadsFile = new File(home,"downloads.json")
|
File downloadsFile = new File(home,"downloads.json")
|
||||||
downloadsFile.withPrintWriter { writer ->
|
downloadsFile.withPrintWriter { writer ->
|
||||||
downloaders.values().each { downloader ->
|
downloaders.each { downloader ->
|
||||||
if (!downloader.cancelled) {
|
if (!downloader.cancelled) {
|
||||||
def json = [:]
|
def json = [:]
|
||||||
json.file = Base64.encode(DataUtil.encodei18nString(downloader.file.getAbsolutePath()))
|
json.file = Base64.encode(DataUtil.encodei18nString(downloader.file.getAbsolutePath()))
|
||||||
@@ -198,13 +130,6 @@ public class DownloadManager {
|
|||||||
json.hashList = Base64.encode(infoHash.hashList)
|
json.hashList = Base64.encode(infoHash.hashList)
|
||||||
else
|
else
|
||||||
json.hashRoot = Base64.encode(infoHash.getRoot())
|
json.hashRoot = Base64.encode(infoHash.getRoot())
|
||||||
|
|
||||||
json.paused = downloader.paused
|
|
||||||
|
|
||||||
json.sequential = downloader.pieces.ratio == 0f
|
|
||||||
|
|
||||||
json.incompletes = Base64.encode(DataUtil.encodei18nString(downloader.incompletes.getAbsolutePath()))
|
|
||||||
|
|
||||||
writer.println(JsonOutput.toJson(json))
|
writer.println(JsonOutput.toJson(json))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -212,7 +137,7 @@ public class DownloadManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
downloaders.values().each { it.stop() }
|
downloaders.each { it.stop() }
|
||||||
Downloader.executorService.shutdownNow()
|
Downloader.executorService.shutdownNow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,56 +3,49 @@ package com.muwire.core.download;
|
|||||||
import net.i2p.data.Base64
|
import net.i2p.data.Base64
|
||||||
|
|
||||||
import com.muwire.core.Constants
|
import com.muwire.core.Constants
|
||||||
import com.muwire.core.EventBus
|
|
||||||
import com.muwire.core.InfoHash
|
import com.muwire.core.InfoHash
|
||||||
import com.muwire.core.Persona
|
|
||||||
import com.muwire.core.connection.Endpoint
|
import com.muwire.core.connection.Endpoint
|
||||||
import com.muwire.core.util.DataUtil
|
|
||||||
|
|
||||||
import static com.muwire.core.util.DataUtil.readTillRN
|
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
|
||||||
import java.nio.file.StandardOpenOption
|
import java.nio.file.StandardOpenOption
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.security.NoSuchAlgorithmException
|
import java.security.NoSuchAlgorithmException
|
||||||
import java.util.logging.Level
|
|
||||||
|
|
||||||
@Log
|
@Log
|
||||||
class DownloadSession {
|
class DownloadSession {
|
||||||
|
|
||||||
private final EventBus eventBus
|
private static int SAMPLES = 10
|
||||||
|
|
||||||
private final String meB64
|
private final String meB64
|
||||||
private final Pieces pieces
|
private final Pieces downloaded, claimed
|
||||||
private final InfoHash infoHash
|
private final InfoHash infoHash
|
||||||
private final Endpoint endpoint
|
private final Endpoint endpoint
|
||||||
private final File file
|
private final File file
|
||||||
private final int pieceSize
|
private final int pieceSize
|
||||||
private final long fileLength
|
private final long fileLength
|
||||||
private final Set<Integer> available
|
|
||||||
private final MessageDigest digest
|
private final MessageDigest digest
|
||||||
|
|
||||||
private long lastSpeedRead = System.currentTimeMillis()
|
private final LinkedList<Long> timestamps = new LinkedList<>()
|
||||||
private long dataSinceLastRead
|
private final LinkedList<Integer> reads = new LinkedList<>()
|
||||||
|
|
||||||
private MappedByteBuffer mapped
|
private ByteBuffer mapped
|
||||||
|
|
||||||
DownloadSession(EventBus eventBus, String meB64, Pieces pieces, InfoHash infoHash, Endpoint endpoint, File file,
|
DownloadSession(String meB64, Pieces downloaded, Pieces claimed, InfoHash infoHash, Endpoint endpoint, File file,
|
||||||
int pieceSize, long fileLength, Set<Integer> available) {
|
int pieceSize, long fileLength) {
|
||||||
this.eventBus = eventBus
|
|
||||||
this.meB64 = meB64
|
this.meB64 = meB64
|
||||||
this.pieces = pieces
|
this.downloaded = downloaded
|
||||||
|
this.claimed = claimed
|
||||||
this.endpoint = endpoint
|
this.endpoint = endpoint
|
||||||
this.infoHash = infoHash
|
this.infoHash = infoHash
|
||||||
this.file = file
|
this.file = file
|
||||||
this.pieceSize = pieceSize
|
this.pieceSize = pieceSize
|
||||||
this.fileLength = fileLength
|
this.fileLength = fileLength
|
||||||
this.available = available
|
|
||||||
try {
|
try {
|
||||||
digest = MessageDigest.getInstance("SHA-256")
|
digest = MessageDigest.getInstance("SHA-256")
|
||||||
} catch (NoSuchAlgorithmException impossible) {
|
} catch (NoSuchAlgorithmException impossible) {
|
||||||
@@ -70,100 +63,70 @@ class DownloadSession {
|
|||||||
OutputStream os = endpoint.getOutputStream()
|
OutputStream os = endpoint.getOutputStream()
|
||||||
InputStream is = endpoint.getInputStream()
|
InputStream is = endpoint.getInputStream()
|
||||||
|
|
||||||
int[] pieceAndPosition
|
int piece
|
||||||
if (available.isEmpty())
|
while(true) {
|
||||||
pieceAndPosition = pieces.claim()
|
piece = downloaded.getRandomPiece()
|
||||||
else
|
if (claimed.isMarked(piece)) {
|
||||||
pieceAndPosition = pieces.claim(new HashSet<>(available))
|
if (downloaded.donePieces() + claimed.donePieces() == downloaded.nPieces) {
|
||||||
if (pieceAndPosition == null)
|
log.info("all pieces claimed")
|
||||||
return false
|
return false
|
||||||
int piece = pieceAndPosition[0]
|
}
|
||||||
int position = pieceAndPosition[1]
|
continue
|
||||||
boolean steal = pieceAndPosition[2] == 1
|
}
|
||||||
boolean unclaim = true
|
break
|
||||||
|
}
|
||||||
|
claimed.markDownloaded(piece)
|
||||||
|
|
||||||
log.info("will download piece $piece from position $position steal $steal")
|
log.info("will download piece $piece")
|
||||||
|
|
||||||
|
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())
|
||||||
|
|
||||||
|
FileChannel channel
|
||||||
try {
|
try {
|
||||||
os.write("GET $root\r\n".getBytes(StandardCharsets.US_ASCII))
|
os.write("GET $root\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
os.write("Range: $start-$end\r\n".getBytes(StandardCharsets.US_ASCII))
|
os.write("Range: $start-$end\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
os.write("X-Persona: $meB64\r\n".getBytes(StandardCharsets.US_ASCII))
|
os.write("X-Persona: $meB64\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
String xHave = DataUtil.encodeXHave(pieces.getDownloaded(), pieces.nPieces)
|
|
||||||
os.write("X-Have: $xHave\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
|
||||||
os.flush()
|
os.flush()
|
||||||
String codeString = readTillRN(is)
|
String code = readTillRN(is)
|
||||||
int space = codeString.indexOf(' ')
|
if (code.startsWith("404 ")) {
|
||||||
if (space > 0)
|
|
||||||
codeString = codeString.substring(0, space)
|
|
||||||
|
|
||||||
int code = Integer.parseInt(codeString.trim())
|
|
||||||
|
|
||||||
if (code == 404) {
|
|
||||||
log.warning("file not found")
|
log.warning("file not found")
|
||||||
endpoint.close()
|
endpoint.close()
|
||||||
return false
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(code == 200 || code == 416)) {
|
if (code.startsWith("416 ")) {
|
||||||
|
log.warning("range $start-$end cannot be satisfied")
|
||||||
|
return // leave endpoint open
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!code.startsWith("200 ")) {
|
||||||
log.warning("unknown code $code")
|
log.warning("unknown code $code")
|
||||||
endpoint.close()
|
endpoint.close()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse all headers
|
// parse all headers
|
||||||
Map<String,String> headers = new HashMap<>()
|
Set<String> headers = new HashSet<>()
|
||||||
String header
|
String header
|
||||||
while((header = readTillRN(is)) != "" && headers.size() < Constants.MAX_HEADERS) {
|
while((header = readTillRN(is)) != "" && headers.size() < Constants.MAX_HEADERS)
|
||||||
int colon = header.indexOf(':')
|
headers.add(header)
|
||||||
if (colon == -1 || colon == header.length() - 1)
|
|
||||||
throw new IOException("invalid header $header")
|
long receivedStart = -1
|
||||||
String key = header.substring(0, colon)
|
long receivedEnd = -1
|
||||||
String value = header.substring(colon + 1)
|
for (String receivedHeader : headers) {
|
||||||
headers[key] = value.trim()
|
def group = (receivedHeader =~ /^Content-Range: (\d+)-(\d+)$/)
|
||||||
|
if (group.size() != 1) {
|
||||||
|
log.info("ignoring header $receivedHeader")
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// prase X-Alt if present
|
receivedStart = Long.parseLong(group[0][1])
|
||||||
if (headers.containsKey("X-Alt")) {
|
receivedEnd = Long.parseLong(group[0][2])
|
||||||
headers["X-Alt"].split(",").each {
|
|
||||||
if (it.length() > 0) {
|
|
||||||
byte [] raw = Base64.decode(it)
|
|
||||||
Persona source = new Persona(new ByteArrayInputStream(raw))
|
|
||||||
eventBus.publish(new SourceDiscoveredEvent(infoHash : infoHash, source : source))
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse X-Have if present
|
|
||||||
if (headers.containsKey("X-Have")) {
|
|
||||||
DataUtil.decodeXHave(headers["X-Have"]).each {
|
|
||||||
available.add(it)
|
|
||||||
}
|
|
||||||
if (!available.contains(piece))
|
|
||||||
return true // try again next time
|
|
||||||
} else {
|
|
||||||
if (code != 200)
|
|
||||||
throw new IOException("Code $code but no X-Have")
|
|
||||||
available.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (code != 200)
|
|
||||||
return true
|
|
||||||
|
|
||||||
String range = headers["Content-Range"]
|
|
||||||
if (range == null)
|
|
||||||
throw new IOException("Code 200 but no Content-Range")
|
|
||||||
|
|
||||||
def group = (range =~ /^(\d+)-(\d+)$/)
|
|
||||||
if (group.size() != 1)
|
|
||||||
throw new IOException("invalid Content-Range header $range")
|
|
||||||
|
|
||||||
long receivedStart = Long.parseLong(group[0][1])
|
|
||||||
long receivedEnd = Long.parseLong(group[0][2])
|
|
||||||
|
|
||||||
if (receivedStart != start || receivedEnd != end) {
|
if (receivedStart != start || receivedEnd != end) {
|
||||||
log.warning("We don't support mismatching ranges yet")
|
log.warning("We don't support mismatching ranges yet")
|
||||||
@@ -172,12 +135,9 @@ class DownloadSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// start the download
|
// start the download
|
||||||
FileChannel channel
|
|
||||||
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))
|
StandardOpenOption.SPARSE, StandardOpenOption.CREATE)) // TODO: double-check, maybe CREATE_NEW
|
||||||
mapped = channel.map(FileChannel.MapMode.READ_WRITE, pieceStart, end - pieceStart + 1)
|
mapped = channel.map(FileChannel.MapMode.READ_WRITE, start, end - start + 1)
|
||||||
mapped.position(position)
|
|
||||||
|
|
||||||
byte[] tmp = new byte[0x1 << 13]
|
byte[] tmp = new byte[0x1 << 13]
|
||||||
while(mapped.hasRemaining()) {
|
while(mapped.hasRemaining()) {
|
||||||
@@ -188,8 +148,13 @@ class DownloadSession {
|
|||||||
throw new IOException()
|
throw new IOException()
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
mapped.put(tmp, 0, read)
|
mapped.put(tmp, 0, read)
|
||||||
dataSinceLastRead += read
|
|
||||||
pieces.markPartial(piece, mapped.position())
|
if (timestamps.size() == SAMPLES) {
|
||||||
|
timestamps.removeFirst()
|
||||||
|
reads.removeFirst()
|
||||||
|
}
|
||||||
|
timestamps.addLast(System.currentTimeMillis())
|
||||||
|
reads.addLast(read)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,19 +163,13 @@ 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)
|
||||||
pieces.markPartial(piece, 0)
|
throw new BadHashException()
|
||||||
throw new BadHashException("bad hash on piece $piece")
|
|
||||||
}
|
downloaded.markDownloaded(piece)
|
||||||
} finally {
|
} finally {
|
||||||
|
claimed.clear(piece)
|
||||||
try { channel?.close() } catch (IOException ignore) {}
|
try { channel?.close() } catch (IOException ignore) {}
|
||||||
DataUtil.tryUnmap(mapped)
|
|
||||||
}
|
|
||||||
pieces.markDownloaded(piece)
|
|
||||||
unclaim = false
|
|
||||||
} finally {
|
|
||||||
if (unclaim && !steal)
|
|
||||||
pieces.unclaim(piece)
|
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -222,11 +181,24 @@ 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)
|
|
||||||
lastSpeedRead = now;
|
while(idx < SAMPLES && timestamps.get(idx) < now - 1000)
|
||||||
int rv = (int) (dataSinceLastRead * 1000.0 / interval)
|
idx++
|
||||||
dataSinceLastRead = 0
|
if (idx == SAMPLES)
|
||||||
rv
|
return 0
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,14 +4,9 @@ import com.muwire.core.InfoHash
|
|||||||
import com.muwire.core.Persona
|
import com.muwire.core.Persona
|
||||||
import com.muwire.core.connection.Endpoint
|
import com.muwire.core.connection.Endpoint
|
||||||
|
|
||||||
import java.nio.file.AtomicMoveNotSupportedException
|
|
||||||
import java.nio.file.Files
|
|
||||||
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
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
|
||||||
import java.util.logging.Level
|
import java.util.logging.Level
|
||||||
|
|
||||||
import com.muwire.core.Constants
|
import com.muwire.core.Constants
|
||||||
@@ -19,16 +14,13 @@ 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
|
||||||
import net.i2p.util.ConcurrentHashSet
|
|
||||||
|
|
||||||
@Log
|
@Log
|
||||||
public class Downloader {
|
public class Downloader {
|
||||||
|
public enum DownloadState { CONNECTING, HASHLIST, DOWNLOADING, FAILED, CANCELLED, FINISHED }
|
||||||
public enum DownloadState { CONNECTING, HASHLIST, DOWNLOADING, FAILED, CANCELLED, PAUSED, FINISHED }
|
|
||||||
private enum WorkerState { CONNECTING, HASHLIST, DOWNLOADING, FINISHED}
|
private enum WorkerState { CONNECTING, HASHLIST, DOWNLOADING, FINISHED}
|
||||||
|
|
||||||
private static final ExecutorService executorService = Executors.newCachedThreadPool({r ->
|
private static final ExecutorService executorService = Executors.newCachedThreadPool({r ->
|
||||||
@@ -42,34 +34,25 @@ public class Downloader {
|
|||||||
private final DownloadManager downloadManager
|
private final DownloadManager downloadManager
|
||||||
private final Persona me
|
private final Persona me
|
||||||
private final File file
|
private final File file
|
||||||
private final Pieces pieces
|
private final Pieces downloaded, claimed
|
||||||
private final long length
|
private final long length
|
||||||
private InfoHash infoHash
|
private InfoHash infoHash
|
||||||
private final int pieceSize
|
private final int pieceSize
|
||||||
private final I2PConnector connector
|
private final I2PConnector connector
|
||||||
private final Set<Destination> destinations
|
private final Set<Destination> destinations
|
||||||
private final int nPieces
|
private final int nPieces
|
||||||
private final File incompletes
|
|
||||||
private final File piecesFile
|
private final File piecesFile
|
||||||
private final File incompleteFile
|
|
||||||
final int pieceSizePow2
|
final int pieceSizePow2
|
||||||
private final Map<Destination, DownloadWorker> activeWorkers = new ConcurrentHashMap<>()
|
private final Map<Destination, DownloadWorker> activeWorkers = new ConcurrentHashMap<>()
|
||||||
private final Set<Destination> successfulDestinations = new ConcurrentHashSet<>()
|
|
||||||
|
|
||||||
|
|
||||||
private volatile boolean cancelled, paused
|
private volatile boolean cancelled
|
||||||
private final AtomicBoolean eventFired = new AtomicBoolean()
|
private volatile boolean eventFired
|
||||||
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,
|
||||||
File incompletes, Pieces pieces) {
|
File incompletes) {
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.me = me
|
this.me = me
|
||||||
this.downloadManager = downloadManager
|
this.downloadManager = downloadManager
|
||||||
@@ -78,13 +61,19 @@ public class Downloader {
|
|||||||
this.length = length
|
this.length = length
|
||||||
this.connector = connector
|
this.connector = connector
|
||||||
this.destinations = destinations
|
this.destinations = destinations
|
||||||
this.incompletes = incompletes
|
|
||||||
this.piecesFile = new File(incompletes, file.getName()+".pieces")
|
this.piecesFile = new File(incompletes, file.getName()+".pieces")
|
||||||
this.incompleteFile = new File(incompletes, file.getName()+".part")
|
|
||||||
this.pieceSizePow2 = pieceSizePow2
|
this.pieceSizePow2 = pieceSizePow2
|
||||||
this.pieceSize = 1 << pieceSizePow2
|
this.pieceSize = 1 << pieceSizePow2
|
||||||
this.pieces = pieces
|
|
||||||
this.nPieces = pieces.nPieces
|
int nPieces
|
||||||
|
if (length % pieceSize == 0)
|
||||||
|
nPieces = length / pieceSize
|
||||||
|
else
|
||||||
|
nPieces = length / pieceSize + 1
|
||||||
|
this.nPieces = nPieces
|
||||||
|
|
||||||
|
downloaded = new Pieces(nPieces, Constants.DOWNLOAD_SEQUENTIAL_RATIO)
|
||||||
|
claimed = new Pieces(nPieces)
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized InfoHash getInfoHash() {
|
public synchronized InfoHash getInfoHash() {
|
||||||
@@ -110,82 +99,44 @@ public class Downloader {
|
|||||||
if (!piecesFile.exists())
|
if (!piecesFile.exists())
|
||||||
return
|
return
|
||||||
piecesFile.eachLine {
|
piecesFile.eachLine {
|
||||||
String [] split = it.split(",")
|
int piece = Integer.parseInt(it)
|
||||||
int piece = Integer.parseInt(split[0])
|
downloaded.markDownloaded(piece)
|
||||||
if (split.length == 1)
|
|
||||||
pieces.markDownloaded(piece)
|
|
||||||
else {
|
|
||||||
int position = Integer.parseInt(split[1])
|
|
||||||
pieces.markPartial(piece, position)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void writePieces() {
|
void writePieces() {
|
||||||
synchronized(piecesFile) {
|
|
||||||
if (piecesFileClosed)
|
|
||||||
return
|
|
||||||
piecesFile.withPrintWriter { writer ->
|
piecesFile.withPrintWriter { writer ->
|
||||||
pieces.write(writer)
|
downloaded.getDownloaded().each { piece ->
|
||||||
|
writer.println(piece)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public long donePieces() {
|
public long donePieces() {
|
||||||
pieces.donePieces()
|
downloaded.donePieces()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public int speed() {
|
public int speed() {
|
||||||
int currSpeed = 0
|
int total = 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)
|
||||||
currSpeed += it.speed()
|
total += it.speed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
total
|
||||||
if (speedArr.size() != downloadManager.muSettings.speedSmoothSeconds) {
|
|
||||||
speedArr.clear()
|
|
||||||
downloadManager.muSettings.speedSmoothSeconds.times { speedArr.add(0) }
|
|
||||||
speedPos = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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() {
|
||||||
if (cancelled)
|
if (cancelled)
|
||||||
return DownloadState.CANCELLED
|
return DownloadState.CANCELLED
|
||||||
if (paused)
|
|
||||||
return DownloadState.PAUSED
|
|
||||||
|
|
||||||
boolean allFinished = true
|
boolean allFinished = true
|
||||||
activeWorkers.values().each {
|
activeWorkers.values().each {
|
||||||
allFinished &= it.currentState == WorkerState.FINISHED
|
allFinished &= it.currentState == WorkerState.FINISHED
|
||||||
}
|
}
|
||||||
if (allFinished) {
|
if (allFinished) {
|
||||||
if (pieces.isComplete())
|
if (downloaded.isComplete())
|
||||||
return DownloadState.FINISHED
|
return DownloadState.FINISHED
|
||||||
return DownloadState.FAILED
|
return DownloadState.FAILED
|
||||||
}
|
}
|
||||||
@@ -219,18 +170,9 @@ public class Downloader {
|
|||||||
public void cancel() {
|
public void cancel() {
|
||||||
cancelled = true
|
cancelled = true
|
||||||
stop()
|
stop()
|
||||||
synchronized(piecesFile) {
|
file.delete()
|
||||||
piecesFileClosed = true
|
|
||||||
piecesFile.delete()
|
piecesFile.delete()
|
||||||
}
|
}
|
||||||
incompleteFile.delete()
|
|
||||||
pieces.clearAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
public void pause() {
|
|
||||||
paused = true
|
|
||||||
stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
activeWorkers.values().each {
|
activeWorkers.values().each {
|
||||||
@@ -248,8 +190,6 @@ public class Downloader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void resume() {
|
public void resume() {
|
||||||
paused = false
|
|
||||||
readPieces()
|
|
||||||
destinations.each { destination ->
|
destinations.each { destination ->
|
||||||
def worker = activeWorkers.get(destination)
|
def worker = activeWorkers.get(destination)
|
||||||
if (worker != null) {
|
if (worker != null) {
|
||||||
@@ -266,21 +206,12 @@ public class Downloader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void addSource(Destination d) {
|
|
||||||
if (activeWorkers.containsKey(d))
|
|
||||||
return
|
|
||||||
DownloadWorker newWorker = new DownloadWorker(d)
|
|
||||||
activeWorkers.put(d, newWorker)
|
|
||||||
executorService.submit(newWorker)
|
|
||||||
}
|
|
||||||
|
|
||||||
class DownloadWorker implements Runnable {
|
class DownloadWorker implements Runnable {
|
||||||
private final Destination destination
|
private final Destination destination
|
||||||
private volatile WorkerState currentState
|
private volatile WorkerState currentState
|
||||||
private volatile Thread downloadThread
|
private volatile Thread downloadThread
|
||||||
private Endpoint endpoint
|
private Endpoint endpoint
|
||||||
private volatile DownloadSession currentSession
|
private volatile DownloadSession currentSession
|
||||||
private final Set<Integer> available = new HashSet<>()
|
|
||||||
|
|
||||||
DownloadWorker(Destination destination) {
|
DownloadWorker(Destination destination) {
|
||||||
this.destination = destination
|
this.destination = destination
|
||||||
@@ -300,38 +231,23 @@ public class Downloader {
|
|||||||
}
|
}
|
||||||
currentState = WorkerState.DOWNLOADING
|
currentState = WorkerState.DOWNLOADING
|
||||||
boolean requestPerformed
|
boolean requestPerformed
|
||||||
while(!pieces.isComplete()) {
|
while(!downloaded.isComplete()) {
|
||||||
currentSession = new DownloadSession(eventBus, me.toBase64(), pieces, getInfoHash(),
|
currentSession = new DownloadSession(me.toBase64(), downloaded, claimed, getInfoHash(), endpoint, file, pieceSize, length)
|
||||||
endpoint, incompleteFile, pieceSize, length, available)
|
|
||||||
requestPerformed = currentSession.request()
|
requestPerformed = currentSession.request()
|
||||||
if (!requestPerformed)
|
if (!requestPerformed)
|
||||||
break
|
break
|
||||||
successfulDestinations.add(endpoint.destination)
|
|
||||||
writePieces()
|
writePieces()
|
||||||
}
|
}
|
||||||
} catch (Exception bad) {
|
} catch (Exception bad) {
|
||||||
log.log(Level.WARNING,"Exception while downloading",DataUtil.findRoot(bad))
|
log.log(Level.WARNING,"Exception while downloading",bad)
|
||||||
} finally {
|
} finally {
|
||||||
writePieces()
|
|
||||||
currentState = WorkerState.FINISHED
|
currentState = WorkerState.FINISHED
|
||||||
if (pieces.isComplete() && eventFired.compareAndSet(false, true)) {
|
if (downloaded.isComplete() && !eventFired) {
|
||||||
synchronized(piecesFile) {
|
|
||||||
piecesFileClosed = true
|
|
||||||
piecesFile.delete()
|
piecesFile.delete()
|
||||||
}
|
eventFired = true
|
||||||
activeWorkers.values().each {
|
|
||||||
if (it.destination != destination)
|
|
||||||
it.cancel()
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
Files.move(incompleteFile.toPath(), file.toPath(), StandardCopyOption.ATOMIC_MOVE)
|
|
||||||
} catch (AtomicMoveNotSupportedException e) {
|
|
||||||
Files.copy(incompleteFile.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
|
||||||
incompleteFile.delete()
|
|
||||||
}
|
|
||||||
eventBus.publish(
|
eventBus.publish(
|
||||||
new FileDownloadedEvent(
|
new FileDownloadedEvent(
|
||||||
downloadedFile : new DownloadedFile(file.getCanonicalFile(), getInfoHash(), pieceSizePow2, successfulDestinations),
|
downloadedFile : new DownloadedFile(file, getInfoHash(), pieceSizePow2, Collections.emptySet()),
|
||||||
downloader : Downloader.this))
|
downloader : Downloader.this))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,11 +1,10 @@
|
|||||||
package com.muwire.core.download
|
package com.muwire.core.download
|
||||||
|
|
||||||
class Pieces {
|
class Pieces {
|
||||||
private final BitSet done, claimed
|
private final BitSet bitSet
|
||||||
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)
|
||||||
@@ -14,104 +13,52 @@ class Pieces {
|
|||||||
Pieces(int nPieces, float ratio) {
|
Pieces(int nPieces, float ratio) {
|
||||||
this.nPieces = nPieces
|
this.nPieces = nPieces
|
||||||
this.ratio = ratio
|
this.ratio = ratio
|
||||||
done = new BitSet(nPieces)
|
bitSet = new BitSet(nPieces)
|
||||||
claimed = new BitSet(nPieces)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized int[] claim() {
|
synchronized int getRandomPiece() {
|
||||||
int claimedCardinality = claimed.cardinality()
|
int cardinality = bitSet.cardinality()
|
||||||
if (claimedCardinality == nPieces) {
|
if (cardinality == nPieces)
|
||||||
// steal
|
return -1
|
||||||
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 * cardinality) / nPieces > ratio) {
|
||||||
int rv = claimed.nextClearBit(0)
|
return bitSet.nextClearBit(0)
|
||||||
claimed.set(rv)
|
|
||||||
return [rv, partials.getOrDefault(rv, 0), 0]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
while(true) {
|
while(true) {
|
||||||
int start = random.nextInt(nPieces)
|
int start = random.nextInt(nPieces)
|
||||||
if (claimed.get(start))
|
if (bitSet.get(start))
|
||||||
continue
|
continue
|
||||||
claimed.set(start)
|
return start
|
||||||
return [start, partials.getOrDefault(start,0), 0]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized int[] claim(Set<Integer> available) {
|
def getDownloaded() {
|
||||||
for (int i = done.nextSetBit(0); i >= 0; i = done.nextSetBit(i+1))
|
|
||||||
available.remove(i)
|
|
||||||
if (available.isEmpty())
|
|
||||||
return null
|
|
||||||
Set<Integer> availableCopy = new HashSet<>(available)
|
|
||||||
for (int i = claimed.nextSetBit(0); i >= 0; i = claimed.nextSetBit(i+1))
|
|
||||||
availableCopy.remove(i)
|
|
||||||
if (availableCopy.isEmpty()) {
|
|
||||||
// steal
|
|
||||||
int rv = available.first()
|
|
||||||
return [rv, partials.getOrDefault(rv, 0), 1]
|
|
||||||
}
|
|
||||||
List<Integer> toList = availableCopy.toList()
|
|
||||||
if (ratio > 0f)
|
|
||||||
Collections.shuffle(toList)
|
|
||||||
int rv = toList[0]
|
|
||||||
claimed.set(rv)
|
|
||||||
[rv, partials.getOrDefault(rv, 0), 0]
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized def getDownloaded() {
|
|
||||||
def rv = []
|
def rv = []
|
||||||
for (int i = done.nextSetBit(0); i >= 0; i = done.nextSetBit(i+1)) {
|
for (int i = bitSet.nextSetBit(0); i >= 0; i = bitSet.nextSetBit(i+1)) {
|
||||||
rv << i
|
rv << i
|
||||||
}
|
}
|
||||||
rv
|
rv
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void markDownloaded(int piece) {
|
synchronized void markDownloaded(int piece) {
|
||||||
done.set(piece)
|
bitSet.set(piece)
|
||||||
claimed.set(piece)
|
|
||||||
partials.remove(piece)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void markPartial(int piece, int position) {
|
synchronized void clear(int piece) {
|
||||||
partials.put(piece, position)
|
bitSet.clear(piece)
|
||||||
}
|
|
||||||
|
|
||||||
synchronized void unclaim(int piece) {
|
|
||||||
claimed.clear(piece)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized boolean isComplete() {
|
synchronized boolean isComplete() {
|
||||||
done.cardinality() == nPieces
|
bitSet.cardinality() == nPieces
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized boolean isMarked(int piece) {
|
||||||
|
bitSet.get(piece)
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized int donePieces() {
|
synchronized int donePieces() {
|
||||||
done.cardinality()
|
bitSet.cardinality()
|
||||||
}
|
|
||||||
|
|
||||||
synchronized boolean isDownloaded(int piece) {
|
|
||||||
done.get(piece)
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized void clearAll() {
|
|
||||||
done.clear()
|
|
||||||
claimed.clear()
|
|
||||||
partials.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized void write(PrintWriter writer) {
|
|
||||||
for (int i = done.nextSetBit(0); i >= 0; i = done.nextSetBit(i+1)) {
|
|
||||||
writer.println(i)
|
|
||||||
}
|
|
||||||
partials.each { piece, position ->
|
|
||||||
writer.println("$piece,$position")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +0,0 @@
|
|||||||
package com.muwire.core.download
|
|
||||||
|
|
||||||
import com.muwire.core.Event
|
|
||||||
import com.muwire.core.InfoHash
|
|
||||||
import com.muwire.core.Persona
|
|
||||||
|
|
||||||
class SourceDiscoveredEvent extends Event {
|
|
||||||
InfoHash infoHash
|
|
||||||
Persona source
|
|
||||||
}
|
|
@@ -3,12 +3,8 @@ package com.muwire.core.download
|
|||||||
import com.muwire.core.Event
|
import com.muwire.core.Event
|
||||||
import com.muwire.core.search.UIResultEvent
|
import com.muwire.core.search.UIResultEvent
|
||||||
|
|
||||||
import net.i2p.data.Destination
|
|
||||||
|
|
||||||
class UIDownloadEvent extends Event {
|
class UIDownloadEvent extends Event {
|
||||||
|
|
||||||
UIResultEvent[] result
|
UIResultEvent[] result
|
||||||
Set<Destination> sources
|
|
||||||
File target
|
File target
|
||||||
boolean sequential
|
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +0,0 @@
|
|||||||
package com.muwire.core.download
|
|
||||||
|
|
||||||
import com.muwire.core.Event
|
|
||||||
|
|
||||||
class UIDownloadPausedEvent extends Event {
|
|
||||||
}
|
|
@@ -1,6 +0,0 @@
|
|||||||
package com.muwire.core.download
|
|
||||||
|
|
||||||
import com.muwire.core.Event
|
|
||||||
|
|
||||||
class UIDownloadResumedEvent extends Event {
|
|
||||||
}
|
|
@@ -1,7 +0,0 @@
|
|||||||
package com.muwire.core.files
|
|
||||||
|
|
||||||
import com.muwire.core.Event
|
|
||||||
|
|
||||||
class DirectoryUnsharedEvent extends Event {
|
|
||||||
File directory
|
|
||||||
}
|
|
@@ -1,166 +1,4 @@
|
|||||||
package com.muwire.core.files
|
package com.muwire.core.files
|
||||||
|
|
||||||
import java.nio.file.FileSystem
|
|
||||||
import java.nio.file.FileSystems
|
|
||||||
import java.nio.file.Path
|
|
||||||
import java.nio.file.Paths
|
|
||||||
import static java.nio.file.StandardWatchEventKinds.*
|
|
||||||
|
|
||||||
import java.nio.file.ClosedWatchServiceException
|
|
||||||
import java.nio.file.WatchEvent
|
|
||||||
import java.nio.file.WatchKey
|
|
||||||
import java.nio.file.WatchService
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
|
|
||||||
import com.muwire.core.EventBus
|
|
||||||
import com.muwire.core.MuWireSettings
|
|
||||||
import com.muwire.core.SharedFile
|
|
||||||
|
|
||||||
import groovy.util.logging.Log
|
|
||||||
import net.i2p.util.SystemVersion
|
|
||||||
|
|
||||||
@Log
|
|
||||||
class DirectoryWatcher {
|
class DirectoryWatcher {
|
||||||
|
|
||||||
private static final long WAIT_TIME = 1000
|
|
||||||
|
|
||||||
private static final WatchEvent.Kind[] kinds
|
|
||||||
static {
|
|
||||||
if (SystemVersion.isMac())
|
|
||||||
kinds = [ENTRY_MODIFY, ENTRY_DELETE]
|
|
||||||
else
|
|
||||||
kinds = [ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE]
|
|
||||||
}
|
|
||||||
|
|
||||||
private final File home
|
|
||||||
private final MuWireSettings muOptions
|
|
||||||
private final EventBus eventBus
|
|
||||||
private final FileManager fileManager
|
|
||||||
private final Thread watcherThread, publisherThread
|
|
||||||
private final Map<File, Long> waitingFiles = new ConcurrentHashMap<>()
|
|
||||||
private final Map<File, WatchKey> watchedDirectories = new ConcurrentHashMap<>()
|
|
||||||
private WatchService watchService
|
|
||||||
private volatile boolean shutdown
|
|
||||||
|
|
||||||
DirectoryWatcher(EventBus eventBus, FileManager fileManager, File home, MuWireSettings muOptions) {
|
|
||||||
this.home = home
|
|
||||||
this.muOptions = muOptions
|
|
||||||
this.eventBus = eventBus
|
|
||||||
this.fileManager = fileManager
|
|
||||||
this.watcherThread = new Thread({watch() } as Runnable, "directory-watcher")
|
|
||||||
watcherThread.setDaemon(true)
|
|
||||||
this.publisherThread = new Thread({publish()} as Runnable, "watched-files-publisher")
|
|
||||||
publisherThread.setDaemon(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
void onAllFilesLoadedEvent(AllFilesLoadedEvent e) {
|
|
||||||
watchService = FileSystems.getDefault().newWatchService()
|
|
||||||
watcherThread.start()
|
|
||||||
publisherThread.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
void stop() {
|
|
||||||
shutdown = true
|
|
||||||
watcherThread?.interrupt()
|
|
||||||
publisherThread?.interrupt()
|
|
||||||
watchService?.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
void onFileSharedEvent(FileSharedEvent e) {
|
|
||||||
if (!e.file.isDirectory())
|
|
||||||
return
|
|
||||||
File canonical = e.file.getCanonicalFile()
|
|
||||||
Path path = canonical.toPath()
|
|
||||||
WatchKey wk = path.register(watchService, kinds)
|
|
||||||
watchedDirectories.put(canonical, wk)
|
|
||||||
|
|
||||||
if (muOptions.watchedDirectories.add(canonical.toString()))
|
|
||||||
saveMuSettings()
|
|
||||||
}
|
|
||||||
|
|
||||||
void onDirectoryUnsharedEvent(DirectoryUnsharedEvent e) {
|
|
||||||
WatchKey wk = watchedDirectories.remove(e.directory)
|
|
||||||
wk?.cancel()
|
|
||||||
|
|
||||||
if (muOptions.watchedDirectories.remove(e.directory.toString()))
|
|
||||||
saveMuSettings()
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveMuSettings() {
|
|
||||||
File muSettingsFile = new File(home, "MuWire.properties")
|
|
||||||
muSettingsFile.withOutputStream {
|
|
||||||
muOptions.write(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void watch() {
|
|
||||||
try {
|
|
||||||
while(!shutdown) {
|
|
||||||
WatchKey key = watchService.take()
|
|
||||||
key.pollEvents().each {
|
|
||||||
switch(it.kind()) {
|
|
||||||
case ENTRY_CREATE: processCreated(key.watchable(), it.context()); break
|
|
||||||
case ENTRY_MODIFY: processModified(key.watchable(), it.context()); break
|
|
||||||
case ENTRY_DELETE: processDeleted(key.watchable(), it.context()); break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
key.reset()
|
|
||||||
}
|
|
||||||
} catch (InterruptedException|ClosedWatchServiceException e) {
|
|
||||||
if (!shutdown)
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void processCreated(Path parent, Path path) {
|
|
||||||
File f= join(parent, path)
|
|
||||||
log.fine("created entry $f")
|
|
||||||
if (f.isDirectory())
|
|
||||||
f.toPath().register(watchService, kinds)
|
|
||||||
else
|
|
||||||
waitingFiles.put(f, System.currentTimeMillis())
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processModified(Path parent, Path path) {
|
|
||||||
File f = join(parent, path)
|
|
||||||
log.fine("modified entry $f")
|
|
||||||
waitingFiles.put(f, System.currentTimeMillis())
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processDeleted(Path parent, Path path) {
|
|
||||||
File f = join(parent, path)
|
|
||||||
log.fine("deleted entry $f")
|
|
||||||
SharedFile sf = fileManager.fileToSharedFile.get(f)
|
|
||||||
if (sf != null)
|
|
||||||
eventBus.publish(new FileUnsharedEvent(unsharedFile : sf))
|
|
||||||
}
|
|
||||||
|
|
||||||
private static File join(Path parent, Path path) {
|
|
||||||
File parentFile = parent.toFile().getCanonicalFile()
|
|
||||||
new File(parentFile, path.toFile().getName()).getCanonicalFile()
|
|
||||||
}
|
|
||||||
|
|
||||||
private void publish() {
|
|
||||||
try {
|
|
||||||
while(!shutdown) {
|
|
||||||
Thread.sleep(WAIT_TIME)
|
|
||||||
long now = System.currentTimeMillis()
|
|
||||||
def published = []
|
|
||||||
waitingFiles.each { file, timestamp ->
|
|
||||||
if (now - timestamp > WAIT_TIME) {
|
|
||||||
log.fine("publishing file $file")
|
|
||||||
eventBus.publish new FileSharedEvent(file : file)
|
|
||||||
published << file
|
|
||||||
}
|
|
||||||
}
|
|
||||||
published.each {
|
|
||||||
waitingFiles.remove(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
if (!shutdown)
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -7,10 +7,4 @@ 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,7 +1,6 @@
|
|||||||
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
|
||||||
|
|
||||||
@@ -19,8 +18,6 @@ class FileHasher {
|
|||||||
/**
|
/**
|
||||||
* @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)
|
|
||||||
* there can be up to 8192 pieces maximum per file
|
|
||||||
*/
|
*/
|
||||||
static int getPieceSize(long size) {
|
static int getPieceSize(long size) {
|
||||||
if (size <= 0x1 << 30)
|
if (size <= 0x1 << 30)
|
||||||
@@ -60,7 +57,6 @@ class FileHasher {
|
|||||||
for (int i = 0; i < numPieces - 1; i++) {
|
for (int i = 0; i < numPieces - 1; i++) {
|
||||||
buf = raf.getChannel().map(MapMode.READ_ONLY, ((long)size) * i, size)
|
buf = raf.getChannel().map(MapMode.READ_ONLY, ((long)size) * i, size)
|
||||||
digest.update buf
|
digest.update buf
|
||||||
DataUtil.tryUnmap(buf)
|
|
||||||
output.write(digest.digest(), 0, 32)
|
output.write(digest.digest(), 0, 32)
|
||||||
}
|
}
|
||||||
def lastPieceLength = length - (numPieces - 1) * ((long)size)
|
def lastPieceLength = length - (numPieces - 1) * ((long)size)
|
||||||
|
@@ -1,15 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -8,10 +8,8 @@ import com.muwire.core.UILoadedEvent
|
|||||||
import com.muwire.core.search.ResultsEvent
|
import com.muwire.core.search.ResultsEvent
|
||||||
import com.muwire.core.search.SearchEvent
|
import com.muwire.core.search.SearchEvent
|
||||||
import com.muwire.core.search.SearchIndex
|
import com.muwire.core.search.SearchIndex
|
||||||
import com.muwire.core.util.DataUtil
|
|
||||||
|
|
||||||
import groovy.util.logging.Log
|
import groovy.util.logging.Log
|
||||||
import net.i2p.data.Base64
|
|
||||||
|
|
||||||
@Log
|
@Log
|
||||||
class FileManager {
|
class FileManager {
|
||||||
@@ -22,7 +20,6 @@ class FileManager {
|
|||||||
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 Map<String, Set<File>> commentToFile = new HashMap<>()
|
|
||||||
final SearchIndex index = new SearchIndex()
|
final SearchIndex index = new SearchIndex()
|
||||||
|
|
||||||
FileManager(EventBus eventBus, MuWireSettings settings) {
|
FileManager(EventBus eventBus, MuWireSettings settings) {
|
||||||
@@ -65,18 +62,6 @@ class FileManager {
|
|||||||
}
|
}
|
||||||
existingFiles.add(sf.getFile())
|
existingFiles.add(sf.getFile())
|
||||||
|
|
||||||
String comment = sf.getComment()
|
|
||||||
if (comment != null) {
|
|
||||||
comment = DataUtil.readi18nString(Base64.decode(comment))
|
|
||||||
index.add(comment)
|
|
||||||
Set<File> existingComment = commentToFile.get(comment)
|
|
||||||
if(existingComment == null) {
|
|
||||||
existingComment = new HashSet<>()
|
|
||||||
commentToFile.put(comment, existingComment)
|
|
||||||
}
|
|
||||||
existingComment.add(sf.getFile())
|
|
||||||
}
|
|
||||||
|
|
||||||
index.add(name)
|
index.add(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,45 +87,9 @@ class FileManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String comment = sf.getComment()
|
|
||||||
if (comment != null) {
|
|
||||||
Set<File> existingComment = commentToFile.get(comment)
|
|
||||||
if (existingComment != null) {
|
|
||||||
existingComment.remove(sf.getFile())
|
|
||||||
if (existingComment.isEmpty()) {
|
|
||||||
commentToFile.remove(comment)
|
|
||||||
index.remove(comment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
index.remove(name)
|
index.remove(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
void onUICommentEvent(UICommentEvent e) {
|
|
||||||
if (e.oldComment != null) {
|
|
||||||
def comment = DataUtil.readi18nString(Base64.decode(e.oldComment))
|
|
||||||
Set<File> existingFiles = commentToFile.get(comment)
|
|
||||||
existingFiles.remove(e.sharedFile.getFile())
|
|
||||||
if (existingFiles.isEmpty()) {
|
|
||||||
commentToFile.remove(comment)
|
|
||||||
index.remove(comment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String comment = e.sharedFile.getComment()
|
|
||||||
comment = DataUtil.readi18nString(Base64.decode(comment))
|
|
||||||
if (comment != null) {
|
|
||||||
index.add(comment)
|
|
||||||
Set<File> existingComment = commentToFile.get(comment)
|
|
||||||
if(existingComment == null) {
|
|
||||||
existingComment = new HashSet<>()
|
|
||||||
commentToFile.put(comment, existingComment)
|
|
||||||
}
|
|
||||||
existingComment.add(e.sharedFile.getFile())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<File, SharedFile> getSharedFiles() {
|
Map<File, SharedFile> getSharedFiles() {
|
||||||
synchronized(fileToSharedFile) {
|
synchronized(fileToSharedFile) {
|
||||||
return new HashMap<>(fileToSharedFile)
|
return new HashMap<>(fileToSharedFile)
|
||||||
@@ -163,15 +112,10 @@ class FileManager {
|
|||||||
} 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 {
|
names.each { files.addAll nameToFiles.getOrDefault(it, []) }
|
||||||
files.addAll nameToFiles.getOrDefault(it, [])
|
|
||||||
if (e.searchComments)
|
|
||||||
files.addAll commentToFile.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)
|
||||||
|
|
||||||
@@ -191,16 +135,4 @@ 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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -5,9 +5,4 @@ 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,7 +4,6 @@ import java.util.concurrent.Executor
|
|||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
import com.muwire.core.EventBus
|
import com.muwire.core.EventBus
|
||||||
import com.muwire.core.MuWireSettings
|
|
||||||
import com.muwire.core.SharedFile
|
import com.muwire.core.SharedFile
|
||||||
|
|
||||||
class HasherService {
|
class HasherService {
|
||||||
@@ -12,15 +11,12 @@ class HasherService {
|
|||||||
final FileHasher hasher
|
final FileHasher hasher
|
||||||
final EventBus eventBus
|
final EventBus eventBus
|
||||||
final FileManager fileManager
|
final FileManager fileManager
|
||||||
final Set<File> hashed = new HashSet<>()
|
|
||||||
final MuWireSettings settings
|
|
||||||
Executor executor
|
Executor executor
|
||||||
|
|
||||||
HasherService(FileHasher hasher, EventBus eventBus, FileManager fileManager, MuWireSettings settings) {
|
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
|
||||||
this.settings = settings
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void start() {
|
void start() {
|
||||||
@@ -28,33 +24,21 @@ class HasherService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onFileSharedEvent(FileSharedEvent evt) {
|
void onFileSharedEvent(FileSharedEvent evt) {
|
||||||
File canonical = evt.file.getCanonicalFile()
|
if (fileManager.fileToSharedFile.containsKey(evt.file))
|
||||||
if (!settings.shareHiddenFiles && canonical.isHidden())
|
|
||||||
return
|
return
|
||||||
if (fileManager.fileToSharedFile.containsKey(canonical))
|
executor.execute( { -> process(evt.file) } as Runnable)
|
||||||
return
|
|
||||||
if (hashed.add(canonical))
|
|
||||||
executor.execute( { -> process(canonical) } as Runnable)
|
|
||||||
}
|
|
||||||
|
|
||||||
void onFileUnsharedEvent(FileUnsharedEvent evt) {
|
|
||||||
hashed.remove(evt.unsharedFile.file)
|
|
||||||
}
|
|
||||||
|
|
||||||
void onDirectoryUnsharedEvent(DirectoryUnsharedEvent evt) {
|
|
||||||
hashed.remove(evt.directory)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void process(File f) {
|
private void process(File f) {
|
||||||
|
f = f.getCanonicalFile()
|
||||||
if (f.isDirectory()) {
|
if (f.isDirectory()) {
|
||||||
f.listFiles().each {eventBus.publish new FileSharedEvent(file: it) }
|
f.listFiles().each {onFileSharedEvent 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 {
|
||||||
eventBus.publish new FileHashingEvent(hashingFile: f)
|
|
||||||
def hash = hasher.hashFile f
|
def hash = hasher.hashFile f
|
||||||
eventBus.publish new FileHashedEvent(sharedFile: new SharedFile(f, hash, FileHasher.getPieceSize(f.length())))
|
eventBus.publish new FileHashedEvent(sharedFile: new SharedFile(f, hash, FileHasher.getPieceSize(f.length())))
|
||||||
}
|
}
|
||||||
|
@@ -3,9 +3,6 @@ package com.muwire.core.files
|
|||||||
import java.nio.file.CopyOption
|
import java.nio.file.CopyOption
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.StandardCopyOption
|
import java.nio.file.StandardCopyOption
|
||||||
import java.util.concurrent.ExecutorService
|
|
||||||
import java.util.concurrent.Executors
|
|
||||||
import java.util.concurrent.ThreadFactory
|
|
||||||
import java.util.logging.Level
|
import java.util.logging.Level
|
||||||
import java.util.stream.Collectors
|
import java.util.stream.Collectors
|
||||||
|
|
||||||
@@ -14,7 +11,6 @@ import com.muwire.core.EventBus
|
|||||||
import com.muwire.core.InfoHash
|
import com.muwire.core.InfoHash
|
||||||
import com.muwire.core.Service
|
import com.muwire.core.Service
|
||||||
import com.muwire.core.SharedFile
|
import com.muwire.core.SharedFile
|
||||||
import com.muwire.core.UILoadedEvent
|
|
||||||
import com.muwire.core.util.DataUtil
|
import com.muwire.core.util.DataUtil
|
||||||
|
|
||||||
import groovy.json.JsonOutput
|
import groovy.json.JsonOutput
|
||||||
@@ -31,35 +27,25 @@ class PersisterService extends Service {
|
|||||||
final int interval
|
final int interval
|
||||||
final Timer timer
|
final Timer timer
|
||||||
final FileManager fileManager
|
final FileManager fileManager
|
||||||
final ExecutorService persisterExecutor = Executors.newSingleThreadExecutor({ r ->
|
|
||||||
new Thread(r, "file persister")
|
|
||||||
} as ThreadFactory)
|
|
||||||
|
|
||||||
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 timer", true)
|
timer = new Timer("file persister", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
void start() {
|
||||||
|
timer.schedule({load()} as TimerTask, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
timer.cancel()
|
timer.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
void onUILoadedEvent(UILoadedEvent e) {
|
|
||||||
timer.schedule({load()} as TimerTask, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
void onUIPersistFilesEvent(UIPersistFilesEvent e) {
|
|
||||||
persistFiles()
|
|
||||||
}
|
|
||||||
|
|
||||||
void load() {
|
void load() {
|
||||||
Thread.currentThread().setPriority(Thread.MIN_PRIORITY)
|
|
||||||
|
|
||||||
if (location.exists() && location.isFile()) {
|
if (location.exists() && location.isFile()) {
|
||||||
int loaded = 0
|
|
||||||
def slurper = new JsonSlurper()
|
def slurper = new JsonSlurper()
|
||||||
try {
|
try {
|
||||||
location.eachLine {
|
location.eachLine {
|
||||||
@@ -69,9 +55,6 @@ class PersisterService extends Service {
|
|||||||
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
|
||||||
loaded++
|
|
||||||
if (loaded % 10 == 0)
|
|
||||||
Thread.sleep(20)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,19 +108,16 @@ class PersisterService extends Service {
|
|||||||
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)
|
||||||
df.setComment(json.comment)
|
|
||||||
return new FileLoadedEvent(loadedFile : df)
|
return new FileLoadedEvent(loadedFile : df)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
SharedFile sf = new SharedFile(file, ih, pieceSize)
|
SharedFile sf = new SharedFile(file, ih, pieceSize)
|
||||||
sf.setComment(json.comment)
|
|
||||||
return new FileLoadedEvent(loadedFile: sf)
|
return new FileLoadedEvent(loadedFile: sf)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void persistFiles() {
|
private void persistFiles() {
|
||||||
persisterExecutor.submit( {
|
|
||||||
def sharedFiles = fileManager.getSharedFiles()
|
def sharedFiles = fileManager.getSharedFiles()
|
||||||
|
|
||||||
File tmp = File.createTempFile("muwire-files", "tmp")
|
File tmp = File.createTempFile("muwire-files", "tmp")
|
||||||
@@ -151,18 +131,21 @@ class PersisterService extends Service {
|
|||||||
}
|
}
|
||||||
Files.copy(tmp.toPath(), location.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
Files.copy(tmp.toPath(), location.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
||||||
tmp.delete()
|
tmp.delete()
|
||||||
} as Runnable)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private def toJson(File f, SharedFile sf) {
|
private def toJson(File f, SharedFile sf) {
|
||||||
def json = [:]
|
def json = [:]
|
||||||
json.file = sf.getB64EncodedFileName()
|
json.file = Base64.encode DataUtil.encodei18nString(f.getCanonicalFile().toString())
|
||||||
json.length = sf.getCachedLength()
|
json.length = f.length()
|
||||||
InfoHash ih = sf.getInfoHash()
|
InfoHash ih = sf.getInfoHash()
|
||||||
json.infoHash = sf.getB64EncodedHashRoot()
|
json.infoHash = Base64.encode ih.getRoot()
|
||||||
json.pieceSize = sf.getPieceSize()
|
json.pieceSize = sf.getPieceSize()
|
||||||
json.hashList = sf.getB64EncodedHashList()
|
byte [] tmp = new byte [32]
|
||||||
json.comment = sf.getComment()
|
json.hashList = []
|
||||||
|
for (int i = 0;i < ih.getHashList().length / 32; i++) {
|
||||||
|
System.arraycopy(ih.getHashList(), i * 32, tmp, 0, 32)
|
||||||
|
json.hashList.add Base64.encode(tmp)
|
||||||
|
}
|
||||||
|
|
||||||
if (sf instanceof DownloadedFile) {
|
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())
|
||||||
|
@@ -1,9 +0,0 @@
|
|||||||
package com.muwire.core.files
|
|
||||||
|
|
||||||
import com.muwire.core.Event
|
|
||||||
import com.muwire.core.SharedFile
|
|
||||||
|
|
||||||
class UICommentEvent extends Event {
|
|
||||||
SharedFile sharedFile
|
|
||||||
String oldComment
|
|
||||||
}
|
|
@@ -1,6 +0,0 @@
|
|||||||
package com.muwire.core.files
|
|
||||||
|
|
||||||
import com.muwire.core.Event
|
|
||||||
|
|
||||||
class UIPersistFilesEvent extends Event {
|
|
||||||
}
|
|
@@ -6,12 +6,7 @@ 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 = [
|
||||||
// zlatinb
|
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"),
|
|
||||||
// sNL
|
|
||||||
new Destination("JC63wJNOqSJmymkj4~UJWywBTvDGikKMoYP0HX2Wz9c5l3otXSkwnxWAFL4cKr~Ygh3BNNi2t93vuLIiI1W8AsE42kR~PwRx~Y-WvIHXR6KUejRmOp-n8WidtjKg9k4aDy428uSOedqXDxys5mpoeQXwDsv1CoPTTwnmb1GWFy~oTGIsCguCl~aJWGnqiKarPO3GJQ~ev-NbvAQzUfC3HeP1e6pdI5CGGjExahTCID5UjpJw8GaDXWlGmYWWH303Xu4x-vAHQy1dJLsOBCn8dZravsn5BKJk~j0POUon45CCx-~NYtaPe0Itt9cMdD2ciC76Rep1D0X0sm1SjlSs8sZ52KmF3oaLZ6OzgI9QLMIyBUrfi41sK5I0qTuUVBAkvW1xr~L-20dYJ9TrbOaOb2-vDIfKaxVi6xQOuhgQDiSBhd3qv2m0xGu-BM9DQYfNA0FdMjnZmqjmji9RMavzQSsVFIbQGLbrLepiEFlb7TseCK5UtRp8TxnG7L4gbYevBQAEAAcAAA=="),
|
|
||||||
// dark_trion
|
|
||||||
new Destination("Gec9L29FVcQvYDgpcYuEYdltJn06PPoOWAcAM8Af-gDm~ehlrJcwlLXXs0hidq~yP2A0X7QcDi6i6shAfuEofTchxGJl8LRNqj9lio7WnB7cIixXWL~uCkD7Np5LMX0~akNX34oOb9RcBYVT2U5rFGJmJ7OtBv~IBkGeLhsMrqaCjahd0jdBO~QJ-t82ZKZhh044d24~JEfF9zSJxdBoCdAcXzryGNy7sYtFVDFsPKJudAxSW-UsSQiGw2~k-TxyF0r-iAt1IdzfNu8Lu0WPqLdhDYJWcPldx2PR5uJorI~zo~z3I5RX3NwzarlbD4nEP5s65ahPSfVCEkzmaJUBgP8DvBqlFaX89K4nGRYc7jkEjJ8cX4L6YPXUpTPWcfKkW259WdQY3YFh6x7rzijrGZewpczOLCrt-bZRYgDrUibmZxKZmNhy~lQu4gYVVjkz1i4tL~DWlhIc4y0x2vItwkYLArPPi~ejTnt-~Lhb7oPMXRcWa3UrwGKpFvGZY4NXBQAEAAcAAA==")
|
|
||||||
]
|
]
|
||||||
|
|
||||||
static List<Destination> getCacheServers() {
|
static List<Destination> getCacheServers() {
|
||||||
|
@@ -7,39 +7,20 @@ class Host {
|
|||||||
private static final int MAX_FAILURES = 3
|
private static final int MAX_FAILURES = 3
|
||||||
|
|
||||||
final Destination destination
|
final Destination destination
|
||||||
private final int clearInterval, hopelessInterval, rejectionInterval
|
|
||||||
int failures,successes
|
int failures,successes
|
||||||
long lastAttempt
|
|
||||||
long lastSuccessfulAttempt
|
|
||||||
long lastRejection
|
|
||||||
|
|
||||||
public Host(Destination destination, int clearInterval, int hopelessInterval, int rejectionInterval) {
|
public Host(Destination destination) {
|
||||||
this.destination = destination
|
this.destination = destination
|
||||||
this.clearInterval = clearInterval
|
|
||||||
this.hopelessInterval = hopelessInterval
|
|
||||||
this.rejectionInterval = rejectionInterval
|
|
||||||
}
|
|
||||||
|
|
||||||
private void connectSuccessful() {
|
|
||||||
failures = 0
|
|
||||||
successes++
|
|
||||||
lastAttempt = System.currentTimeMillis()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void onConnect() {
|
synchronized void onConnect() {
|
||||||
connectSuccessful()
|
failures = 0
|
||||||
lastSuccessfulAttempt = lastAttempt
|
successes++
|
||||||
}
|
|
||||||
|
|
||||||
synchronized void onReject() {
|
|
||||||
connectSuccessful()
|
|
||||||
lastRejection = lastAttempt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void onFailure() {
|
synchronized void onFailure() {
|
||||||
failures++
|
failures++
|
||||||
successes = 0
|
successes = 0
|
||||||
lastAttempt = System.currentTimeMillis()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized boolean isFailed() {
|
synchronized boolean isFailed() {
|
||||||
@@ -53,18 +34,4 @@ class Host {
|
|||||||
synchronized void clearFailures() {
|
synchronized void clearFailures() {
|
||||||
failures = 0
|
failures = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized boolean canTryAgain() {
|
|
||||||
lastSuccessfulAttempt > 0 &&
|
|
||||||
System.currentTimeMillis() - lastAttempt > (clearInterval * 60 * 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized boolean isHopeless() {
|
|
||||||
isFailed() &&
|
|
||||||
System.currentTimeMillis() - lastSuccessfulAttempt > (hopelessInterval * 60 * 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized boolean isRecentlyRejected() {
|
|
||||||
System.currentTimeMillis() - lastRejection < (rejectionInterval * 60 * 1000)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -52,7 +52,7 @@ class HostCache extends Service {
|
|||||||
hosts.get(e.destination).clearFailures()
|
hosts.get(e.destination).clearFailures()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Host host = new Host(e.destination, settings.hostClearInterval, settings.hostHopelessInterval, settings.hostRejectInterval)
|
Host host = new Host(e.destination)
|
||||||
if (allowHost(host)) {
|
if (allowHost(host)) {
|
||||||
hosts.put(e.destination, host)
|
hosts.put(e.destination, host)
|
||||||
}
|
}
|
||||||
@@ -64,16 +64,14 @@ class HostCache extends Service {
|
|||||||
Destination dest = e.endpoint.destination
|
Destination dest = e.endpoint.destination
|
||||||
Host host = hosts.get(dest)
|
Host host = hosts.get(dest)
|
||||||
if (host == null) {
|
if (host == null) {
|
||||||
host = new Host(dest, settings.hostClearInterval, settings.hostHopelessInterval, settings.hostRejectInterval)
|
host = new Host(dest)
|
||||||
hosts.put(dest, host)
|
hosts.put(dest, host)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(e.status) {
|
switch(e.status) {
|
||||||
case ConnectionAttemptStatus.SUCCESSFUL:
|
case ConnectionAttemptStatus.SUCCESSFUL:
|
||||||
host.onConnect()
|
|
||||||
break
|
|
||||||
case ConnectionAttemptStatus.REJECTED:
|
case ConnectionAttemptStatus.REJECTED:
|
||||||
host.onReject()
|
host.onConnect()
|
||||||
break
|
break
|
||||||
case ConnectionAttemptStatus.FAILED:
|
case ConnectionAttemptStatus.FAILED:
|
||||||
host.onFailure()
|
host.onFailure()
|
||||||
@@ -84,10 +82,6 @@ class HostCache extends Service {
|
|||||||
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])}
|
||||||
rv.removeAll {
|
|
||||||
def h = hosts[it];
|
|
||||||
(h.isFailed() && !h.canTryAgain()) || h.isRecentlyRejected()
|
|
||||||
}
|
|
||||||
if (rv.size() <= n)
|
if (rv.size() <= n)
|
||||||
return rv
|
return rv
|
||||||
Collections.shuffle(rv)
|
Collections.shuffle(rv)
|
||||||
@@ -106,37 +100,15 @@ class HostCache extends Service {
|
|||||||
rv[0..n-1]
|
rv[0..n-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
int countFailingHosts() {
|
|
||||||
List<Destination> rv = new ArrayList<>(hosts.keySet())
|
|
||||||
rv.retainAll {
|
|
||||||
hosts[it].isFailed()
|
|
||||||
}
|
|
||||||
rv.size()
|
|
||||||
}
|
|
||||||
|
|
||||||
int countHopelessHosts() {
|
|
||||||
List<Destination> rv = new ArrayList<>(hosts.keySet())
|
|
||||||
rv.retainAll {
|
|
||||||
hosts[it].isHopeless()
|
|
||||||
}
|
|
||||||
rv.size()
|
|
||||||
}
|
|
||||||
|
|
||||||
void load() {
|
void load() {
|
||||||
if (storage.exists()) {
|
if (storage.exists()) {
|
||||||
JsonSlurper slurper = new JsonSlurper()
|
JsonSlurper slurper = new JsonSlurper()
|
||||||
storage.eachLine {
|
storage.eachLine {
|
||||||
def entry = slurper.parseText(it)
|
def entry = slurper.parseText(it)
|
||||||
Destination dest = new Destination(entry.destination)
|
Destination dest = new Destination(entry.destination)
|
||||||
Host host = new Host(dest, settings.hostClearInterval, settings.hostHopelessInterval, settings.hostRejectInterval)
|
Host host = new Host(dest)
|
||||||
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)
|
|
||||||
host.lastAttempt = entry.lastAttempt
|
|
||||||
if (entry.lastSuccessfulAttempt != null)
|
|
||||||
host.lastSuccessfulAttempt = entry.lastSuccessfulAttempt
|
|
||||||
if (entry.lastRejection != null)
|
|
||||||
host.lastRejection = entry.lastRejection
|
|
||||||
if (allowHost(host))
|
if (allowHost(host))
|
||||||
hosts.put(dest, host)
|
hosts.put(dest, host)
|
||||||
}
|
}
|
||||||
@@ -146,6 +118,8 @@ class HostCache extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean allowHost(Host host) {
|
private boolean allowHost(Host host) {
|
||||||
|
if (host.isFailed())
|
||||||
|
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)
|
||||||
@@ -164,14 +138,11 @@ class HostCache extends Service {
|
|||||||
storage.delete()
|
storage.delete()
|
||||||
storage.withPrintWriter { writer ->
|
storage.withPrintWriter { writer ->
|
||||||
hosts.each { dest, host ->
|
hosts.each { dest, host ->
|
||||||
if (allowHost(host) && !host.isHopeless()) {
|
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.lastSuccessfulAttempt = host.lastSuccessfulAttempt
|
|
||||||
map.lastRejection = host.lastRejection
|
|
||||||
def json = JsonOutput.toJson(map)
|
def json = JsonOutput.toJson(map)
|
||||||
writer.println json
|
writer.println json
|
||||||
}
|
}
|
||||||
|
@@ -1,28 +0,0 @@
|
|||||||
package com.muwire.core.mesh
|
|
||||||
|
|
||||||
import com.muwire.core.InfoHash
|
|
||||||
import com.muwire.core.Persona
|
|
||||||
import com.muwire.core.download.Pieces
|
|
||||||
|
|
||||||
import net.i2p.data.Destination
|
|
||||||
import net.i2p.util.ConcurrentHashSet
|
|
||||||
|
|
||||||
class Mesh {
|
|
||||||
private final InfoHash infoHash
|
|
||||||
private final Set<Persona> sources = new ConcurrentHashSet<>()
|
|
||||||
private final Pieces pieces
|
|
||||||
|
|
||||||
Mesh(InfoHash infoHash, Pieces pieces) {
|
|
||||||
this.infoHash = infoHash
|
|
||||||
this.pieces = pieces
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<Persona> getRandom(int n, Persona exclude) {
|
|
||||||
List<Persona> tmp = new ArrayList<>(sources)
|
|
||||||
tmp.remove(exclude)
|
|
||||||
Collections.shuffle(tmp)
|
|
||||||
if (tmp.size() < n)
|
|
||||||
return tmp
|
|
||||||
tmp[0..n-1]
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,103 +0,0 @@
|
|||||||
package com.muwire.core.mesh
|
|
||||||
|
|
||||||
import java.util.stream.Collectors
|
|
||||||
|
|
||||||
import com.muwire.core.Constants
|
|
||||||
import com.muwire.core.InfoHash
|
|
||||||
import com.muwire.core.MuWireSettings
|
|
||||||
import com.muwire.core.Persona
|
|
||||||
import com.muwire.core.download.Pieces
|
|
||||||
import com.muwire.core.download.SourceDiscoveredEvent
|
|
||||||
import com.muwire.core.files.FileManager
|
|
||||||
import com.muwire.core.util.DataUtil
|
|
||||||
|
|
||||||
import groovy.json.JsonOutput
|
|
||||||
import groovy.json.JsonSlurper
|
|
||||||
import net.i2p.data.Base64
|
|
||||||
|
|
||||||
class MeshManager {
|
|
||||||
|
|
||||||
private final Map<InfoHash, Mesh> meshes = Collections.synchronizedMap(new HashMap<>())
|
|
||||||
private final FileManager fileManager
|
|
||||||
private final File home
|
|
||||||
private final MuWireSettings settings
|
|
||||||
|
|
||||||
MeshManager(FileManager fileManager, File home, MuWireSettings settings) {
|
|
||||||
this.fileManager = fileManager
|
|
||||||
this.home = home
|
|
||||||
this.settings = settings
|
|
||||||
load()
|
|
||||||
}
|
|
||||||
|
|
||||||
Mesh get(InfoHash infoHash) {
|
|
||||||
meshes.get(infoHash)
|
|
||||||
}
|
|
||||||
|
|
||||||
Mesh getOrCreate(InfoHash infoHash, int nPieces, boolean sequential) {
|
|
||||||
synchronized(meshes) {
|
|
||||||
if (meshes.containsKey(infoHash))
|
|
||||||
return meshes.get(infoHash)
|
|
||||||
float ratio = sequential ? 0f : settings.downloadSequentialRatio
|
|
||||||
Pieces pieces = new Pieces(nPieces, ratio)
|
|
||||||
if (fileManager.rootToFiles.containsKey(infoHash)) {
|
|
||||||
for (int i = 0; i < nPieces; i++)
|
|
||||||
pieces.markDownloaded(i)
|
|
||||||
}
|
|
||||||
Mesh rv = new Mesh(infoHash, pieces)
|
|
||||||
meshes.put(infoHash, rv)
|
|
||||||
return rv
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void onSourceDiscoveredEvent(SourceDiscoveredEvent e) {
|
|
||||||
Mesh mesh = meshes.get(e.infoHash)
|
|
||||||
if (mesh == null)
|
|
||||||
return
|
|
||||||
mesh.sources.add(e.source)
|
|
||||||
save()
|
|
||||||
}
|
|
||||||
|
|
||||||
private void save() {
|
|
||||||
File meshFile = new File(home, "mesh.json")
|
|
||||||
synchronized(meshes) {
|
|
||||||
meshFile.withPrintWriter { writer ->
|
|
||||||
meshes.values().each { mesh ->
|
|
||||||
def json = [:]
|
|
||||||
json.timestamp = System.currentTimeMillis()
|
|
||||||
json.infoHash = Base64.encode(mesh.infoHash.getRoot())
|
|
||||||
json.sources = mesh.sources.stream().map({it.toBase64()}).collect(Collectors.toList())
|
|
||||||
json.nPieces = mesh.pieces.nPieces
|
|
||||||
json.xHave = DataUtil.encodeXHave(mesh.pieces.downloaded, mesh.pieces.nPieces)
|
|
||||||
writer.println(JsonOutput.toJson(json))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void load() {
|
|
||||||
File meshFile = new File(home, "mesh.json")
|
|
||||||
if (!meshFile.exists())
|
|
||||||
return
|
|
||||||
long now = System.currentTimeMillis()
|
|
||||||
JsonSlurper slurper = new JsonSlurper()
|
|
||||||
meshFile.eachLine {
|
|
||||||
def json = slurper.parseText(it)
|
|
||||||
if (now - json.timestamp > settings.meshExpiration * 60 * 1000)
|
|
||||||
return
|
|
||||||
InfoHash infoHash = new InfoHash(Base64.decode(json.infoHash))
|
|
||||||
Pieces pieces = new Pieces(json.nPieces, settings.downloadSequentialRatio)
|
|
||||||
|
|
||||||
Mesh mesh = new Mesh(infoHash, pieces)
|
|
||||||
json.sources.each { source ->
|
|
||||||
Persona persona = new Persona(new ByteArrayInputStream(Base64.decode(source)))
|
|
||||||
mesh.sources.add(persona)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (json.xHave != null)
|
|
||||||
DataUtil.decodeXHave(json.xHave).each { pieces.markDownloaded(it) }
|
|
||||||
|
|
||||||
if (!mesh.sources.isEmpty())
|
|
||||||
meshes.put(infoHash, mesh)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,87 +0,0 @@
|
|||||||
package com.muwire.core.search
|
|
||||||
|
|
||||||
import com.muwire.core.Constants
|
|
||||||
import com.muwire.core.EventBus
|
|
||||||
import com.muwire.core.connection.Endpoint
|
|
||||||
import com.muwire.core.connection.I2PConnector
|
|
||||||
import com.muwire.core.util.DataUtil
|
|
||||||
|
|
||||||
import groovy.json.JsonSlurper
|
|
||||||
import groovy.util.logging.Log
|
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets
|
|
||||||
import java.util.concurrent.Executor
|
|
||||||
import java.util.concurrent.Executors
|
|
||||||
import java.util.logging.Level
|
|
||||||
import java.util.zip.GZIPInputStream
|
|
||||||
|
|
||||||
@Log
|
|
||||||
class BrowseManager {
|
|
||||||
|
|
||||||
private final I2PConnector connector
|
|
||||||
private final EventBus eventBus
|
|
||||||
|
|
||||||
private final Executor browserThread = Executors.newSingleThreadExecutor()
|
|
||||||
|
|
||||||
BrowseManager(I2PConnector connector, EventBus eventBus) {
|
|
||||||
this.connector = connector
|
|
||||||
this.eventBus = eventBus
|
|
||||||
}
|
|
||||||
|
|
||||||
void onUIBrowseEvent(UIBrowseEvent e) {
|
|
||||||
browserThread.execute({
|
|
||||||
Endpoint endpoint = null
|
|
||||||
try {
|
|
||||||
eventBus.publish(new BrowseStatusEvent(status : BrowseStatus.CONNECTING))
|
|
||||||
endpoint = connector.connect(e.host.destination)
|
|
||||||
OutputStream os = endpoint.getOutputStream()
|
|
||||||
os.write("BROWSE\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
|
||||||
|
|
||||||
InputStream is = endpoint.getInputStream()
|
|
||||||
String code = DataUtil.readTillRN(is)
|
|
||||||
if (!code.startsWith("200"))
|
|
||||||
throw new IOException("Invalid code $code")
|
|
||||||
|
|
||||||
// parse all headers
|
|
||||||
Map<String,String> headers = new HashMap<>()
|
|
||||||
String header
|
|
||||||
while((header = DataUtil.readTillRN(is)) != "" && headers.size() < Constants.MAX_HEADERS) {
|
|
||||||
int colon = header.indexOf(':')
|
|
||||||
if (colon == -1 || colon == header.length() - 1)
|
|
||||||
throw new IOException("invalid header $header")
|
|
||||||
String key = header.substring(0, colon)
|
|
||||||
String value = header.substring(colon + 1)
|
|
||||||
headers[key] = value.trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!headers.containsKey("Count"))
|
|
||||||
throw new IOException("No count header")
|
|
||||||
|
|
||||||
int results = Integer.parseInt(headers['Count'])
|
|
||||||
|
|
||||||
// at this stage, start pulling the results
|
|
||||||
eventBus.publish(new BrowseStatusEvent(status : BrowseStatus.FETCHING, totalResults : results))
|
|
||||||
|
|
||||||
JsonSlurper slurper = new JsonSlurper()
|
|
||||||
DataInputStream dis = new DataInputStream(new GZIPInputStream(is))
|
|
||||||
UUID uuid = UUID.randomUUID()
|
|
||||||
for (int i = 0; i < results; i++) {
|
|
||||||
int size = dis.readUnsignedShort()
|
|
||||||
byte [] tmp = new byte[size]
|
|
||||||
dis.readFully(tmp)
|
|
||||||
def json = slurper.parse(tmp)
|
|
||||||
UIResultEvent result = ResultsParser.parse(e.host, uuid, json)
|
|
||||||
eventBus.publish(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
eventBus.publish(new BrowseStatusEvent(status : BrowseStatus.FINISHED))
|
|
||||||
|
|
||||||
} catch (Exception bad) {
|
|
||||||
log.log(Level.WARNING, "browse failed", bad)
|
|
||||||
eventBus.publish(new BrowseStatusEvent(status : BrowseStatus.FAILED))
|
|
||||||
} finally {
|
|
||||||
endpoint?.close()
|
|
||||||
}
|
|
||||||
} as Runnable)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,5 +0,0 @@
|
|||||||
package com.muwire.core.search;
|
|
||||||
|
|
||||||
public enum BrowseStatus {
|
|
||||||
CONNECTING, FETCHING, FINISHED, FAILED
|
|
||||||
}
|
|
@@ -1,8 +0,0 @@
|
|||||||
package com.muwire.core.search
|
|
||||||
|
|
||||||
import com.muwire.core.Event
|
|
||||||
|
|
||||||
class BrowseStatusEvent extends Event {
|
|
||||||
BrowseStatus status
|
|
||||||
int totalResults
|
|
||||||
}
|
|
@@ -1,7 +1,5 @@
|
|||||||
package com.muwire.core.search
|
package com.muwire.core.search
|
||||||
|
|
||||||
import java.util.stream.Collectors
|
|
||||||
|
|
||||||
import javax.naming.directory.InvalidSearchControlsException
|
import javax.naming.directory.InvalidSearchControlsException
|
||||||
|
|
||||||
import com.muwire.core.InfoHash
|
import com.muwire.core.InfoHash
|
||||||
@@ -9,7 +7,6 @@ import com.muwire.core.Persona
|
|||||||
import com.muwire.core.util.DataUtil
|
import com.muwire.core.util.DataUtil
|
||||||
|
|
||||||
import net.i2p.data.Base64
|
import net.i2p.data.Base64
|
||||||
import net.i2p.data.Destination
|
|
||||||
|
|
||||||
class ResultsParser {
|
class ResultsParser {
|
||||||
public static UIResultEvent parse(Persona p, UUID uuid, def json) throws InvalidSearchResultException {
|
public static UIResultEvent parse(Persona p, UUID uuid, def json) throws InvalidSearchResultException {
|
||||||
@@ -61,7 +58,6 @@ class ResultsParser {
|
|||||||
size : size,
|
size : size,
|
||||||
infohash : parsedIH,
|
infohash : parsedIH,
|
||||||
pieceSize : pieceSize,
|
pieceSize : pieceSize,
|
||||||
sources : Collections.emptySet(),
|
|
||||||
uuid : uuid)
|
uuid : uuid)
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new InvalidSearchResultException("parsing search result failed",e)
|
throw new InvalidSearchResultException("parsing search result failed",e)
|
||||||
@@ -86,27 +82,11 @@ class ResultsParser {
|
|||||||
if (infoHash.length != InfoHash.SIZE)
|
if (infoHash.length != InfoHash.SIZE)
|
||||||
throw new InvalidSearchResultException("invalid infohash size $infoHash.length")
|
throw new InvalidSearchResultException("invalid infohash size $infoHash.length")
|
||||||
int pieceSize = json.pieceSize
|
int pieceSize = json.pieceSize
|
||||||
|
|
||||||
Set<Destination> sources = Collections.emptySet()
|
|
||||||
if (json.sources != null)
|
|
||||||
sources = json.sources.stream().map({new Destination(it)}).collect(Collectors.toSet())
|
|
||||||
|
|
||||||
String comment = null
|
|
||||||
if (json.comment != null)
|
|
||||||
comment = DataUtil.readi18nString(Base64.decode(json.comment))
|
|
||||||
|
|
||||||
boolean browse = false
|
|
||||||
if (json.browse != null)
|
|
||||||
browse = json.browse
|
|
||||||
|
|
||||||
return new UIResultEvent( sender : p,
|
return new UIResultEvent( sender : p,
|
||||||
name : name,
|
name : name,
|
||||||
size : size,
|
size : size,
|
||||||
infohash : new InfoHash(infoHash),
|
infohash : new InfoHash(infoHash),
|
||||||
pieceSize : pieceSize,
|
pieceSize : pieceSize,
|
||||||
sources : sources,
|
|
||||||
comment : comment,
|
|
||||||
browse : browse,
|
|
||||||
uuid: uuid)
|
uuid: uuid)
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new InvalidSearchResultException("parsing search result failed",e)
|
throw new InvalidSearchResultException("parsing search result failed",e)
|
||||||
|
@@ -4,7 +4,6 @@ import com.muwire.core.SharedFile
|
|||||||
import com.muwire.core.connection.Endpoint
|
import com.muwire.core.connection.Endpoint
|
||||||
import com.muwire.core.connection.I2PConnector
|
import com.muwire.core.connection.I2PConnector
|
||||||
import com.muwire.core.files.FileHasher
|
import com.muwire.core.files.FileHasher
|
||||||
import com.muwire.core.util.DataUtil
|
|
||||||
import com.muwire.core.Persona
|
import com.muwire.core.Persona
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
@@ -12,14 +11,9 @@ import java.util.concurrent.Executor
|
|||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.ThreadFactory
|
import java.util.concurrent.ThreadFactory
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import java.util.logging.Level
|
|
||||||
import java.util.stream.Collectors
|
|
||||||
import java.util.zip.GZIPOutputStream
|
|
||||||
|
|
||||||
import com.muwire.core.DownloadedFile
|
|
||||||
import com.muwire.core.EventBus
|
import com.muwire.core.EventBus
|
||||||
import com.muwire.core.InfoHash
|
import com.muwire.core.InfoHash
|
||||||
import com.muwire.core.MuWireSettings
|
|
||||||
|
|
||||||
import groovy.json.JsonOutput
|
import groovy.json.JsonOutput
|
||||||
import groovy.util.logging.Log
|
import groovy.util.logging.Log
|
||||||
@@ -45,46 +39,33 @@ class ResultsSender {
|
|||||||
private final I2PConnector connector
|
private final I2PConnector connector
|
||||||
private final Persona me
|
private final Persona me
|
||||||
private final EventBus eventBus
|
private final EventBus eventBus
|
||||||
private final MuWireSettings settings
|
|
||||||
|
|
||||||
ResultsSender(EventBus eventBus, I2PConnector connector, Persona me, MuWireSettings settings) {
|
ResultsSender(EventBus eventBus, I2PConnector connector, Persona me) {
|
||||||
this.connector = connector;
|
this.connector = connector;
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.me = me
|
this.me = me
|
||||||
this.settings = settings
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendResults(UUID uuid, SharedFile[] results, Destination target, boolean oobInfohash, boolean compressedResults) {
|
void sendResults(UUID uuid, SharedFile[] results, Destination target, boolean oobInfohash) {
|
||||||
log.info("Sending $results.length results for uuid $uuid to ${target.toBase32()} oobInfohash : $oobInfohash")
|
log.info("Sending $results.length results for uuid $uuid to ${target.toBase32()} oobInfohash : $oobInfohash")
|
||||||
if (target.equals(me.destination)) {
|
if (target.equals(me.destination)) {
|
||||||
def uiResultEvents = []
|
|
||||||
results.each {
|
results.each {
|
||||||
long length = it.getFile().length()
|
long length = it.getFile().length()
|
||||||
int pieceSize = it.getPieceSize()
|
int pieceSize = it.getPieceSize()
|
||||||
if (pieceSize == 0)
|
if (pieceSize == 0)
|
||||||
pieceSize = FileHasher.getPieceSize(length)
|
pieceSize = FileHasher.getPieceSize(length)
|
||||||
Set<Destination> suggested = Collections.emptySet()
|
|
||||||
if (it instanceof DownloadedFile)
|
|
||||||
suggested = it.sources
|
|
||||||
def comment = null
|
|
||||||
if (it.getComment() != null) {
|
|
||||||
comment = DataUtil.readi18nString(Base64.decode(it.getComment()))
|
|
||||||
}
|
|
||||||
def uiResultEvent = new UIResultEvent( sender : me,
|
def uiResultEvent = new UIResultEvent( sender : me,
|
||||||
name : it.getFile().getName(),
|
name : it.getFile().getName(),
|
||||||
size : length,
|
size : length,
|
||||||
infohash : it.getInfoHash(),
|
infohash : it.getInfoHash(),
|
||||||
pieceSize : pieceSize,
|
pieceSize : pieceSize,
|
||||||
uuid : uuid,
|
uuid : uuid
|
||||||
sources : suggested,
|
|
||||||
comment : comment
|
|
||||||
)
|
)
|
||||||
uiResultEvents << uiResultEvent
|
eventBus.publish(uiResultEvent)
|
||||||
}
|
}
|
||||||
eventBus.publish(new UIResultBatchEvent(uuid : uuid, results : uiResultEvents))
|
|
||||||
} else {
|
} else {
|
||||||
executor.execute(new ResultSendJob(uuid : uuid, results : results,
|
executor.execute(new ResultSendJob(uuid : uuid, results : results,
|
||||||
target: target, oobInfohash : oobInfohash, compressedResults : compressedResults))
|
target: target, oobInfohash : oobInfohash))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,14 +74,12 @@ class ResultsSender {
|
|||||||
SharedFile [] results
|
SharedFile [] results
|
||||||
Destination target
|
Destination target
|
||||||
boolean oobInfohash
|
boolean oobInfohash
|
||||||
boolean compressedResults
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
byte [] tmp = new byte[InfoHash.SIZE]
|
||||||
JsonOutput jsonOutput = new JsonOutput()
|
JsonOutput jsonOutput = new JsonOutput()
|
||||||
Endpoint endpoint = null;
|
Endpoint endpoint = null;
|
||||||
if (!compressedResults) {
|
|
||||||
try {
|
try {
|
||||||
endpoint = connector.connect(target)
|
endpoint = connector.connect(target)
|
||||||
DataOutputStream os = new DataOutputStream(endpoint.getOutputStream())
|
DataOutputStream os = new DataOutputStream(endpoint.getOutputStream())
|
||||||
@@ -108,43 +87,7 @@ class ResultsSender {
|
|||||||
me.write(os)
|
me.write(os)
|
||||||
os.writeShort((short)results.length)
|
os.writeShort((short)results.length)
|
||||||
results.each {
|
results.each {
|
||||||
def obj = sharedFileToObj(it, settings.browseFiles)
|
byte [] name = it.getFile().getName().getBytes(StandardCharsets.UTF_8)
|
||||||
def json = jsonOutput.toJson(obj)
|
|
||||||
os.writeShort((short)json.length())
|
|
||||||
os.write(json.getBytes(StandardCharsets.US_ASCII))
|
|
||||||
}
|
|
||||||
os.flush()
|
|
||||||
} finally {
|
|
||||||
endpoint?.close()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
endpoint = connector.connect(target)
|
|
||||||
OutputStream os = endpoint.getOutputStream()
|
|
||||||
os.write("RESULTS $uuid\r\n".getBytes(StandardCharsets.US_ASCII))
|
|
||||||
os.write("Sender: ${me.toBase64()}\r\n".getBytes(StandardCharsets.US_ASCII))
|
|
||||||
os.write("Count: $results.length\r\n".getBytes(StandardCharsets.US_ASCII))
|
|
||||||
os.write("\r\n".getBytes(StandardCharsets.US_ASCII))
|
|
||||||
DataOutputStream dos = new DataOutputStream(new GZIPOutputStream(os))
|
|
||||||
results.each {
|
|
||||||
def obj = sharedFileToObj(it, settings.browseFiles)
|
|
||||||
def json = jsonOutput.toJson(obj)
|
|
||||||
dos.writeShort((short)json.length())
|
|
||||||
dos.write(json.getBytes(StandardCharsets.US_ASCII))
|
|
||||||
}
|
|
||||||
dos.close()
|
|
||||||
} finally {
|
|
||||||
endpoint?.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.log(Level.WARNING, "problem sending results",e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static def sharedFileToObj(SharedFile sf, boolean browseFiles) {
|
|
||||||
byte [] name = sf.getFile().getName().getBytes(StandardCharsets.UTF_8)
|
|
||||||
def baos = new ByteArrayOutputStream()
|
def baos = new ByteArrayOutputStream()
|
||||||
def daos = new DataOutputStream(baos)
|
def daos = new DataOutputStream(baos)
|
||||||
daos.writeShort((short) name.length)
|
daos.writeShort((short) name.length)
|
||||||
@@ -153,19 +96,28 @@ class ResultsSender {
|
|||||||
String encodedName = Base64.encode(baos.toByteArray())
|
String encodedName = Base64.encode(baos.toByteArray())
|
||||||
def obj = [:]
|
def obj = [:]
|
||||||
obj.type = "Result"
|
obj.type = "Result"
|
||||||
obj.version = 2
|
obj.version = oobInfohash ? 2 : 1
|
||||||
obj.name = encodedName
|
obj.name = encodedName
|
||||||
obj.infohash = Base64.encode(sf.getInfoHash().getRoot())
|
obj.infohash = Base64.encode(it.getInfoHash().getRoot())
|
||||||
obj.size = sf.getCachedLength()
|
obj.size = it.getFile().length()
|
||||||
obj.pieceSize = sf.getPieceSize()
|
obj.pieceSize = it.getPieceSize()
|
||||||
|
if (!oobInfohash) {
|
||||||
if (sf instanceof DownloadedFile)
|
byte [] hashList = it.getInfoHash().getHashList()
|
||||||
obj.sources = sf.sources.stream().map({dest -> dest.toBase64()}).collect(Collectors.toSet())
|
def hashListB64 = []
|
||||||
|
for (int i = 0; i < hashList.length / InfoHash.SIZE; i++) {
|
||||||
if (sf.getComment() != null)
|
System.arraycopy(hashList, InfoHash.SIZE * i, tmp, 0, InfoHash.SIZE)
|
||||||
obj.comment = sf.getComment()
|
hashListB64 << Base64.encode(tmp)
|
||||||
|
}
|
||||||
obj.browse = browseFiles
|
obj.hashList = hashListB64
|
||||||
obj
|
}
|
||||||
|
def json = jsonOutput.toJson(obj)
|
||||||
|
os.writeShort((short)json.length())
|
||||||
|
os.write(json.getBytes(StandardCharsets.US_ASCII))
|
||||||
|
}
|
||||||
|
os.flush()
|
||||||
|
} finally {
|
||||||
|
endpoint?.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,13 +9,11 @@ class SearchEvent extends Event {
|
|||||||
byte [] searchHash
|
byte [] searchHash
|
||||||
UUID uuid
|
UUID uuid
|
||||||
boolean oobInfohash
|
boolean oobInfohash
|
||||||
boolean searchComments
|
|
||||||
boolean compressedResults
|
|
||||||
|
|
||||||
String toString() {
|
String toString() {
|
||||||
def infoHash = null
|
def infoHash = null
|
||||||
if (searchHash != null)
|
if (searchHash != null)
|
||||||
infoHash = new InfoHash(searchHash)
|
infoHash = new InfoHash(searchHash)
|
||||||
"searchTerms: $searchTerms searchHash:$infoHash, uuid:$uuid oobInfohash:$oobInfohash searchComments:$searchComments compressedResults:$compressedResults"
|
"searchTerms: $searchTerms searchHash:$infoHash, uuid:$uuid oobInfohash:$oobInfohash"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
package com.muwire.core.search
|
package com.muwire.core.search
|
||||||
|
|
||||||
import com.muwire.core.SplitPattern
|
import com.muwire.core.Constants
|
||||||
|
|
||||||
class SearchIndex {
|
class SearchIndex {
|
||||||
|
|
||||||
@@ -32,11 +32,8 @@ class SearchIndex {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static String[] split(String source) {
|
private static String[] split(String source) {
|
||||||
source = source.replaceAll(SplitPattern.SPLIT_PATTERN, " ").toLowerCase()
|
source = source.replaceAll(Constants.SPLIT_PATTERN, " ").toLowerCase()
|
||||||
String [] split = source.split(" ")
|
source.split(" ")
|
||||||
def rv = []
|
|
||||||
split.each { if (it.length() > 0) rv << it }
|
|
||||||
rv.toArray(new String[0])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String[] search(List<String> terms) {
|
String[] search(List<String> terms) {
|
||||||
|
@@ -44,7 +44,7 @@ public class SearchManager {
|
|||||||
log.info("No results for search uuid $event.uuid")
|
log.info("No results for search uuid $event.uuid")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
resultsSender.sendResults(event.uuid, event.results, target, event.searchEvent.oobInfohash, event.searchEvent.compressedResults)
|
resultsSender.sendResults(event.uuid, event.results, target, event.searchEvent.oobInfohash)
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasLocalSearch(UUID uuid) {
|
boolean hasLocalSearch(UUID uuid) {
|
||||||
|
@@ -1,8 +0,0 @@
|
|||||||
package com.muwire.core.search
|
|
||||||
|
|
||||||
import com.muwire.core.Event
|
|
||||||
import com.muwire.core.Persona
|
|
||||||
|
|
||||||
class UIBrowseEvent extends Event {
|
|
||||||
Persona host
|
|
||||||
}
|
|
@@ -1,8 +0,0 @@
|
|||||||
package com.muwire.core.search
|
|
||||||
|
|
||||||
import com.muwire.core.Event
|
|
||||||
|
|
||||||
class UIResultBatchEvent extends Event {
|
|
||||||
UUID uuid
|
|
||||||
UIResultEvent[] results
|
|
||||||
}
|
|
@@ -4,18 +4,13 @@ import com.muwire.core.Event
|
|||||||
import com.muwire.core.InfoHash
|
import com.muwire.core.InfoHash
|
||||||
import com.muwire.core.Persona
|
import com.muwire.core.Persona
|
||||||
|
|
||||||
import net.i2p.data.Destination
|
|
||||||
|
|
||||||
class UIResultEvent extends Event {
|
class UIResultEvent extends Event {
|
||||||
Persona sender
|
Persona sender
|
||||||
Set<Destination> sources
|
|
||||||
UUID uuid
|
UUID uuid
|
||||||
String name
|
String name
|
||||||
long size
|
long size
|
||||||
InfoHash infohash
|
InfoHash infohash
|
||||||
int pieceSize
|
int pieceSize
|
||||||
String comment
|
|
||||||
boolean browse
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
|
@@ -1,31 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,161 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,9 +0,0 @@
|
|||||||
package com.muwire.core.trust
|
|
||||||
|
|
||||||
import com.muwire.core.Event
|
|
||||||
import com.muwire.core.Persona
|
|
||||||
|
|
||||||
class TrustSubscriptionEvent extends Event {
|
|
||||||
Persona persona
|
|
||||||
boolean subscribe
|
|
||||||
}
|
|
@@ -1,7 +0,0 @@
|
|||||||
package com.muwire.core.trust
|
|
||||||
|
|
||||||
import com.muwire.core.Event
|
|
||||||
|
|
||||||
class TrustSubscriptionUpdatedEvent extends Event {
|
|
||||||
RemoteTrustList trustList
|
|
||||||
}
|
|
@@ -3,15 +3,7 @@ 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
|
||||||
@@ -21,7 +13,6 @@ 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
|
||||||
@@ -30,24 +21,16 @@ 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
|
||||||
|
|
||||||
private volatile InfoHash updateInfoHash
|
UpdateClient(EventBus eventBus, I2PSession session, String myVersion, MuWireSettings settings) {
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,24 +43,6 @@ 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) {
|
||||||
@@ -141,32 +106,8 @@ class UpdateClient {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
String infoHash
|
|
||||||
if (settings.updateType == "jar") {
|
|
||||||
infoHash = payload.infoHash
|
|
||||||
} else
|
|
||||||
infoHash = payload[settings.updateType]
|
|
||||||
|
|
||||||
|
|
||||||
if (!settings.autoDownloadUpdate) {
|
|
||||||
log.info("new version $payload.version available, publishing event")
|
log.info("new version $payload.version available, publishing event")
|
||||||
eventBus.publish(new UpdateAvailableEvent(version : payload.version, signer : payload.signer, infoHash : infoHash))
|
eventBus.publish(new UpdateAvailableEvent(version : payload.version, signer : payload.signer, infoHash : payload.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)
|
||||||
|
@@ -1,8 +0,0 @@
|
|||||||
package com.muwire.core.update
|
|
||||||
|
|
||||||
import com.muwire.core.Event
|
|
||||||
|
|
||||||
class UpdateDownloadedEvent extends Event {
|
|
||||||
String version
|
|
||||||
String signer
|
|
||||||
}
|
|
@@ -3,5 +3,5 @@ package com.muwire.core.update
|
|||||||
import net.i2p.data.Destination
|
import net.i2p.data.Destination
|
||||||
|
|
||||||
class UpdateServers {
|
class UpdateServers {
|
||||||
static final Destination UPDATE_SERVER = new Destination("VJYAiCPZHNLraWvLkeRLxRiT4PHAqNqRO1nH240r7u1noBw8Pa~-lJOhKR7CccPkEN8ejSi4H6XjqKYLC8BKLVLeOgnAbedUVx81MV7DETPDdPEGV4RVu6YDFri7-tJOeqauGHxtlXT44YWuR69xKrTG3u4~iTWgxKnlBDht9Q3aVpSPFD2KqEizfVxolqXI0zmAZ2xMi8jfl0oe4GbgHrD9hR2FYj6yKfdqcUgHVobY4kDdJt-u31QqwWdsQMEj8Y3tR2XcNaITEVPiAjoKgBrYwB4jddWPNaT4XdHz76d9p9Iqes7dhOKq3OKpk6kg-bfIKiEOiA1mY49fn5h8pNShTqV7QBhh4CE4EDT3Szl~WsLdrlHUKJufSi7erEMh3coF7HORpF1wah2Xw7q470t~b8dKGKi7N7xQsqhGruDm66PH9oE9Kt9WBVBq2zORdPRtRM61I7EnrwDlbOkL0y~XpvQ3JKUQKdBQ3QsOJt8CHlhHHXMMbvqhntR61RSDBQAEAAcAAA==")
|
static final Destination UPDATE_SERVER = new Destination("pSWieSRB3czCl3Zz4WpKp4Z8tjv-05zbogRDS7SEnKcSdWOupVwjzQ92GsgQh1VqgoSRk1F8dpZOnHxxz5HFy9D7ri0uFdkMyXdSKoB7IgkkvCfTAyEmeaPwSYnurF3Zk7u286E7YG2rZkQZgJ77tow7ZS0mxFB7Z0Ti-VkZ9~GeGePW~howwNm4iSQACZA0DyTpI8iv5j4I0itPCQRgaGziob~Vfvjk49nd8N4jtaDGo9cEcafikVzQ2OgBgYWL6LRbrrItwuGqsDvITUHWaElUYIDhRQYUq8gYiUA6rwAJputfhFU0J7lIxFR9vVY7YzRvcFckfr0DNI4VQVVlPnRPkUxQa--BlldMaCIppWugjgKLwqiSiHywKpSMlBWgY2z1ry4ueEBo1WEP-mEf88wRk4cFQBCKtctCQnIG2GsnATqTl-VGUAsuzeNWZiFSwXiTy~gQ094yWx-K06fFZUDt4CMiLZVhGlixiInD~34FCRC9LVMtFcqiFB2M-Ql2AAAA")
|
||||||
}
|
}
|
||||||
|
@@ -2,5 +2,4 @@ package com.muwire.core.upload
|
|||||||
|
|
||||||
class ContentRequest extends Request {
|
class ContentRequest extends Request {
|
||||||
Range range
|
Range range
|
||||||
int have
|
|
||||||
}
|
}
|
||||||
|
@@ -5,58 +5,34 @@ 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
|
||||||
import java.nio.file.StandardOpenOption
|
import java.nio.file.StandardOpenOption
|
||||||
import java.util.stream.Collectors
|
|
||||||
|
|
||||||
import com.muwire.core.Persona
|
|
||||||
import com.muwire.core.connection.Endpoint
|
import com.muwire.core.connection.Endpoint
|
||||||
import com.muwire.core.mesh.Mesh
|
|
||||||
import com.muwire.core.util.DataUtil
|
|
||||||
|
|
||||||
import net.i2p.data.Destination
|
|
||||||
|
|
||||||
class ContentUploader extends Uploader {
|
class ContentUploader extends Uploader {
|
||||||
|
|
||||||
private final File file
|
private final File file
|
||||||
private final ContentRequest request
|
private final ContentRequest request
|
||||||
private final Mesh mesh
|
|
||||||
private final int pieceSize
|
|
||||||
|
|
||||||
ContentUploader(File file, ContentRequest request, Endpoint endpoint, Mesh mesh, int pieceSize) {
|
ContentUploader(File file, ContentRequest request, Endpoint endpoint) {
|
||||||
super(endpoint)
|
super(endpoint)
|
||||||
this.file = file
|
this.file = file
|
||||||
this.request = request
|
this.request = request
|
||||||
this.mesh = mesh
|
|
||||||
this.pieceSize = pieceSize
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void respond() {
|
void respond() {
|
||||||
OutputStream os = endpoint.getOutputStream()
|
OutputStream os = endpoint.getOutputStream()
|
||||||
Range range = request.getRange()
|
Range range = request.getRange()
|
||||||
boolean satisfiable = true
|
if (range.start >= file.length() || range.end >= file.length()) {
|
||||||
final long length = file.length()
|
os.write("416 Range Not Satisfiable\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
if (range.start >= length || range.end >= length)
|
|
||||||
satisfiable = false
|
|
||||||
if (satisfiable) {
|
|
||||||
int startPiece = range.start / (0x1 << pieceSize)
|
|
||||||
int endPiece = range.end / (0x1 << pieceSize)
|
|
||||||
for (int i = startPiece; i <= endPiece; i++)
|
|
||||||
satisfiable &= mesh.pieces.isDownloaded(i)
|
|
||||||
}
|
|
||||||
if (!satisfiable) {
|
|
||||||
os.write("416 Range Not Satisfiable\r\n".getBytes(StandardCharsets.US_ASCII))
|
|
||||||
writeMesh(request.downloader)
|
|
||||||
os.write("\r\n".getBytes(StandardCharsets.US_ASCII))
|
|
||||||
os.flush()
|
os.flush()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
os.write("200 OK\r\n".getBytes(StandardCharsets.US_ASCII))
|
os.write("200 OK\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
os.write("Content-Range: $range.start-$range.end\r\n".getBytes(StandardCharsets.US_ASCII))
|
os.write("Content-Range: $range.start-$range.end\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
writeMesh(request.downloader)
|
|
||||||
os.write("\r\n".getBytes(StandardCharsets.US_ASCII))
|
|
||||||
|
|
||||||
FileChannel channel = null
|
FileChannel channel
|
||||||
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,55 +48,7 @@ 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeMesh(Persona toExclude) {
|
|
||||||
String xHave = DataUtil.encodeXHave(mesh.pieces.getDownloaded(), mesh.pieces.nPieces)
|
|
||||||
endpoint.getOutputStream().write("X-Have: $xHave\r\n".getBytes(StandardCharsets.US_ASCII))
|
|
||||||
|
|
||||||
Set<Persona> sources = mesh.getRandom(9, toExclude)
|
|
||||||
if (!sources.isEmpty()) {
|
|
||||||
String xAlts = sources.stream().map({ it.toBase64() }).collect(Collectors.joining(","))
|
|
||||||
endpoint.getOutputStream().write("X-Alt: $xAlts\r\n".getBytes(StandardCharsets.US_ASCII))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return file.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized int getProgress() {
|
|
||||||
if (mapped == null)
|
|
||||||
return 0
|
|
||||||
int position = mapped.position()
|
|
||||||
int total = request.getRange().end - request.getRange().start
|
|
||||||
(int)(position * 100.0 / total)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getDownloader() {
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -6,8 +6,6 @@ import java.nio.charset.StandardCharsets
|
|||||||
import com.muwire.core.InfoHash
|
import com.muwire.core.InfoHash
|
||||||
import com.muwire.core.connection.Endpoint
|
import com.muwire.core.connection.Endpoint
|
||||||
|
|
||||||
import net.i2p.data.Base64
|
|
||||||
|
|
||||||
class HashListUploader extends Uploader {
|
class HashListUploader extends Uploader {
|
||||||
private final InfoHash infoHash
|
private final InfoHash infoHash
|
||||||
private final HashListRequest request
|
private final HashListRequest request
|
||||||
@@ -16,7 +14,6 @@ class HashListUploader extends Uploader {
|
|||||||
super(endpoint)
|
super(endpoint)
|
||||||
this.infoHash = infoHash
|
this.infoHash = infoHash
|
||||||
mapped = ByteBuffer.wrap(infoHash.getHashList())
|
mapped = ByteBuffer.wrap(infoHash.getHashList())
|
||||||
this.request = request
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void respond() {
|
void respond() {
|
||||||
@@ -35,34 +32,4 @@ class HashListUploader extends Uploader {
|
|||||||
}
|
}
|
||||||
endpoint.getOutputStream().flush()
|
endpoint.getOutputStream().flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return "Hash list for " + Base64.encode(infoHash.getRoot());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized int getProgress() {
|
|
||||||
(int)(mapped.position() * 100.0 / mapped.capacity())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getDownloader() {
|
|
||||||
request.downloader.getHumanReadableName()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getDonePieces() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getTotalPieces() {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getTotalSize() {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,6 @@ import java.nio.charset.StandardCharsets
|
|||||||
import com.muwire.core.Constants
|
import com.muwire.core.Constants
|
||||||
import com.muwire.core.InfoHash
|
import com.muwire.core.InfoHash
|
||||||
import com.muwire.core.Persona
|
import com.muwire.core.Persona
|
||||||
import com.muwire.core.util.DataUtil
|
|
||||||
|
|
||||||
import groovy.util.logging.Log
|
import groovy.util.logging.Log
|
||||||
import net.i2p.data.Base64
|
import net.i2p.data.Base64
|
||||||
@@ -49,14 +48,8 @@ class Request {
|
|||||||
def decoded = Base64.decode(encoded)
|
def decoded = Base64.decode(encoded)
|
||||||
downloader = new Persona(new ByteArrayInputStream(decoded))
|
downloader = new Persona(new ByteArrayInputStream(decoded))
|
||||||
}
|
}
|
||||||
|
|
||||||
int have = 0
|
|
||||||
if (headers.containsKey("X-Have")) {
|
|
||||||
def encoded = headers["X-Have"].trim()
|
|
||||||
have = DataUtil.decodeXHave(encoded).size()
|
|
||||||
}
|
|
||||||
new ContentRequest( infoHash : infoHash, range : new Range(start, end),
|
new ContentRequest( infoHash : infoHash, range : new Range(start, end),
|
||||||
headers : headers, downloader : downloader, have : have)
|
headers : headers, downloader : downloader)
|
||||||
}
|
}
|
||||||
|
|
||||||
static Request parseHashListRequest(InfoHash infoHash, InputStream is) throws IOException {
|
static Request parseHashListRequest(InfoHash infoHash, InputStream is) throws IOException {
|
||||||
|
@@ -6,12 +6,7 @@ import com.muwire.core.EventBus
|
|||||||
import com.muwire.core.InfoHash
|
import com.muwire.core.InfoHash
|
||||||
import com.muwire.core.SharedFile
|
import com.muwire.core.SharedFile
|
||||||
import com.muwire.core.connection.Endpoint
|
import com.muwire.core.connection.Endpoint
|
||||||
import com.muwire.core.download.DownloadManager
|
|
||||||
import com.muwire.core.download.Downloader
|
|
||||||
import com.muwire.core.download.SourceDiscoveredEvent
|
|
||||||
import com.muwire.core.files.FileManager
|
import com.muwire.core.files.FileManager
|
||||||
import com.muwire.core.mesh.Mesh
|
|
||||||
import com.muwire.core.mesh.MeshManager
|
|
||||||
|
|
||||||
import groovy.util.logging.Log
|
import groovy.util.logging.Log
|
||||||
import net.i2p.data.Base64
|
import net.i2p.data.Base64
|
||||||
@@ -20,17 +15,12 @@ import net.i2p.data.Base64
|
|||||||
public class UploadManager {
|
public class UploadManager {
|
||||||
private final EventBus eventBus
|
private final EventBus eventBus
|
||||||
private final FileManager fileManager
|
private final FileManager fileManager
|
||||||
private final MeshManager meshManager
|
|
||||||
private final DownloadManager downloadManager
|
|
||||||
|
|
||||||
public UploadManager() {}
|
public UploadManager() {}
|
||||||
|
|
||||||
public UploadManager(EventBus eventBus, FileManager fileManager,
|
public UploadManager(EventBus eventBus, FileManager fileManager) {
|
||||||
MeshManager meshManager, DownloadManager downloadManager) {
|
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.fileManager = fileManager
|
this.fileManager = fileManager
|
||||||
this.meshManager = meshManager
|
|
||||||
this.downloadManager = downloadManager
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void processGET(Endpoint e) throws IOException {
|
public void processGET(Endpoint e) throws IOException {
|
||||||
@@ -54,10 +44,8 @@ public class UploadManager {
|
|||||||
log.info("Responding to upload request for root $infoHashString")
|
log.info("Responding to upload request for root $infoHashString")
|
||||||
|
|
||||||
byte [] infoHashRoot = Base64.decode(infoHashString)
|
byte [] infoHashRoot = Base64.decode(infoHashString)
|
||||||
InfoHash infoHash = new InfoHash(infoHashRoot)
|
|
||||||
Set<SharedFile> sharedFiles = fileManager.getSharedFiles(infoHashRoot)
|
Set<SharedFile> sharedFiles = fileManager.getSharedFiles(infoHashRoot)
|
||||||
Downloader downloader = downloadManager.downloaders.get(infoHash)
|
if (sharedFiles == null || sharedFiles.isEmpty()) {
|
||||||
if (downloader == null && (sharedFiles == null || sharedFiles.isEmpty())) {
|
|
||||||
log.info "file not found"
|
log.info "file not found"
|
||||||
e.getOutputStream().write("404 File Not Found\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
e.getOutputStream().write("404 File Not Found\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
e.getOutputStream().flush()
|
e.getOutputStream().flush()
|
||||||
@@ -73,31 +61,13 @@ public class UploadManager {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentRequest request = Request.parseContentRequest(infoHash, e.getInputStream())
|
Request request = Request.parseContentRequest(new InfoHash(infoHashRoot), e.getInputStream())
|
||||||
if (request.downloader != null && request.downloader.destination != e.destination) {
|
if (request.downloader != null && request.downloader.destination != e.destination) {
|
||||||
log.info("Downloader persona doesn't match their destination")
|
log.info("Downloader persona doesn't match their destination")
|
||||||
e.close()
|
e.close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
Uploader uploader = new ContentUploader(sharedFiles.iterator().next().file, request, e)
|
||||||
if (request.have > 0)
|
|
||||||
eventBus.publish(new SourceDiscoveredEvent(infoHash : request.infoHash, source : request.downloader))
|
|
||||||
|
|
||||||
Mesh mesh
|
|
||||||
File file
|
|
||||||
int pieceSize
|
|
||||||
if (downloader != null) {
|
|
||||||
mesh = meshManager.get(infoHash)
|
|
||||||
file = downloader.incompleteFile
|
|
||||||
pieceSize = downloader.pieceSizePow2
|
|
||||||
} else {
|
|
||||||
SharedFile sharedFile = sharedFiles.iterator().next();
|
|
||||||
mesh = meshManager.getOrCreate(request.infoHash, sharedFile.NPieces, false)
|
|
||||||
file = sharedFile.file
|
|
||||||
pieceSize = sharedFile.pieceSize
|
|
||||||
}
|
|
||||||
|
|
||||||
Uploader uploader = new ContentUploader(file, request, e, mesh, pieceSize)
|
|
||||||
eventBus.publish(new UploadEvent(uploader : uploader))
|
eventBus.publish(new UploadEvent(uploader : uploader))
|
||||||
try {
|
try {
|
||||||
uploader.respond()
|
uploader.respond()
|
||||||
@@ -115,10 +85,8 @@ public class UploadManager {
|
|||||||
log.info("Responding to hashlist request for root $infoHashString")
|
log.info("Responding to hashlist request for root $infoHashString")
|
||||||
|
|
||||||
byte [] infoHashRoot = Base64.decode(infoHashString)
|
byte [] infoHashRoot = Base64.decode(infoHashString)
|
||||||
InfoHash infoHash = new InfoHash(infoHashRoot)
|
|
||||||
Downloader downloader = downloadManager.downloaders.get(infoHash)
|
|
||||||
Set<SharedFile> sharedFiles = fileManager.getSharedFiles(infoHashRoot)
|
Set<SharedFile> sharedFiles = fileManager.getSharedFiles(infoHashRoot)
|
||||||
if (downloader == null && (sharedFiles == null || sharedFiles.isEmpty())) {
|
if (sharedFiles == null || sharedFiles.isEmpty()) {
|
||||||
log.info "file not found"
|
log.info "file not found"
|
||||||
e.getOutputStream().write("404 File Not Found\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
e.getOutputStream().write("404 File Not Found\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
e.getOutputStream().flush()
|
e.getOutputStream().flush()
|
||||||
@@ -134,30 +102,13 @@ public class UploadManager {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
Request request = Request.parseHashListRequest(infoHash, e.getInputStream())
|
Request request = Request.parseHashListRequest(new InfoHash(infoHashRoot), e.getInputStream())
|
||||||
if (request.downloader != null && request.downloader.destination != e.destination) {
|
if (request.downloader != null && request.downloader.destination != e.destination) {
|
||||||
log.info("Downloader persona doesn't match their destination")
|
log.info("Downloader persona doesn't match their destination")
|
||||||
e.close()
|
e.close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
Uploader uploader = new HashListUploader(e, sharedFiles.iterator().next().infoHash, request)
|
||||||
InfoHash fullInfoHash
|
|
||||||
if (downloader == null) {
|
|
||||||
fullInfoHash = sharedFiles.iterator().next().infoHash
|
|
||||||
} else {
|
|
||||||
byte [] hashList = downloader.getInfoHash().getHashList()
|
|
||||||
if (hashList != null && hashList.length > 0)
|
|
||||||
fullInfoHash = downloader.getInfoHash()
|
|
||||||
else {
|
|
||||||
log.info("infohash not found in downloader")
|
|
||||||
e.getOutputStream().write("404 File Not Found\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
|
||||||
e.getOutputStream().flush()
|
|
||||||
e.close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Uploader uploader = new HashListUploader(e, fullInfoHash, request)
|
|
||||||
eventBus.publish(new UploadEvent(uploader : uploader))
|
eventBus.publish(new UploadEvent(uploader : uploader))
|
||||||
try {
|
try {
|
||||||
uploader.respond()
|
uploader.respond()
|
||||||
@@ -179,10 +130,8 @@ public class UploadManager {
|
|||||||
log.info("Responding to upload request for root $infoHashString")
|
log.info("Responding to upload request for root $infoHashString")
|
||||||
|
|
||||||
infoHashRoot = Base64.decode(infoHashString)
|
infoHashRoot = Base64.decode(infoHashString)
|
||||||
infoHash = new InfoHash(infoHashRoot)
|
|
||||||
sharedFiles = fileManager.getSharedFiles(infoHashRoot)
|
sharedFiles = fileManager.getSharedFiles(infoHashRoot)
|
||||||
downloader = downloadManager.downloaders.get(infoHash)
|
if (sharedFiles == null || sharedFiles.isEmpty()) {
|
||||||
if (downloader == null && (sharedFiles == null || sharedFiles.isEmpty())) {
|
|
||||||
log.info "file not found"
|
log.info "file not found"
|
||||||
e.getOutputStream().write("404 File Not Found\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
e.getOutputStream().write("404 File Not Found\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
e.getOutputStream().flush()
|
e.getOutputStream().flush()
|
||||||
@@ -204,25 +153,7 @@ public class UploadManager {
|
|||||||
e.close()
|
e.close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
uploader = new ContentUploader(sharedFiles.iterator().next().file, request, e)
|
||||||
if (request.have > 0)
|
|
||||||
eventBus.publish(new SourceDiscoveredEvent(infoHash : request.infoHash, source : request.downloader))
|
|
||||||
|
|
||||||
Mesh mesh
|
|
||||||
File file
|
|
||||||
int pieceSize
|
|
||||||
if (downloader != null) {
|
|
||||||
mesh = meshManager.get(infoHash)
|
|
||||||
file = downloader.incompleteFile
|
|
||||||
pieceSize = downloader.pieceSizePow2
|
|
||||||
} else {
|
|
||||||
SharedFile sharedFile = sharedFiles.iterator().next();
|
|
||||||
mesh = meshManager.getOrCreate(request.infoHash, sharedFile.NPieces, false)
|
|
||||||
file = sharedFile.file
|
|
||||||
pieceSize = sharedFile.pieceSize
|
|
||||||
}
|
|
||||||
|
|
||||||
uploader = new ContentUploader(file, request, e, mesh, pieceSize)
|
|
||||||
eventBus.publish(new UploadEvent(uploader : uploader))
|
eventBus.publish(new UploadEvent(uploader : uploader))
|
||||||
try {
|
try {
|
||||||
uploader.respond()
|
uploader.respond()
|
||||||
|
@@ -23,19 +23,4 @@ abstract class Uploader {
|
|||||||
return -1
|
return -1
|
||||||
mapped.position()
|
mapped.position()
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract String getName();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return an integer between 0 and 100
|
|
||||||
*/
|
|
||||||
abstract int getProgress();
|
|
||||||
|
|
||||||
abstract String getDownloader();
|
|
||||||
|
|
||||||
abstract int getDonePieces();
|
|
||||||
|
|
||||||
abstract int getTotalPieces();
|
|
||||||
|
|
||||||
abstract long getTotalSize();
|
|
||||||
}
|
}
|
||||||
|
82
core/src/main/groovy/com/muwire/core/util/DataUtil.groovy
Normal file
82
core/src/main/groovy/com/muwire/core/util/DataUtil.groovy
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
package com.muwire.core.util
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
|
||||||
|
import com.muwire.core.Constants
|
||||||
|
|
||||||
|
class DataUtil {
|
||||||
|
|
||||||
|
private final static int MAX_SHORT = (0x1 << 16) - 1
|
||||||
|
|
||||||
|
static void writeUnsignedShort(int value, OutputStream os) {
|
||||||
|
if (value > MAX_SHORT || value < 0)
|
||||||
|
throw new IllegalArgumentException("$value invalid")
|
||||||
|
|
||||||
|
byte lsb = (byte) (value & 0xFF)
|
||||||
|
byte msb = (byte) (value >> 8)
|
||||||
|
|
||||||
|
os.write(msb)
|
||||||
|
os.write(lsb)
|
||||||
|
}
|
||||||
|
|
||||||
|
private final static int MAX_HEADER = 0x7FFFFF
|
||||||
|
|
||||||
|
static void packHeader(int length, byte [] header) {
|
||||||
|
if (header.length != 3)
|
||||||
|
throw new IllegalArgumentException("header length $header.length")
|
||||||
|
if (length < 0 || length > MAX_HEADER)
|
||||||
|
throw new IllegalArgumentException("length $length")
|
||||||
|
|
||||||
|
header[2] = (byte) (length & 0xFF)
|
||||||
|
header[1] = (byte) ((length >> 8) & 0xFF)
|
||||||
|
header[0] = (byte) ((length >> 16) & 0x7F)
|
||||||
|
}
|
||||||
|
|
||||||
|
static int readLength(byte [] header) {
|
||||||
|
if (header.length != 3)
|
||||||
|
throw new IllegalArgumentException("header length $header.length")
|
||||||
|
|
||||||
|
return (((int)(header[0] & 0x7F)) << 16) |
|
||||||
|
(((int)(header[1] & 0xFF) << 8)) |
|
||||||
|
((int)header[2] & 0xFF)
|
||||||
|
}
|
||||||
|
|
||||||
|
static String readi18nString(byte [] encoded) {
|
||||||
|
if (encoded.length < 2)
|
||||||
|
throw new IllegalArgumentException("encoding too short $encoded.length")
|
||||||
|
int length = ((encoded[0] & 0xFF) << 8) | (encoded[1] & 0xFF)
|
||||||
|
if (encoded.length != length + 2)
|
||||||
|
throw new IllegalArgumentException("encoding doesn't match length, expected $length found $encoded.length")
|
||||||
|
byte [] string = new byte[length]
|
||||||
|
System.arraycopy(encoded, 2, string, 0, length)
|
||||||
|
new String(string, StandardCharsets.UTF_8)
|
||||||
|
}
|
||||||
|
|
||||||
|
static byte[] encodei18nString(String string) {
|
||||||
|
byte [] utf8 = string.getBytes(StandardCharsets.UTF_8)
|
||||||
|
if (utf8.length > Short.MAX_VALUE)
|
||||||
|
throw new IllegalArgumentException("String in utf8 too long $utf8.length")
|
||||||
|
def baos = new ByteArrayOutputStream()
|
||||||
|
def daos = new DataOutputStream(baos)
|
||||||
|
daos.writeShort((short) utf8.length)
|
||||||
|
daos.write(utf8)
|
||||||
|
daos.close()
|
||||||
|
baos.toByteArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String readTillRN(InputStream is) {
|
||||||
|
def baos = new ByteArrayOutputStream()
|
||||||
|
while(baos.size() < (Constants.MAX_HEADER_SIZE)) {
|
||||||
|
byte read = is.read()
|
||||||
|
if (read == -1)
|
||||||
|
throw new IOException()
|
||||||
|
if (read == '\r') {
|
||||||
|
if (is.read() != '\n')
|
||||||
|
throw new IOException("invalid header")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
baos.write(read)
|
||||||
|
}
|
||||||
|
new String(baos.toByteArray(), StandardCharsets.US_ASCII)
|
||||||
|
}
|
||||||
|
}
|
@@ -1,13 +0,0 @@
|
|||||||
package com.muwire.core;
|
|
||||||
|
|
||||||
import net.i2p.crypto.SigType;
|
|
||||||
|
|
||||||
public class Constants {
|
|
||||||
public static final byte PERSONA_VERSION = (byte)1;
|
|
||||||
public static final SigType SIG_TYPE = SigType.EdDSA_SHA512_Ed25519;
|
|
||||||
|
|
||||||
public static final int MAX_HEADER_SIZE = 0x1 << 14;
|
|
||||||
public static final int MAX_HEADERS = 16;
|
|
||||||
|
|
||||||
public static final int MAX_RESULTS = 0x1 << 16;
|
|
||||||
}
|
|
@@ -1,7 +1,6 @@
|
|||||||
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;
|
||||||
@@ -10,8 +9,7 @@ public class DownloadedFile extends SharedFile {
|
|||||||
|
|
||||||
private final Set<Destination> sources;
|
private final Set<Destination> sources;
|
||||||
|
|
||||||
public DownloadedFile(File file, InfoHash infoHash, int pieceSize, Set<Destination> sources)
|
public DownloadedFile(File file, InfoHash infoHash, int pieceSize, Set<Destination> sources) {
|
||||||
throws IOException {
|
|
||||||
super(file, infoHash, pieceSize);
|
super(file, infoHash, pieceSize);
|
||||||
this.sources = sources;
|
this.sources = sources;
|
||||||
}
|
}
|
||||||
|
@@ -1,13 +1,6 @@
|
|||||||
package com.muwire.core;
|
package com.muwire.core;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import com.muwire.core.util.DataUtil;
|
|
||||||
|
|
||||||
import net.i2p.data.Base64;
|
|
||||||
|
|
||||||
public class SharedFile {
|
public class SharedFile {
|
||||||
|
|
||||||
@@ -15,31 +8,10 @@ public class SharedFile {
|
|||||||
private final InfoHash infoHash;
|
private final InfoHash infoHash;
|
||||||
private final int pieceSize;
|
private final int pieceSize;
|
||||||
|
|
||||||
private final String cachedPath;
|
public SharedFile(File file, InfoHash infoHash, int pieceSize) {
|
||||||
private final long cachedLength;
|
|
||||||
|
|
||||||
private final String b64EncodedFileName;
|
|
||||||
private final String b64EncodedHashRoot;
|
|
||||||
private final List<String> b64EncodedHashList;
|
|
||||||
|
|
||||||
private volatile String comment;
|
|
||||||
|
|
||||||
public SharedFile(File file, InfoHash infoHash, int pieceSize) throws IOException {
|
|
||||||
this.file = file;
|
this.file = file;
|
||||||
this.infoHash = infoHash;
|
this.infoHash = infoHash;
|
||||||
this.pieceSize = pieceSize;
|
this.pieceSize = pieceSize;
|
||||||
this.cachedPath = file.getAbsolutePath();
|
|
||||||
this.cachedLength = file.length();
|
|
||||||
this.b64EncodedFileName = Base64.encode(DataUtil.encodei18nString(file.toString()));
|
|
||||||
this.b64EncodedHashRoot = Base64.encode(infoHash.getRoot());
|
|
||||||
|
|
||||||
List<String> b64List = new ArrayList<String>();
|
|
||||||
byte[] tmp = new byte[32];
|
|
||||||
for (int i = 0; i < infoHash.getHashList().length / 32; i++) {
|
|
||||||
System.arraycopy(infoHash.getHashList(), i * 32, tmp, 0, 32);
|
|
||||||
b64List.add(Base64.encode(tmp));
|
|
||||||
}
|
|
||||||
this.b64EncodedHashList = b64List;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public File getFile() {
|
public File getFile() {
|
||||||
@@ -53,54 +25,4 @@ public class SharedFile {
|
|||||||
public int getPieceSize() {
|
public int getPieceSize() {
|
||||||
return pieceSize;
|
return pieceSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getNPieces() {
|
|
||||||
long length = file.length();
|
|
||||||
int rawPieceSize = 0x1 << pieceSize;
|
|
||||||
int rv = (int) (length / rawPieceSize);
|
|
||||||
if (length % rawPieceSize != 0)
|
|
||||||
rv++;
|
|
||||||
return rv;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getB64EncodedFileName() {
|
|
||||||
return b64EncodedFileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getB64EncodedHashRoot() {
|
|
||||||
return b64EncodedHashRoot;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getB64EncodedHashList() {
|
|
||||||
return b64EncodedHashList;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCachedPath() {
|
|
||||||
return cachedPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getCachedLength() {
|
|
||||||
return cachedLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setComment(String comment) {
|
|
||||||
this.comment = comment;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getComment() {
|
|
||||||
return comment;
|
|
||||||
}
|
|
||||||
|
|
||||||
@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,168 +0,0 @@
|
|||||||
package com.muwire.core.util;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import com.muwire.core.Constants;
|
|
||||||
|
|
||||||
import net.i2p.data.Base64;
|
|
||||||
|
|
||||||
public class DataUtil {
|
|
||||||
|
|
||||||
private final static int MAX_SHORT = (0x1 << 16) - 1;
|
|
||||||
|
|
||||||
static void writeUnsignedShort(int value, OutputStream os) throws IOException {
|
|
||||||
if (value > MAX_SHORT || value < 0)
|
|
||||||
throw new IllegalArgumentException("$value invalid");
|
|
||||||
|
|
||||||
byte lsb = (byte) (value & 0xFF);
|
|
||||||
byte msb = (byte) (value >> 8);
|
|
||||||
|
|
||||||
os.write(msb);
|
|
||||||
os.write(lsb);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final static int MAX_HEADER = 0x7FFFFF;
|
|
||||||
|
|
||||||
static void packHeader(int length, byte [] header) {
|
|
||||||
if (header.length != 3)
|
|
||||||
throw new IllegalArgumentException("header length $header.length");
|
|
||||||
if (length < 0 || length > MAX_HEADER)
|
|
||||||
throw new IllegalArgumentException("length $length");
|
|
||||||
|
|
||||||
header[2] = (byte) (length & 0xFF);
|
|
||||||
header[1] = (byte) ((length >> 8) & 0xFF);
|
|
||||||
header[0] = (byte) ((length >> 16) & 0x7F);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int readLength(byte [] header) {
|
|
||||||
if (header.length != 3)
|
|
||||||
throw new IllegalArgumentException("header length $header.length");
|
|
||||||
|
|
||||||
return (((int)(header[0] & 0x7F)) << 16) |
|
|
||||||
(((int)(header[1] & 0xFF) << 8)) |
|
|
||||||
((int)header[2] & 0xFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
static String readi18nString(byte [] encoded) {
|
|
||||||
if (encoded.length < 2)
|
|
||||||
throw new IllegalArgumentException("encoding too short $encoded.length");
|
|
||||||
int length = ((encoded[0] & 0xFF) << 8) | (encoded[1] & 0xFF);
|
|
||||||
if (encoded.length != length + 2)
|
|
||||||
throw new IllegalArgumentException("encoding doesn't match length, expected $length found $encoded.length");
|
|
||||||
byte [] string = new byte[length];
|
|
||||||
System.arraycopy(encoded, 2, string, 0, length);
|
|
||||||
return new String(string, StandardCharsets.UTF_8);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] encodei18nString(String string) {
|
|
||||||
byte [] utf8 = string.getBytes(StandardCharsets.UTF_8);
|
|
||||||
if (utf8.length > Short.MAX_VALUE)
|
|
||||||
throw new IllegalArgumentException("String in utf8 too long $utf8.length");
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
DataOutputStream daos = new DataOutputStream(baos);
|
|
||||||
try {
|
|
||||||
daos.writeShort((short) utf8.length);
|
|
||||||
daos.write(utf8);
|
|
||||||
daos.close();
|
|
||||||
} catch (IOException impossible) {
|
|
||||||
throw new IllegalStateException(impossible);
|
|
||||||
}
|
|
||||||
return baos.toByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String readTillRN(InputStream is) throws IOException {
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
while(baos.size() < (Constants.MAX_HEADER_SIZE)) {
|
|
||||||
int read = is.read();
|
|
||||||
if (read == -1)
|
|
||||||
throw new IOException();
|
|
||||||
if (read == '\r') {
|
|
||||||
if (is.read() != '\n')
|
|
||||||
throw new IOException("invalid header");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
baos.write(read);
|
|
||||||
}
|
|
||||||
return new String(baos.toByteArray(), StandardCharsets.US_ASCII);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String encodeXHave(List<Integer> pieces, int totalPieces) {
|
|
||||||
int bytes = totalPieces / 8;
|
|
||||||
if (totalPieces % 8 != 0)
|
|
||||||
bytes++;
|
|
||||||
byte[] raw = new byte[bytes];
|
|
||||||
for (int it : pieces) {
|
|
||||||
int byteIdx = it / 8;
|
|
||||||
int offset = it % 8;
|
|
||||||
int mask = 0x80 >>> offset;
|
|
||||||
raw[byteIdx] |= mask;
|
|
||||||
}
|
|
||||||
return Base64.encode(raw);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<Integer> decodeXHave(String xHave) {
|
|
||||||
byte [] availablePieces = Base64.decode(xHave);
|
|
||||||
List<Integer> available = new ArrayList<>();
|
|
||||||
for (int i = 0; i < availablePieces.length; i ++) {
|
|
||||||
byte b = availablePieces[i];
|
|
||||||
for (int j = 0; j < 8 ; j++) {
|
|
||||||
byte mask = (byte) (0x80 >>> j);
|
|
||||||
if ((b & mask) == mask) {
|
|
||||||
available.add(i * 8 + j);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return available;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Throwable findRoot(Throwable e) {
|
|
||||||
while(e.getCause() != null)
|
|
||||||
e = e.getCause();
|
|
||||||
return 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,31 +1,21 @@
|
|||||||
package com.muwire.core.download
|
package com.muwire.core.download
|
||||||
|
|
||||||
import static org.junit.Assert.fail
|
|
||||||
|
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Ignore
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
import com.muwire.core.EventBus
|
|
||||||
import com.muwire.core.InfoHash
|
import com.muwire.core.InfoHash
|
||||||
import com.muwire.core.Persona
|
|
||||||
import com.muwire.core.Personas
|
|
||||||
import com.muwire.core.connection.Endpoint
|
import com.muwire.core.connection.Endpoint
|
||||||
import com.muwire.core.files.FileHasher
|
import com.muwire.core.files.FileHasher
|
||||||
import static com.muwire.core.util.DataUtil.readTillRN
|
import static com.muwire.core.util.DataUtil.readTillRN
|
||||||
import static com.muwire.core.util.DataUtil.encodeXHave
|
|
||||||
|
|
||||||
import net.i2p.data.Base64
|
import net.i2p.data.Base64
|
||||||
import net.i2p.util.ConcurrentHashSet
|
|
||||||
|
|
||||||
class DownloadSessionTest {
|
class DownloadSessionTest {
|
||||||
|
|
||||||
private EventBus eventBus
|
|
||||||
private File source, target
|
private File source, target
|
||||||
private InfoHash infoHash
|
private InfoHash infoHash
|
||||||
private Endpoint endpoint
|
private Endpoint endpoint
|
||||||
private Pieces pieces
|
private Pieces pieces, claimed
|
||||||
private String rootBase64
|
private String rootBase64
|
||||||
|
|
||||||
private DownloadSession session
|
private DownloadSession session
|
||||||
@@ -34,16 +24,6 @@ class DownloadSessionTest {
|
|||||||
private InputStream fromDownloader, fromUploader
|
private InputStream fromDownloader, fromUploader
|
||||||
private OutputStream toDownloader, toUploader
|
private OutputStream toDownloader, toUploader
|
||||||
|
|
||||||
private volatile boolean performed
|
|
||||||
private Set<Integer> available = new ConcurrentHashSet<>()
|
|
||||||
private volatile IOException thrown
|
|
||||||
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
eventBus = new EventBus()
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initSession(int size, def claimedPieces = []) {
|
private void initSession(int size, def claimedPieces = []) {
|
||||||
Random r = new Random()
|
Random r = new Random()
|
||||||
byte [] content = new byte[size]
|
byte [] content = new byte[size]
|
||||||
@@ -68,7 +48,8 @@ class DownloadSessionTest {
|
|||||||
else
|
else
|
||||||
nPieces = size / pieceSize + 1
|
nPieces = size / pieceSize + 1
|
||||||
pieces = new Pieces(nPieces)
|
pieces = new Pieces(nPieces)
|
||||||
claimedPieces.each {pieces.claimed.set(it)}
|
claimed = new Pieces(nPieces)
|
||||||
|
claimedPieces.each {claimed.markDownloaded(it)}
|
||||||
|
|
||||||
fromDownloader = new PipedInputStream()
|
fromDownloader = new PipedInputStream()
|
||||||
fromUploader = new PipedInputStream()
|
fromUploader = new PipedInputStream()
|
||||||
@@ -76,20 +57,12 @@ class DownloadSessionTest {
|
|||||||
toUploader = new PipedOutputStream(fromDownloader)
|
toUploader = new PipedOutputStream(fromDownloader)
|
||||||
endpoint = new Endpoint(null, fromUploader, toUploader, null)
|
endpoint = new Endpoint(null, fromUploader, toUploader, null)
|
||||||
|
|
||||||
session = new DownloadSession(eventBus, "",pieces, infoHash, endpoint, target, pieceSize, size, available)
|
session = new DownloadSession("",pieces, claimed, infoHash, endpoint, target, pieceSize, size)
|
||||||
downloadThread = new Thread( { perform() } as Runnable)
|
downloadThread = new Thread( { session.request() } as Runnable)
|
||||||
downloadThread.setDaemon(true)
|
downloadThread.setDaemon(true)
|
||||||
downloadThread.start()
|
downloadThread.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
private void perform() {
|
|
||||||
try {
|
|
||||||
performed = session.request()
|
|
||||||
} catch (IOException e) {
|
|
||||||
thrown = e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void teardown() {
|
public void teardown() {
|
||||||
source?.delete()
|
source?.delete()
|
||||||
@@ -104,7 +77,6 @@ class DownloadSessionTest {
|
|||||||
assert "GET $rootBase64" == readTillRN(fromDownloader)
|
assert "GET $rootBase64" == readTillRN(fromDownloader)
|
||||||
assert "Range: 0-19" == readTillRN(fromDownloader)
|
assert "Range: 0-19" == readTillRN(fromDownloader)
|
||||||
readTillRN(fromDownloader)
|
readTillRN(fromDownloader)
|
||||||
readTillRN(fromDownloader)
|
|
||||||
assert "" == readTillRN(fromDownloader)
|
assert "" == readTillRN(fromDownloader)
|
||||||
|
|
||||||
toDownloader.write("200 OK\r\n".bytes)
|
toDownloader.write("200 OK\r\n".bytes)
|
||||||
@@ -116,9 +88,6 @@ class DownloadSessionTest {
|
|||||||
|
|
||||||
assert pieces.isComplete()
|
assert pieces.isComplete()
|
||||||
assert target.bytes == source.bytes
|
assert target.bytes == source.bytes
|
||||||
assert performed
|
|
||||||
assert available.isEmpty()
|
|
||||||
assert thrown == null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -130,7 +99,6 @@ class DownloadSessionTest {
|
|||||||
assert "GET $rootBase64" == readTillRN(fromDownloader)
|
assert "GET $rootBase64" == readTillRN(fromDownloader)
|
||||||
readTillRN(fromDownloader)
|
readTillRN(fromDownloader)
|
||||||
readTillRN(fromDownloader)
|
readTillRN(fromDownloader)
|
||||||
readTillRN(fromDownloader)
|
|
||||||
assert "" == readTillRN(fromDownloader)
|
assert "" == readTillRN(fromDownloader)
|
||||||
|
|
||||||
toDownloader.write("200 OK\r\n".bytes)
|
toDownloader.write("200 OK\r\n".bytes)
|
||||||
@@ -141,9 +109,6 @@ class DownloadSessionTest {
|
|||||||
Thread.sleep(150)
|
Thread.sleep(150)
|
||||||
assert pieces.isComplete()
|
assert pieces.isComplete()
|
||||||
assert target.bytes == source.bytes
|
assert target.bytes == source.bytes
|
||||||
assert performed
|
|
||||||
assert available.isEmpty()
|
|
||||||
assert thrown == null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -161,7 +126,6 @@ class DownloadSessionTest {
|
|||||||
assert (start == 0 && end == ((1 << pieceSize) - 1)) ||
|
assert (start == 0 && end == ((1 << pieceSize) - 1)) ||
|
||||||
(start == (1 << pieceSize) && end == (1 << pieceSize))
|
(start == (1 << pieceSize) && end == (1 << pieceSize))
|
||||||
|
|
||||||
readTillRN(fromDownloader)
|
|
||||||
readTillRN(fromDownloader)
|
readTillRN(fromDownloader)
|
||||||
assert "" == readTillRN(fromDownloader)
|
assert "" == readTillRN(fromDownloader)
|
||||||
|
|
||||||
@@ -175,21 +139,14 @@ class DownloadSessionTest {
|
|||||||
Thread.sleep(150)
|
Thread.sleep(150)
|
||||||
assert !pieces.isComplete()
|
assert !pieces.isComplete()
|
||||||
assert 1 == pieces.donePieces()
|
assert 1 == pieces.donePieces()
|
||||||
assert performed
|
|
||||||
assert available.isEmpty()
|
|
||||||
assert thrown == null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore // this needs to be rewritten with stealing in mind
|
|
||||||
public void testSmallFileClaimed() {
|
public void testSmallFileClaimed() {
|
||||||
initSession(20, [0])
|
initSession(20, [0])
|
||||||
long now = System.currentTimeMillis()
|
long now = System.currentTimeMillis()
|
||||||
downloadThread.join(150)
|
downloadThread.join(100)
|
||||||
assert 100 >= (System.currentTimeMillis() - now)
|
assert 100 > (System.currentTimeMillis() - now)
|
||||||
assert !performed
|
|
||||||
assert available.isEmpty()
|
|
||||||
assert thrown == null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -197,7 +154,7 @@ class DownloadSessionTest {
|
|||||||
int pieceSize = FileHasher.getPieceSize(1)
|
int pieceSize = FileHasher.getPieceSize(1)
|
||||||
int size = (1 << pieceSize) * 10
|
int size = (1 << pieceSize) * 10
|
||||||
initSession(size, [1,2,3,4,5,6,7,8,9])
|
initSession(size, [1,2,3,4,5,6,7,8,9])
|
||||||
assert !pieces.claimed.get(0)
|
assert !claimed.isMarked(0)
|
||||||
|
|
||||||
assert "GET $rootBase64" == readTillRN(fromDownloader)
|
assert "GET $rootBase64" == readTillRN(fromDownloader)
|
||||||
String range = readTillRN(fromDownloader)
|
String range = readTillRN(fromDownloader)
|
||||||
@@ -205,131 +162,7 @@ class DownloadSessionTest {
|
|||||||
int start = Integer.parseInt(matcher[0][1])
|
int start = Integer.parseInt(matcher[0][1])
|
||||||
int end = Integer.parseInt(matcher[0][2])
|
int end = Integer.parseInt(matcher[0][2])
|
||||||
|
|
||||||
assert pieces.claimed.get(0)
|
assert claimed.isMarked(0)
|
||||||
assert start == 0 && end == (1 << pieceSize) - 1
|
assert start == 0 && end == (1 << pieceSize) - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test416NoHave() {
|
|
||||||
initSession(20)
|
|
||||||
readAllHeaders(fromDownloader)
|
|
||||||
|
|
||||||
toDownloader.write("416 don't have it\r\n\r\n".bytes)
|
|
||||||
toDownloader.flush()
|
|
||||||
Thread.sleep(150)
|
|
||||||
assert !performed
|
|
||||||
assert available.isEmpty()
|
|
||||||
assert thrown != null
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test416Have() {
|
|
||||||
initSession(20)
|
|
||||||
readAllHeaders(fromDownloader)
|
|
||||||
|
|
||||||
toDownloader.write("416 don't have it\r\n".bytes)
|
|
||||||
toDownloader.write("X-Have: ${encodeXHave([0], 1)}\r\n\r\n".bytes)
|
|
||||||
toDownloader.flush()
|
|
||||||
|
|
||||||
Thread.sleep(150)
|
|
||||||
assert performed
|
|
||||||
assert available.contains(0)
|
|
||||||
assert thrown == null
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test416Have2Pieces() {
|
|
||||||
int pieceSize = FileHasher.getPieceSize(1)
|
|
||||||
int size = (1 << pieceSize) + 1
|
|
||||||
initSession(size)
|
|
||||||
readAllHeaders(fromDownloader)
|
|
||||||
|
|
||||||
toDownloader.write("416 don't have it\r\n".bytes)
|
|
||||||
toDownloader.write("X-Have: ${encodeXHave([1], 2)}\r\n\r\n".bytes)
|
|
||||||
toDownloader.flush()
|
|
||||||
|
|
||||||
Thread.sleep(150)
|
|
||||||
assert performed
|
|
||||||
assert available.contains(1)
|
|
||||||
assert thrown == null
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test200TwoPieces1Available() {
|
|
||||||
int pieceSize = FileHasher.getPieceSize(1)
|
|
||||||
int size = (1 << pieceSize) * 9 + 1
|
|
||||||
initSession(size)
|
|
||||||
|
|
||||||
Set<String> headers = readAllHeaders(fromDownloader)
|
|
||||||
def matcher = null
|
|
||||||
headers.each {
|
|
||||||
if (it.startsWith("Range"))
|
|
||||||
matcher = (it =~ /^Range: (\d+)-(\d+)$/)
|
|
||||||
}
|
|
||||||
assert matcher.groupCount() > 0
|
|
||||||
int start = Integer.parseInt(matcher[0][1])
|
|
||||||
int end = Integer.parseInt(matcher[0][2])
|
|
||||||
|
|
||||||
if (start == 0)
|
|
||||||
fail("inconlcusive")
|
|
||||||
|
|
||||||
toDownloader.write("416 don't have it \r\n".bytes)
|
|
||||||
toDownloader.write("X-Have: ${encodeXHave([0],2)}\r\n\r\n".bytes)
|
|
||||||
toDownloader.flush()
|
|
||||||
downloadThread.join()
|
|
||||||
|
|
||||||
assert performed
|
|
||||||
performed = false
|
|
||||||
assert available.contains(0)
|
|
||||||
assert thrown == null
|
|
||||||
|
|
||||||
// request same session
|
|
||||||
downloadThread = new Thread( { perform() } as Runnable)
|
|
||||||
downloadThread.setDaemon(true)
|
|
||||||
downloadThread.start()
|
|
||||||
|
|
||||||
Thread.sleep(150)
|
|
||||||
|
|
||||||
headers = readAllHeaders(fromDownloader)
|
|
||||||
matcher = null
|
|
||||||
headers.each {
|
|
||||||
if (it.startsWith("Range"))
|
|
||||||
matcher = (it =~ /^Range: (\d+)-(\d+)$/)
|
|
||||||
}
|
|
||||||
assert matcher.groupCount() > 0
|
|
||||||
start = Integer.parseInt(matcher[0][1])
|
|
||||||
end = Integer.parseInt(matcher[0][2])
|
|
||||||
assert start == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testXAlt() throws Exception {
|
|
||||||
Personas personas = new Personas()
|
|
||||||
def sources = []
|
|
||||||
def listener = new Object() {
|
|
||||||
public void onSourceDiscoveredEvent(SourceDiscoveredEvent e) {
|
|
||||||
sources << e.source
|
|
||||||
}
|
|
||||||
}
|
|
||||||
eventBus.register(SourceDiscoveredEvent.class, listener)
|
|
||||||
|
|
||||||
initSession(20)
|
|
||||||
readAllHeaders(fromDownloader)
|
|
||||||
toDownloader.write("416 don't have it\r\n".bytes)
|
|
||||||
toDownloader.write("X-Alt: ${personas.persona1.toBase64()},${personas.persona2.toBase64()}\r\n\r\n".bytes)
|
|
||||||
toDownloader.flush()
|
|
||||||
|
|
||||||
Thread.sleep(150)
|
|
||||||
assert sources.contains(personas.persona1)
|
|
||||||
assert sources.contains(personas.persona2)
|
|
||||||
assert 2 == sources.size()
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Set<String> readAllHeaders(InputStream is) {
|
|
||||||
Set<String> rv = new HashSet<>()
|
|
||||||
String header
|
|
||||||
while((header = readTillRN(is)) != "")
|
|
||||||
rv.add(header)
|
|
||||||
rv
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -16,7 +16,7 @@ class PiecesTest {
|
|||||||
public void testSinglePiece() {
|
public void testSinglePiece() {
|
||||||
pieces = new Pieces(1)
|
pieces = new Pieces(1)
|
||||||
assert !pieces.isComplete()
|
assert !pieces.isComplete()
|
||||||
assert pieces.claim() == [0,0,0]
|
assert pieces.getRandomPiece() == 0
|
||||||
pieces.markDownloaded(0)
|
pieces.markDownloaded(0)
|
||||||
assert pieces.isComplete()
|
assert pieces.isComplete()
|
||||||
}
|
}
|
||||||
@@ -25,28 +25,13 @@ class PiecesTest {
|
|||||||
public void testTwoPieces() {
|
public void testTwoPieces() {
|
||||||
pieces = new Pieces(2)
|
pieces = new Pieces(2)
|
||||||
assert !pieces.isComplete()
|
assert !pieces.isComplete()
|
||||||
int[] piece = pieces.claim()
|
int piece = pieces.getRandomPiece()
|
||||||
assert piece[0] == 0 || piece[0] == 1
|
assert piece == 0 || piece == 1
|
||||||
pieces.markDownloaded(piece[0])
|
pieces.markDownloaded(piece)
|
||||||
assert !pieces.isComplete()
|
assert !pieces.isComplete()
|
||||||
int[] piece2 = pieces.claim()
|
int piece2 = pieces.getRandomPiece()
|
||||||
assert piece[0] != piece2[0]
|
assert piece != piece2
|
||||||
pieces.markDownloaded(piece2[0])
|
pieces.markDownloaded(piece2)
|
||||||
assert pieces.isComplete()
|
assert pieces.isComplete()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testClaimAvailable() {
|
|
||||||
pieces = new Pieces(2)
|
|
||||||
int[] claimed = pieces.claim([0].toSet())
|
|
||||||
assert claimed == [0,0,0]
|
|
||||||
assert [0,0,1] == pieces.claim([0].toSet())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testClaimNoneAvailable() {
|
|
||||||
pieces = new Pieces(20)
|
|
||||||
int[] claimed = pieces.claim()
|
|
||||||
assert [0,0,0] == pieces.claim(claimed.toSet())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -26,7 +26,7 @@ class FileHasherTest extends GroovyTestCase {
|
|||||||
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 27 == FileHasher.getPieceSize(FileHasher.MAX_SIZE)
|
||||||
shouldFail IllegalArgumentException, {
|
shouldFail IllegalArgumentException, {
|
||||||
FileHasher.getPieceSize(Long.MAX_VALUE)
|
FileHasher.getPieceSize(Long.MAX_VALUE)
|
||||||
}
|
}
|
||||||
|
@@ -25,10 +25,8 @@ class HasherServiceTest {
|
|||||||
void before() {
|
void before() {
|
||||||
eventBus = new EventBus()
|
eventBus = new EventBus()
|
||||||
hasher = new FileHasher()
|
hasher = new FileHasher()
|
||||||
def props = new MuWireSettings()
|
service = new HasherService(hasher, eventBus, new FileManager(eventBus, new MuWireSettings()))
|
||||||
service = new HasherService(hasher, eventBus, new FileManager(eventBus, props), props)
|
|
||||||
eventBus.register(FileHashedEvent.class, listener)
|
eventBus.register(FileHashedEvent.class, listener)
|
||||||
eventBus.register(FileSharedEvent.class, service)
|
|
||||||
service.start()
|
service.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -78,7 +78,7 @@ class PersisterServiceLoadingTest {
|
|||||||
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.start()
|
||||||
Thread.sleep(2000)
|
Thread.sleep(2000)
|
||||||
|
|
||||||
assert listener.publishedFiles.size() == 1
|
assert listener.publishedFiles.size() == 1
|
||||||
@@ -121,7 +121,7 @@ class PersisterServiceLoadingTest {
|
|||||||
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.start()
|
||||||
Thread.sleep(2000)
|
Thread.sleep(2000)
|
||||||
|
|
||||||
assert listener.publishedFiles.size() == 1
|
assert listener.publishedFiles.size() == 1
|
||||||
@@ -163,7 +163,7 @@ class PersisterServiceLoadingTest {
|
|||||||
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.start()
|
||||||
Thread.sleep(2000)
|
Thread.sleep(2000)
|
||||||
|
|
||||||
assert listener.publishedFiles.size() == 2
|
assert listener.publishedFiles.size() == 2
|
||||||
@@ -195,7 +195,7 @@ class PersisterServiceLoadingTest {
|
|||||||
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.start()
|
||||||
Thread.sleep(2000)
|
Thread.sleep(2000)
|
||||||
|
|
||||||
assert listener.publishedFiles.size() == 1
|
assert listener.publishedFiles.size() == 1
|
||||||
|
@@ -58,7 +58,7 @@ class PersisterServiceSavingTest {
|
|||||||
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.start()
|
||||||
Thread.sleep(1500)
|
Thread.sleep(1500)
|
||||||
|
|
||||||
JsonSlurper jsonSlurper = new JsonSlurper()
|
JsonSlurper jsonSlurper = new JsonSlurper()
|
||||||
@@ -77,7 +77,7 @@ class PersisterServiceSavingTest {
|
|||||||
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.start()
|
||||||
Thread.sleep(1500)
|
Thread.sleep(1500)
|
||||||
|
|
||||||
JsonSlurper jsonSlurper = new JsonSlurper()
|
JsonSlurper jsonSlurper = new JsonSlurper()
|
||||||
|
@@ -72,9 +72,6 @@ class HostCacheTest {
|
|||||||
TrustLevel.NEUTRAL
|
TrustLevel.NEUTRAL
|
||||||
}
|
}
|
||||||
settingsMock.ignore.allowUntrusted { true }
|
settingsMock.ignore.allowUntrusted { true }
|
||||||
settingsMock.ignore.getHostClearInterval { 0 }
|
|
||||||
settingsMock.ignore.getHostHopelessInterval { 0 }
|
|
||||||
settingsMock.ignore.getHostRejectInterval { 0 }
|
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
@@ -94,10 +91,6 @@ class HostCacheTest {
|
|||||||
TrustLevel.DISTRUSTED
|
TrustLevel.DISTRUSTED
|
||||||
}
|
}
|
||||||
|
|
||||||
settingsMock.ignore.getHostClearInterval { 0 }
|
|
||||||
settingsMock.ignore.getHostHopelessInterval { 0 }
|
|
||||||
settingsMock.ignore.getHostRejectInterval { 0 }
|
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||||
@@ -111,9 +104,6 @@ class HostCacheTest {
|
|||||||
TrustLevel.NEUTRAL
|
TrustLevel.NEUTRAL
|
||||||
}
|
}
|
||||||
settingsMock.ignore.allowUntrusted { false }
|
settingsMock.ignore.allowUntrusted { false }
|
||||||
settingsMock.ignore.getHostClearInterval { 0 }
|
|
||||||
settingsMock.ignore.getHostHopelessInterval { 0 }
|
|
||||||
settingsMock.ignore.getHostRejectInterval { 0 }
|
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
@@ -133,9 +123,6 @@ class HostCacheTest {
|
|||||||
}
|
}
|
||||||
trustMock.demand.getLevel{ d -> TrustLevel.TRUSTED }
|
trustMock.demand.getLevel{ d -> TrustLevel.TRUSTED }
|
||||||
trustMock.demand.getLevel{ d -> TrustLevel.TRUSTED }
|
trustMock.demand.getLevel{ d -> TrustLevel.TRUSTED }
|
||||||
settingsMock.ignore.getHostClearInterval { 0 }
|
|
||||||
settingsMock.ignore.getHostHopelessInterval { 0 }
|
|
||||||
settingsMock.ignore.getHostRejectInterval { 0 }
|
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||||
@@ -152,14 +139,6 @@ class HostCacheTest {
|
|||||||
assert d == destinations.dest1
|
assert d == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
trustMock.demand.getLevel { d ->
|
|
||||||
assert d == destinations.dest1
|
|
||||||
TrustLevel.TRUSTED
|
|
||||||
}
|
|
||||||
|
|
||||||
settingsMock.ignore.getHostClearInterval { 100 }
|
|
||||||
settingsMock.ignore.getHostHopelessInterval { 0 }
|
|
||||||
settingsMock.ignore.getHostRejectInterval { 0 }
|
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||||
@@ -179,10 +158,6 @@ class HostCacheTest {
|
|||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
|
|
||||||
settingsMock.ignore.getHostClearInterval { 0 }
|
|
||||||
settingsMock.ignore.getHostHopelessInterval { 0 }
|
|
||||||
settingsMock.ignore.getHostRejectInterval { 0 }
|
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||||
|
|
||||||
@@ -208,10 +183,6 @@ class HostCacheTest {
|
|||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
|
|
||||||
settingsMock.ignore.getHostClearInterval { 0 }
|
|
||||||
settingsMock.ignore.getHostHopelessInterval { 0 }
|
|
||||||
settingsMock.ignore.getHostRejectInterval { 0 }
|
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||||
|
|
||||||
@@ -243,10 +214,6 @@ class HostCacheTest {
|
|||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
|
|
||||||
settingsMock.ignore.getHostClearInterval { 0 }
|
|
||||||
settingsMock.ignore.getHostHopelessInterval { 0 }
|
|
||||||
settingsMock.ignore.getHostRejectInterval { 0 }
|
|
||||||
|
|
||||||
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))
|
||||||
@@ -262,11 +229,6 @@ class HostCacheTest {
|
|||||||
assert d == destinations.dest1
|
assert d == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
|
|
||||||
settingsMock.ignore.getHostClearInterval { 0 }
|
|
||||||
settingsMock.ignore.getHostHopelessInterval { 0 }
|
|
||||||
settingsMock.ignore.getHostRejectInterval { 0 }
|
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||||
Thread.sleep(150)
|
Thread.sleep(150)
|
||||||
@@ -298,10 +260,6 @@ class HostCacheTest {
|
|||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
|
|
||||||
settingsMock.ignore.getHostClearInterval { 0 }
|
|
||||||
settingsMock.ignore.getHostHopelessInterval { 0 }
|
|
||||||
settingsMock.ignore.getHostRejectInterval { 0 }
|
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
def rv = cache.getHosts(5)
|
def rv = cache.getHosts(5)
|
||||||
assert rv.size() == 1
|
assert rv.size() == 1
|
||||||
|
@@ -83,11 +83,4 @@ class SearchIndexTest {
|
|||||||
assert found.size() == 1
|
assert found.size() == 1
|
||||||
assert found.contains("b c.d")
|
assert found.contains("b c.d")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void testDuplicateTerm() {
|
|
||||||
initIndex(["MuWire-0.3.3.jar"])
|
|
||||||
def found = index.search(["muwire", "0", "3", "jar"])
|
|
||||||
assert found.size() == 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -9,18 +9,18 @@ import com.muwire.core.InfoHash
|
|||||||
|
|
||||||
class RequestParsingTest {
|
class RequestParsingTest {
|
||||||
|
|
||||||
ContentRequest request
|
Request request
|
||||||
|
|
||||||
private void fromString(String requestString) {
|
private void fromString(String requestString) {
|
||||||
def is = new ByteArrayInputStream(requestString.getBytes(StandardCharsets.US_ASCII))
|
def is = new ByteArrayInputStream(requestString.getBytes(StandardCharsets.US_ASCII))
|
||||||
request = Request.parseContentRequest(new InfoHash(new byte[InfoHash.SIZE]), is)
|
request = Request.parse(new InfoHash(new byte[InfoHash.SIZE]), is)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static void failed(String requestString) {
|
private static void failed(String requestString) {
|
||||||
try {
|
try {
|
||||||
def is = new ByteArrayInputStream(requestString.getBytes(StandardCharsets.US_ASCII))
|
def is = new ByteArrayInputStream(requestString.getBytes(StandardCharsets.US_ASCII))
|
||||||
Request.parseContentRequest(new InfoHash(new byte[InfoHash.SIZE]), is)
|
Request.parse(new InfoHash(new byte[InfoHash.SIZE]), is)
|
||||||
assert false
|
assert false
|
||||||
} catch (IOException expected) {}
|
} catch (IOException expected) {}
|
||||||
}
|
}
|
||||||
|
@@ -9,9 +9,6 @@ import org.junit.Test
|
|||||||
|
|
||||||
import com.muwire.core.InfoHash
|
import com.muwire.core.InfoHash
|
||||||
import com.muwire.core.connection.Endpoint
|
import com.muwire.core.connection.Endpoint
|
||||||
import com.muwire.core.download.Pieces
|
|
||||||
import com.muwire.core.files.FileHasher
|
|
||||||
import com.muwire.core.mesh.Mesh
|
|
||||||
|
|
||||||
class UploaderTest {
|
class UploaderTest {
|
||||||
|
|
||||||
@@ -22,7 +19,7 @@ class UploaderTest {
|
|||||||
InputStream is
|
InputStream is
|
||||||
OutputStream os
|
OutputStream os
|
||||||
|
|
||||||
ContentRequest request
|
Request request
|
||||||
Uploader uploader
|
Uploader uploader
|
||||||
|
|
||||||
byte[] inFile
|
byte[] inFile
|
||||||
@@ -55,13 +52,7 @@ class UploaderTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void startUpload() {
|
private void startUpload() {
|
||||||
def hasher = new FileHasher()
|
uploader = new Uploader(file, request, endpoint)
|
||||||
InfoHash infoHash = hasher.hashFile(file)
|
|
||||||
Pieces pieces = new Pieces(FileHasher.getPieceSize(file.length()))
|
|
||||||
for (int i = 0; i < pieces.nPieces; i++)
|
|
||||||
pieces.markDownloaded(i)
|
|
||||||
Mesh mesh = new Mesh(infoHash, pieces)
|
|
||||||
uploader = new ContentUploader(file, request, endpoint, mesh, FileHasher.getPieceSize(file.length()))
|
|
||||||
uploadThread = new Thread(uploader.respond() as Runnable)
|
uploadThread = new Thread(uploader.respond() as Runnable)
|
||||||
uploadThread.setDaemon(true)
|
uploadThread.setDaemon(true)
|
||||||
uploadThread.start()
|
uploadThread.start()
|
||||||
@@ -86,11 +77,10 @@ class UploaderTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testSmallFile() {
|
public void testSmallFile() {
|
||||||
fillFile(20)
|
fillFile(20)
|
||||||
request = new ContentRequest(range : new Range(0,19))
|
request = new Request(range : new Range(0,19))
|
||||||
startUpload()
|
startUpload()
|
||||||
assert "200 OK" == readUntilRN()
|
assert "200 OK" == readUntilRN()
|
||||||
assert "Content-Range: 0-19" == readUntilRN()
|
assert "Content-Range: 0-19" == readUntilRN()
|
||||||
assert readUntilRN().startsWith("X-Have")
|
|
||||||
assert "" == readUntilRN()
|
assert "" == readUntilRN()
|
||||||
|
|
||||||
byte [] data = new byte[20]
|
byte [] data = new byte[20]
|
||||||
@@ -102,11 +92,10 @@ class UploaderTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testRequestMiddle() {
|
public void testRequestMiddle() {
|
||||||
fillFile(20)
|
fillFile(20)
|
||||||
request = new ContentRequest(range : new Range(5,15))
|
request = new Request(range : new Range(5,15))
|
||||||
startUpload()
|
startUpload()
|
||||||
assert "200 OK" == readUntilRN()
|
assert "200 OK" == readUntilRN()
|
||||||
assert "Content-Range: 5-15" == readUntilRN()
|
assert "Content-Range: 5-15" == readUntilRN()
|
||||||
assert readUntilRN().startsWith("X-Have")
|
|
||||||
assert "" == readUntilRN()
|
assert "" == readUntilRN()
|
||||||
|
|
||||||
byte [] data = new byte[11]
|
byte [] data = new byte[11]
|
||||||
@@ -119,10 +108,9 @@ class UploaderTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testOutOfRange() {
|
public void testOutOfRange() {
|
||||||
fillFile(20)
|
fillFile(20)
|
||||||
request = new ContentRequest(range : new Range(0,20))
|
request = new Request(range : new Range(0,20))
|
||||||
startUpload()
|
startUpload()
|
||||||
assert "416 Range Not Satisfiable" == readUntilRN()
|
assert "416 Range Not Satisfiable" == readUntilRN()
|
||||||
assert readUntilRN().startsWith("X-Have")
|
|
||||||
assert "" == readUntilRN()
|
assert "" == readUntilRN()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,12 +118,11 @@ class UploaderTest {
|
|||||||
public void testLargeFile() {
|
public void testLargeFile() {
|
||||||
final int length = 0x1 << 14
|
final int length = 0x1 << 14
|
||||||
fillFile(length)
|
fillFile(length)
|
||||||
request = new ContentRequest(range : new Range(0, length - 1))
|
request = new Request(range : new Range(0, length - 1))
|
||||||
startUpload()
|
startUpload()
|
||||||
readUntilRN()
|
readUntilRN()
|
||||||
readUntilRN()
|
readUntilRN()
|
||||||
readUntilRN()
|
readUntilRN()
|
||||||
readUntilRN()
|
|
||||||
|
|
||||||
byte [] data = new byte[length]
|
byte [] data = new byte[length]
|
||||||
DataInputStream dis = new DataInputStream(is)
|
DataInputStream dis = new DataInputStream(is)
|
||||||
|
@@ -49,7 +49,7 @@ Files are transferred over HTTP1.1 protocol with some custom headers added for d
|
|||||||
|
|
||||||
### Mesh management
|
### Mesh management
|
||||||
|
|
||||||
Download mesh management is a simplified version of Gnutella's "Alternate Location" system. For more information see the "download-mesh" document.
|
Download mesh management is identical to Gnutella, except instead of ip addresses MuWire personas are used. [More information](http://rfc-gnutella.sourceforge.net/developer/tmp/download-mesh.html)
|
||||||
|
|
||||||
### In-Network updates
|
### In-Network updates
|
||||||
|
|
||||||
|
@@ -1,15 +0,0 @@
|
|||||||
# Download Mesh / Partial Sharing
|
|
||||||
|
|
||||||
MuWire uses a system similar to Gnutella's "Alternate Location" download mesh management system, however it is simplified to account for I2P's strengths and borrows a bit from BitTorrent's "Have" message.
|
|
||||||
|
|
||||||
### "X-Have" header
|
|
||||||
|
|
||||||
With every request a downloader makes it sends an "X-Have" header containing the Base64-encoded representation of a bitfield where bits set to 1 represent pieces of the file that the downloader already has. To make partial file sharing possible, if the uploader does not have the complete file it also sends this header in every response. If the header is missing it is assumed the uploader has the complete file.
|
|
||||||
|
|
||||||
### "X-Alt" header
|
|
||||||
|
|
||||||
The uploader can recommend other uploaders to the downloader via the "X-Alt" header. The format of this header is a comma-separated list of Base64-encoded Personas that have previously reported having at least one piece of the file to the uploader via the "X-Have" header.
|
|
||||||
|
|
||||||
### Differences from Gnutella
|
|
||||||
|
|
||||||
Unlike Gnutella the uploader is the sole repository where possible sources of the file are tracked. There is no negative "X-Nalt" header to prevent attacking the download mesh by mass downvoting of sources.
|
|
@@ -1,20 +1,5 @@
|
|||||||
group = com.muwire
|
group = com.muwire
|
||||||
version = 0.5.2
|
version = 0.2.1
|
||||||
i2pVersion = 0.9.42
|
|
||||||
groovyVersion = 2.4.15
|
groovyVersion = 2.4.15
|
||||||
slf4jVersion = 1.7.25
|
slf4jVersion = 1.7.25
|
||||||
spockVersion = 1.1-groovy-2.4
|
spockVersion = 1.1-groovy-2.4
|
||||||
grailsVersion=4.0.0
|
|
||||||
gorm.version=7.0.2.RELEASE
|
|
||||||
|
|
||||||
sourceCompatibility=1.8
|
|
||||||
targetCompatibility=1.8
|
|
||||||
|
|
||||||
# plugin properties
|
|
||||||
author = zab@mail.i2p
|
|
||||||
signer = zab@mail.i2p
|
|
||||||
keystorePassword=changeit
|
|
||||||
websiteURL=http://muwire.i2p
|
|
||||||
updateURLsu3=http://muwire.i2p/MuWire.su3
|
|
||||||
|
|
||||||
pack200=true
|
|
||||||
|
34
gradlew
vendored
34
gradlew
vendored
@@ -11,21 +11,21 @@
|
|||||||
PRG="$0"
|
PRG="$0"
|
||||||
# Need this for relative symlinks.
|
# Need this for relative symlinks.
|
||||||
while [ -h "$PRG" ] ; do
|
while [ -h "$PRG" ] ; do
|
||||||
ls=$(ls -ld "$PRG")
|
ls=`ls -ld "$PRG"`
|
||||||
link=$(expr "$ls" : '.*-> \(.*\)$')
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
if expr "$link" : '/.*' > /dev/null; then
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
PRG="$link"
|
PRG="$link"
|
||||||
else
|
else
|
||||||
PRG=$(dirname "$PRG")"/$link"
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
SAVED="$(pwd)"
|
SAVED="`pwd`"
|
||||||
cd "$(dirname "$PRG")/" >/dev/null
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
APP_HOME="$(pwd -P)"
|
APP_HOME="`pwd -P`"
|
||||||
cd "$SAVED" >/dev/null
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
APP_NAME="Gradle"
|
APP_NAME="Gradle"
|
||||||
APP_BASE_NAME=$(basename "$0")
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
DEFAULT_JVM_OPTS=""
|
DEFAULT_JVM_OPTS=""
|
||||||
@@ -49,7 +49,7 @@ cygwin=false
|
|||||||
msys=false
|
msys=false
|
||||||
darwin=false
|
darwin=false
|
||||||
nonstop=false
|
nonstop=false
|
||||||
case "$(uname)" in
|
case "`uname`" in
|
||||||
CYGWIN* )
|
CYGWIN* )
|
||||||
cygwin=true
|
cygwin=true
|
||||||
;;
|
;;
|
||||||
@@ -90,7 +90,7 @@ fi
|
|||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
MAX_FD_LIMIT=$(ulimit -H -n)
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
if [ $? -eq 0 ] ; then
|
if [ $? -eq 0 ] ; then
|
||||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
MAX_FD="$MAX_FD_LIMIT"
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
@@ -111,12 +111,12 @@ fi
|
|||||||
|
|
||||||
# For Cygwin, switch paths to Windows format before running java
|
# For Cygwin, switch paths to Windows format before running java
|
||||||
if $cygwin ; then
|
if $cygwin ; then
|
||||||
APP_HOME=$(cygpath --path --mixed "$APP_HOME")
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
CLASSPATH=$(cygpath --path --mixed "$CLASSPATH")
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
JAVACMD=$(cygpath --unix "$JAVACMD")
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
# We build the pattern for arguments to be converted via cygpath
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
ROOTDIRSRAW=$(find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null)
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
SEP=""
|
SEP=""
|
||||||
for dir in $ROOTDIRSRAW ; do
|
for dir in $ROOTDIRSRAW ; do
|
||||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
@@ -130,13 +130,13 @@ if $cygwin ; then
|
|||||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
i=0
|
i=0
|
||||||
for arg in "$@" ; do
|
for arg in "$@" ; do
|
||||||
CHECK=$(echo "$arg"|egrep -c "$OURCYGPATTERN" -)
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
CHECK2=$(echo "$arg"|egrep -c "^-") ### Determine if an option
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
eval $(echo args$i)=$(cygpath --path --ignore --mixed "$arg")
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
else
|
else
|
||||||
eval $(echo args$i)="\"$arg\""
|
eval `echo args$i`="\"$arg\""
|
||||||
fi
|
fi
|
||||||
i=$((i+1))
|
i=$((i+1))
|
||||||
done
|
done
|
||||||
|
@@ -44,9 +44,9 @@ mainClassName = 'com.muwire.gui.Launcher'
|
|||||||
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties']
|
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties']
|
||||||
|
|
||||||
apply from: 'gradle/publishing.gradle'
|
apply from: 'gradle/publishing.gradle'
|
||||||
// apply from: 'gradle/code-coverage.gradle'
|
apply from: 'gradle/code-coverage.gradle'
|
||||||
// apply from: 'gradle/code-quality.gradle'
|
apply from: 'gradle/code-quality.gradle'
|
||||||
// apply from: 'gradle/integration-test.gradle'
|
apply from: 'gradle/integration-test.gradle'
|
||||||
// apply from: 'gradle/package.gradle'
|
// apply from: 'gradle/package.gradle'
|
||||||
apply from: 'gradle/docs.gradle'
|
apply from: 'gradle/docs.gradle'
|
||||||
apply plugin: 'com.github.johnrengelman.shadow'
|
apply plugin: 'com.github.johnrengelman.shadow'
|
||||||
@@ -58,11 +58,7 @@ dependencies {
|
|||||||
compile project(":core")
|
compile project(":core")
|
||||||
compile "org.codehaus.griffon:griffon-guice:${griffon.version}"
|
compile "org.codehaus.griffon:griffon-guice:${griffon.version}"
|
||||||
|
|
||||||
// runtime "org.slf4j:slf4j-simple:${slf4jVersion}"
|
runtime "org.slf4j:slf4j-simple:${slf4jVersion}"
|
||||||
|
|
||||||
runtime group: 'org.slf4j', name: 'slf4j-jdk14', version: "${slf4jVersion}"
|
|
||||||
runtime group: 'org.slf4j', name: 'slf4j-api', version: "${slf4jVersion}"
|
|
||||||
runtime group: 'org.slf4j', name: 'jul-to-slf4j', version: "${slf4jVersion}"
|
|
||||||
runtime "javax.annotation:javax.annotation-api:1.3.2"
|
runtime "javax.annotation:javax.annotation-api:1.3.2"
|
||||||
|
|
||||||
testCompile "org.codehaus.griffon:griffon-fest-test:${griffon.version}"
|
testCompile "org.codehaus.griffon:griffon-fest-test:${griffon.version}"
|
||||||
@@ -123,7 +119,6 @@ if (hasProperty('debugRun') && ((project.debugRun as boolean))) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
task jacocoRootMerge(type: org.gradle.testing.jacoco.tasks.JacocoMerge, dependsOn: [test, jacocoTestReport, jacocoIntegrationTestReport]) {
|
task jacocoRootMerge(type: org.gradle.testing.jacoco.tasks.JacocoMerge, dependsOn: [test, jacocoTestReport, jacocoIntegrationTestReport]) {
|
||||||
executionData = files(jacocoTestReport.executionData, jacocoIntegrationTestReport.executionData)
|
executionData = files(jacocoTestReport.executionData, jacocoIntegrationTestReport.executionData)
|
||||||
destinationFile = file("${buildDir}/jacoco/root.exec")
|
destinationFile = file("${buildDir}/jacoco/root.exec")
|
||||||
@@ -143,5 +138,4 @@ task jacocoRootReport(dependsOn: jacocoRootMerge, type: JacocoReport) {
|
|||||||
xml.destination = file("${buildDir}/reports/jacoco/root/root.xml")
|
xml.destination = file("${buildDir}/reports/jacoco/root/root.xml")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
|
@@ -26,39 +26,4 @@ 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'
|
|
||||||
}
|
|
||||||
'show-comment' {
|
|
||||||
model = 'com.muwire.gui.ShowCommentModel'
|
|
||||||
view = 'com.muwire.gui.ShowCommentView'
|
|
||||||
controller = 'com.muwire.gui.ShowCommentController'
|
|
||||||
}
|
|
||||||
'add-comment' {
|
|
||||||
model = 'com.muwire.gui.AddCommentModel'
|
|
||||||
view = 'com.muwire.gui.AddCommentView'
|
|
||||||
controller = 'com.muwire.gui.AddCommentController'
|
|
||||||
}
|
|
||||||
'browse' {
|
|
||||||
model = 'com.muwire.gui.BrowseModel'
|
|
||||||
view = 'com.muwire.gui.BrowseView'
|
|
||||||
controller = 'com.muwire.gui.BrowseController'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1,45 +0,0 @@
|
|||||||
package com.muwire.gui
|
|
||||||
|
|
||||||
import griffon.core.artifact.GriffonController
|
|
||||||
import griffon.core.controller.ControllerAction
|
|
||||||
import griffon.inject.MVCMember
|
|
||||||
import griffon.metadata.ArtifactProviderFor
|
|
||||||
import net.i2p.data.Base64
|
|
||||||
|
|
||||||
import javax.annotation.Nonnull
|
|
||||||
|
|
||||||
import com.muwire.core.Core
|
|
||||||
import com.muwire.core.files.UICommentEvent
|
|
||||||
import com.muwire.core.util.DataUtil
|
|
||||||
|
|
||||||
@ArtifactProviderFor(GriffonController)
|
|
||||||
class AddCommentController {
|
|
||||||
@MVCMember @Nonnull
|
|
||||||
AddCommentModel model
|
|
||||||
@MVCMember @Nonnull
|
|
||||||
AddCommentView view
|
|
||||||
|
|
||||||
Core core
|
|
||||||
|
|
||||||
@ControllerAction
|
|
||||||
void save() {
|
|
||||||
String comment = view.textarea.getText()
|
|
||||||
if (comment.trim().length() == 0)
|
|
||||||
comment = null
|
|
||||||
else
|
|
||||||
comment = Base64.encode(DataUtil.encodei18nString(comment))
|
|
||||||
model.selectedFiles.each {
|
|
||||||
def event = new UICommentEvent(sharedFile : it, oldComment : it.getComment())
|
|
||||||
it.setComment(comment)
|
|
||||||
core.eventBus.publish(event)
|
|
||||||
}
|
|
||||||
mvcGroup.parentGroup.view.refreshSharedFiles()
|
|
||||||
cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
@ControllerAction
|
|
||||||
void cancel() {
|
|
||||||
view.dialog.setVisible(false)
|
|
||||||
mvcGroup.destroy()
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,99 +0,0 @@
|
|||||||
package com.muwire.gui
|
|
||||||
|
|
||||||
import griffon.core.artifact.GriffonController
|
|
||||||
import griffon.core.controller.ControllerAction
|
|
||||||
import griffon.inject.MVCMember
|
|
||||||
import griffon.metadata.ArtifactProviderFor
|
|
||||||
import net.i2p.data.Base64
|
|
||||||
|
|
||||||
import javax.annotation.Nonnull
|
|
||||||
|
|
||||||
import com.muwire.core.EventBus
|
|
||||||
import com.muwire.core.download.UIDownloadEvent
|
|
||||||
import com.muwire.core.search.BrowseStatus
|
|
||||||
import com.muwire.core.search.BrowseStatusEvent
|
|
||||||
import com.muwire.core.search.UIBrowseEvent
|
|
||||||
import com.muwire.core.search.UIResultEvent
|
|
||||||
|
|
||||||
@ArtifactProviderFor(GriffonController)
|
|
||||||
class BrowseController {
|
|
||||||
@MVCMember @Nonnull
|
|
||||||
BrowseModel model
|
|
||||||
@MVCMember @Nonnull
|
|
||||||
BrowseView view
|
|
||||||
|
|
||||||
EventBus eventBus
|
|
||||||
|
|
||||||
|
|
||||||
void register() {
|
|
||||||
eventBus.register(BrowseStatusEvent.class, this)
|
|
||||||
eventBus.register(UIResultEvent.class, this)
|
|
||||||
eventBus.publish(new UIBrowseEvent(host : model.host))
|
|
||||||
}
|
|
||||||
|
|
||||||
void mvcGroupDestroy() {
|
|
||||||
eventBus.unregister(BrowseStatusEvent.class, this)
|
|
||||||
eventBus.unregister(UIResultEvent.class, this)
|
|
||||||
}
|
|
||||||
|
|
||||||
void onBrowseStatusEvent(BrowseStatusEvent e) {
|
|
||||||
runInsideUIAsync {
|
|
||||||
model.status = e.status
|
|
||||||
if (e.status == BrowseStatus.FETCHING)
|
|
||||||
model.totalResults = e.totalResults
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void onUIResultEvent(UIResultEvent e) {
|
|
||||||
runInsideUIAsync {
|
|
||||||
model.results << e
|
|
||||||
model.resultCount = model.results.size()
|
|
||||||
view.resultsTable.model.fireTableDataChanged()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ControllerAction
|
|
||||||
void dismiss() {
|
|
||||||
view.dialog.setVisible(false)
|
|
||||||
mvcGroup.destroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
@ControllerAction
|
|
||||||
void download() {
|
|
||||||
def selectedResults = view.selectedResults()
|
|
||||||
if (selectedResults == null || selectedResults.isEmpty())
|
|
||||||
return
|
|
||||||
selectedResults.removeAll {
|
|
||||||
!mvcGroup.parentGroup.parentGroup.model.canDownload(it.infohash)
|
|
||||||
}
|
|
||||||
|
|
||||||
selectedResults.each { result ->
|
|
||||||
def file = new File(application.context.get("muwire-settings").downloadLocation, result.name)
|
|
||||||
eventBus.publish(new UIDownloadEvent(
|
|
||||||
result : [result],
|
|
||||||
sources : [model.host.destination],
|
|
||||||
target : file,
|
|
||||||
sequential : mvcGroup.parentGroup.view.sequentialDownloadCheckbox.model.isSelected()
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
mvcGroup.parentGroup.parentGroup.view.showDownloadsWindow.call()
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
|
|
||||||
@ControllerAction
|
|
||||||
void viewComment() {
|
|
||||||
def selectedResults = view.selectedResults()
|
|
||||||
if (selectedResults == null || selectedResults.size() != 1)
|
|
||||||
return
|
|
||||||
def result = selectedResults[0]
|
|
||||||
if (result.comment == null)
|
|
||||||
return
|
|
||||||
|
|
||||||
String groupId = Base64.encode(result.infohash.getRoot())
|
|
||||||
Map<String,Object> params = new HashMap<>()
|
|
||||||
params['result'] = result
|
|
||||||
|
|
||||||
mvcGroup.createMVCGroup("show-comment", groupId, params)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,105 +0,0 @@
|
|||||||
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 clearHits() {
|
|
||||||
int selectedRule = view.getSelectedRule()
|
|
||||||
if (selectedRule < 0)
|
|
||||||
return
|
|
||||||
Matcher matcher = model.rules[selectedRule]
|
|
||||||
matcher.matches.clear()
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,41 +0,0 @@
|
|||||||
package com.muwire.gui
|
|
||||||
|
|
||||||
import griffon.core.artifact.GriffonController
|
|
||||||
import griffon.core.controller.ControllerAction
|
|
||||||
import griffon.inject.MVCMember
|
|
||||||
import griffon.metadata.ArtifactProviderFor
|
|
||||||
import net.i2p.router.Router
|
|
||||||
|
|
||||||
import javax.annotation.Nonnull
|
|
||||||
|
|
||||||
import com.muwire.core.Core
|
|
||||||
|
|
||||||
@ArtifactProviderFor(GriffonController)
|
|
||||||
class I2PStatusController {
|
|
||||||
@MVCMember @Nonnull
|
|
||||||
I2PStatusModel model
|
|
||||||
@MVCMember @Nonnull
|
|
||||||
I2PStatusView view
|
|
||||||
|
|
||||||
@ControllerAction
|
|
||||||
void refresh() {
|
|
||||||
Core core = application.context.get("core")
|
|
||||||
Router router = core.router
|
|
||||||
model.networkStatus = router._context.commSystem().status.toStatusString()
|
|
||||||
model.floodfill = router._context.netDb().floodfillEnabled()
|
|
||||||
model.ntcpConnections = router._context.commSystem().getTransports()["NTCP"].countPeers()
|
|
||||||
model.ssuConnections = router._context.commSystem().getTransports()["SSU"].countPeers()
|
|
||||||
model.participatingTunnels = router._context.tunnelManager().getParticipatingCount()
|
|
||||||
model.activePeers = router._context.profileOrganizer().countActivePeers()
|
|
||||||
model.receiveBps = router._context.bandwidthLimiter().getReceiveBps15s()
|
|
||||||
model.sendBps = router._context.bandwidthLimiter().getSendBps15s()
|
|
||||||
model.participatingBW = router._context.bandwidthLimiter().getCurrentParticipatingBandwidth()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@ControllerAction
|
|
||||||
void close() {
|
|
||||||
view.dialog.setVisible(false)
|
|
||||||
mvcGroup.destroy()
|
|
||||||
}
|
|
||||||
}
|
|
@@ -11,27 +11,16 @@ import net.i2p.data.Base64
|
|||||||
|
|
||||||
import javax.annotation.Nonnull
|
import javax.annotation.Nonnull
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.swing.JTable
|
|
||||||
|
|
||||||
|
import com.muwire.core.Constants
|
||||||
import com.muwire.core.Core
|
import com.muwire.core.Core
|
||||||
import com.muwire.core.Persona
|
|
||||||
import com.muwire.core.SharedFile
|
|
||||||
import com.muwire.core.SplitPattern
|
|
||||||
import com.muwire.core.download.Downloader
|
|
||||||
import com.muwire.core.download.DownloadStartedEvent
|
import com.muwire.core.download.DownloadStartedEvent
|
||||||
import com.muwire.core.download.UIDownloadCancelledEvent
|
import com.muwire.core.download.UIDownloadCancelledEvent
|
||||||
import com.muwire.core.download.UIDownloadEvent
|
import com.muwire.core.download.UIDownloadEvent
|
||||||
import com.muwire.core.download.UIDownloadPausedEvent
|
|
||||||
import com.muwire.core.download.UIDownloadResumedEvent
|
|
||||||
import com.muwire.core.files.DirectoryUnsharedEvent
|
|
||||||
import com.muwire.core.files.FileUnsharedEvent
|
|
||||||
import com.muwire.core.files.UIPersistFilesEvent
|
|
||||||
import com.muwire.core.search.QueryEvent
|
import com.muwire.core.search.QueryEvent
|
||||||
import com.muwire.core.search.SearchEvent
|
import com.muwire.core.search.SearchEvent
|
||||||
import com.muwire.core.trust.RemoteTrustList
|
|
||||||
import com.muwire.core.trust.TrustEvent
|
import com.muwire.core.trust.TrustEvent
|
||||||
import com.muwire.core.trust.TrustLevel
|
import com.muwire.core.trust.TrustLevel
|
||||||
import com.muwire.core.trust.TrustSubscriptionEvent
|
|
||||||
|
|
||||||
@ArtifactProviderFor(GriffonController)
|
@ArtifactProviderFor(GriffonController)
|
||||||
class MainFrameController {
|
class MainFrameController {
|
||||||
@@ -41,8 +30,6 @@ class MainFrameController {
|
|||||||
|
|
||||||
@MVCMember @Nonnull
|
@MVCMember @Nonnull
|
||||||
MainFrameModel model
|
MainFrameModel model
|
||||||
@MVCMember @Nonnull
|
|
||||||
MainFrameView view
|
|
||||||
|
|
||||||
private volatile Core core
|
private volatile Core core
|
||||||
|
|
||||||
@@ -55,13 +42,10 @@ class MainFrameController {
|
|||||||
search = search.trim()
|
search = search.trim()
|
||||||
if (search.length() == 0)
|
if (search.length() == 0)
|
||||||
return
|
return
|
||||||
if (search.length() > 128)
|
|
||||||
search = search.substring(0,128)
|
|
||||||
def uuid = UUID.randomUUID()
|
def uuid = UUID.randomUUID()
|
||||||
Map<String, Object> params = new HashMap<>()
|
Map<String, Object> params = new HashMap<>()
|
||||||
params["search-terms"] = search
|
params["search-terms"] = search
|
||||||
params["uuid"] = uuid.toString()
|
params["uuid"] = uuid.toString()
|
||||||
params["core"] = core
|
|
||||||
def group = mvcGroup.createMVCGroup("SearchTab", uuid.toString(), params)
|
def group = mvcGroup.createMVCGroup("SearchTab", uuid.toString(), params)
|
||||||
model.results[uuid.toString()] = group
|
model.results[uuid.toString()] = group
|
||||||
|
|
||||||
@@ -78,18 +62,14 @@ class MainFrameController {
|
|||||||
|
|
||||||
def searchEvent
|
def searchEvent
|
||||||
if (hashSearch) {
|
if (hashSearch) {
|
||||||
searchEvent = new SearchEvent(searchHash : root, uuid : uuid, oobInfohash: true, compressedResults : true)
|
searchEvent = new SearchEvent(searchHash : root, uuid : uuid)
|
||||||
} else {
|
} else {
|
||||||
// this can be improved a lot
|
// this can be improved a lot
|
||||||
def replaced = search.toLowerCase().trim().replaceAll(SplitPattern.SPLIT_PATTERN, " ")
|
def replaced = search.toLowerCase().trim().replaceAll(Constants.SPLIT_PATTERN, " ")
|
||||||
def terms = replaced.split(" ")
|
def terms = replaced.split(" ")
|
||||||
def nonEmpty = []
|
searchEvent = new SearchEvent(searchTerms : terms, uuid : uuid, oobInfohash: true)
|
||||||
terms.each { if (it.length() > 0) nonEmpty << it }
|
|
||||||
searchEvent = new SearchEvent(searchTerms : nonEmpty, uuid : uuid, oobInfohash: true,
|
|
||||||
searchComments : core.muOptions.searchComments, compressedResults : true)
|
|
||||||
}
|
}
|
||||||
boolean firstHop = core.muOptions.allowUntrusted || core.muOptions.searchExtraHop
|
core.eventBus.publish(new QueryEvent(searchEvent : searchEvent, firstHop : true,
|
||||||
core.eventBus.publish(new QueryEvent(searchEvent : searchEvent, firstHop : firstHop,
|
|
||||||
replyTo: core.me.destination, receivedOn: core.me.destination,
|
replyTo: core.me.destination, receivedOn: core.me.destination,
|
||||||
originator : core.me))
|
originator : core.me))
|
||||||
}
|
}
|
||||||
@@ -101,7 +81,6 @@ class MainFrameController {
|
|||||||
Map<String, Object> params = new HashMap<>()
|
Map<String, Object> params = new HashMap<>()
|
||||||
params["search-terms"] = tabTitle
|
params["search-terms"] = tabTitle
|
||||||
params["uuid"] = uuid.toString()
|
params["uuid"] = uuid.toString()
|
||||||
params["core"] = core
|
|
||||||
def group = mvcGroup.createMVCGroup("SearchTab", uuid.toString(), params)
|
def group = mvcGroup.createMVCGroup("SearchTab", uuid.toString(), params)
|
||||||
model.results[uuid.toString()] = group
|
model.results[uuid.toString()] = group
|
||||||
|
|
||||||
@@ -112,6 +91,20 @@ class MainFrameController {
|
|||||||
originator : core.me))
|
originator : core.me))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def selectedResult() {
|
||||||
|
def selected = builder.getVariable("result-tabs").getSelectedComponent()
|
||||||
|
def group = selected.getClientProperty("mvc-group")
|
||||||
|
def table = selected.getClientProperty("results-table")
|
||||||
|
int row = table.getSelectedRow()
|
||||||
|
if (row == -1)
|
||||||
|
return
|
||||||
|
def sortEvt = group.view.lastSortEvent
|
||||||
|
if (sortEvt != null) {
|
||||||
|
row = group.view.resultsTable.rowSorter.convertRowIndexToModel(row)
|
||||||
|
}
|
||||||
|
group.model.results[row]
|
||||||
|
}
|
||||||
|
|
||||||
private int selectedDownload() {
|
private int selectedDownload() {
|
||||||
def downloadsTable = builder.getVariable("downloads-table")
|
def downloadsTable = builder.getVariable("downloads-table")
|
||||||
def selected = downloadsTable.getSelectedRow()
|
def selected = downloadsTable.getSelectedRow()
|
||||||
@@ -122,28 +115,41 @@ class MainFrameController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ControllerAction
|
@ControllerAction
|
||||||
void trustPersonaFromSearch() {
|
void download() {
|
||||||
int selected = builder.getVariable("searches-table").getSelectedRow()
|
def result = selectedResult()
|
||||||
if (selected < 0)
|
if (result == null)
|
||||||
return
|
return // TODO disable button
|
||||||
Persona p = model.searches[selected].originator
|
|
||||||
core.eventBus.publish( new TrustEvent(persona : p, level : TrustLevel.TRUSTED) )
|
def file = new File(application.context.get("muwire-settings").downloadLocation, result.name)
|
||||||
|
|
||||||
|
def selected = builder.getVariable("result-tabs").getSelectedComponent()
|
||||||
|
def group = selected.getClientProperty("mvc-group")
|
||||||
|
|
||||||
|
def resultsBucket = group.model.hashBucket[result.infohash]
|
||||||
|
|
||||||
|
core.eventBus.publish(new UIDownloadEvent(result : resultsBucket, target : file))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ControllerAction
|
@ControllerAction
|
||||||
void distrustPersonaFromSearch() {
|
void trust() {
|
||||||
int selected = builder.getVariable("searches-table").getSelectedRow()
|
def result = selectedResult()
|
||||||
if (selected < 0)
|
if (result == null)
|
||||||
return
|
return // TODO disable button
|
||||||
Persona p = model.searches[selected].originator
|
core.eventBus.publish( new TrustEvent(persona : result.sender, level : TrustLevel.TRUSTED))
|
||||||
core.eventBus.publish( new TrustEvent(persona : p, level : TrustLevel.DISTRUSTED) )
|
}
|
||||||
|
|
||||||
|
@ControllerAction
|
||||||
|
void distrust() {
|
||||||
|
def result = selectedResult()
|
||||||
|
if (result == null)
|
||||||
|
return // TODO disable button
|
||||||
|
core.eventBus.publish( new TrustEvent(persona : result.sender, level : TrustLevel.DISTRUSTED))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ControllerAction
|
@ControllerAction
|
||||||
void cancel() {
|
void cancel() {
|
||||||
def downloader = model.downloads[selectedDownload()].downloader
|
def downloader = model.downloads[selectedDownload()].downloader
|
||||||
downloader.cancel()
|
downloader.cancel()
|
||||||
model.downloadInfoHashes.remove(downloader.getInfoHash())
|
|
||||||
core.eventBus.publish(new UIDownloadCancelledEvent(downloader : downloader))
|
core.eventBus.publish(new UIDownloadCancelledEvent(downloader : downloader))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,153 +157,37 @@ class MainFrameController {
|
|||||||
void resume() {
|
void resume() {
|
||||||
def downloader = model.downloads[selectedDownload()].downloader
|
def downloader = model.downloads[selectedDownload()].downloader
|
||||||
downloader.resume()
|
downloader.resume()
|
||||||
core.eventBus.publish(new UIDownloadResumedEvent())
|
|
||||||
}
|
|
||||||
|
|
||||||
@ControllerAction
|
|
||||||
void pause() {
|
|
||||||
def downloader = model.downloads[selectedDownload()].downloader
|
|
||||||
downloader.pause()
|
|
||||||
core.eventBus.publish(new UIDownloadPausedEvent())
|
|
||||||
}
|
|
||||||
|
|
||||||
@ControllerAction
|
|
||||||
void clear() {
|
|
||||||
def toRemove = []
|
|
||||||
model.downloads.each {
|
|
||||||
if (it.downloader.getCurrentState() == Downloader.DownloadState.CANCELLED) {
|
|
||||||
toRemove << it
|
|
||||||
} else if (it.downloader.getCurrentState() == Downloader.DownloadState.FINISHED) {
|
|
||||||
toRemove << it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
toRemove.each {
|
|
||||||
model.downloads.remove(it)
|
|
||||||
}
|
|
||||||
model.clearButtonEnabled = false
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void markTrust(String tableName, TrustLevel level, def list) {
|
private void markTrust(String tableName, TrustLevel level, def list) {
|
||||||
int row = view.getSelectedTrustTablesRow(tableName)
|
int row = builder.getVariable(tableName).getSelectedRow()
|
||||||
if (row < 0)
|
if (row < 0)
|
||||||
return
|
return
|
||||||
builder.getVariable(tableName).model.fireTableDataChanged()
|
|
||||||
core.eventBus.publish(new TrustEvent(persona : list[row], level : level))
|
core.eventBus.publish(new TrustEvent(persona : list[row], level : level))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ControllerAction
|
@ControllerAction
|
||||||
void markTrusted() {
|
void markTrusted() {
|
||||||
markTrust("distrusted-table", TrustLevel.TRUSTED, model.distrusted)
|
markTrust("distrusted-table", TrustLevel.TRUSTED, model.distrusted)
|
||||||
model.markTrustedButtonEnabled = false
|
|
||||||
model.markNeutralFromDistrustedButtonEnabled = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ControllerAction
|
@ControllerAction
|
||||||
void markNeutralFromDistrusted() {
|
void markNeutralFromDistrusted() {
|
||||||
markTrust("distrusted-table", TrustLevel.NEUTRAL, model.distrusted)
|
markTrust("distrusted-table", TrustLevel.NEUTRAL, model.distrusted)
|
||||||
model.markTrustedButtonEnabled = false
|
|
||||||
model.markNeutralFromDistrustedButtonEnabled = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ControllerAction
|
@ControllerAction
|
||||||
void markDistrusted() {
|
void markDistrusted() {
|
||||||
markTrust("trusted-table", TrustLevel.DISTRUSTED, model.trusted)
|
markTrust("trusted-table", TrustLevel.DISTRUSTED, model.trusted)
|
||||||
model.subscribeButtonEnabled = false
|
|
||||||
model.markDistrustedButtonEnabled = false
|
|
||||||
model.markNeutralFromTrustedButtonEnabled = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ControllerAction
|
@ControllerAction
|
||||||
void markNeutralFromTrusted() {
|
void markNeutralFromTrusted() {
|
||||||
markTrust("trusted-table", TrustLevel.NEUTRAL, model.trusted)
|
markTrust("trusted-table", TrustLevel.NEUTRAL, model.trusted)
|
||||||
model.subscribeButtonEnabled = false
|
|
||||||
model.markDistrustedButtonEnabled = false
|
|
||||||
model.markNeutralFromTrustedButtonEnabled = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ControllerAction
|
void unshareSelectedFiles() {
|
||||||
void subscribe() {
|
println "unsharing selected files"
|
||||||
int row = view.getSelectedTrustTablesRow("trusted-table")
|
|
||||||
if (row < 0)
|
|
||||||
return
|
|
||||||
Persona p = model.trusted[row]
|
|
||||||
core.muOptions.trustSubscriptions.add(p)
|
|
||||||
saveMuWireSettings()
|
|
||||||
core.eventBus.publish(new TrustSubscriptionEvent(persona : p, subscribe : true))
|
|
||||||
model.subscribeButtonEnabled = false
|
|
||||||
model.markDistrustedButtonEnabled = false
|
|
||||||
model.markNeutralFromTrustedButtonEnabled = false
|
|
||||||
}
|
|
||||||
|
|
||||||
@ControllerAction
|
|
||||||
void review() {
|
|
||||||
RemoteTrustList list = getSelectedTrustList()
|
|
||||||
if (list == null)
|
|
||||||
return
|
|
||||||
Map<String,Object> env = new HashMap<>()
|
|
||||||
env["trustList"] = list
|
|
||||||
env["trustService"] = core.trustService
|
|
||||||
env["eventBus"] = core.eventBus
|
|
||||||
mvcGroup.createMVCGroup("trust-list", env)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@ControllerAction
|
|
||||||
void update() {
|
|
||||||
RemoteTrustList list = getSelectedTrustList()
|
|
||||||
if (list == null)
|
|
||||||
return
|
|
||||||
core.eventBus.publish(new TrustSubscriptionEvent(persona : list.persona, subscribe : true))
|
|
||||||
}
|
|
||||||
|
|
||||||
@ControllerAction
|
|
||||||
void unsubscribe() {
|
|
||||||
RemoteTrustList list = getSelectedTrustList()
|
|
||||||
if (list == null)
|
|
||||||
return
|
|
||||||
core.muOptions.trustSubscriptions.remove(list.persona)
|
|
||||||
saveMuWireSettings()
|
|
||||||
model.subscriptions.remove(list)
|
|
||||||
JTable table = builder.getVariable("subscription-table")
|
|
||||||
table.model.fireTableDataChanged()
|
|
||||||
core.eventBus.publish(new TrustSubscriptionEvent(persona : list.persona, subscribe : false))
|
|
||||||
}
|
|
||||||
|
|
||||||
private RemoteTrustList getSelectedTrustList() {
|
|
||||||
int row = view.getSelectedTrustTablesRow("subscription-table")
|
|
||||||
if (row < 0)
|
|
||||||
return null
|
|
||||||
model.subscriptions[row]
|
|
||||||
}
|
|
||||||
|
|
||||||
void unshareSelectedFile() {
|
|
||||||
def sf = view.selectedSharedFiles()
|
|
||||||
if (sf == null)
|
|
||||||
return
|
|
||||||
sf.each {
|
|
||||||
core.eventBus.publish(new FileUnsharedEvent(unsharedFile : it))
|
|
||||||
}
|
|
||||||
core.eventBus.publish(new UIPersistFilesEvent())
|
|
||||||
}
|
|
||||||
|
|
||||||
@ControllerAction
|
|
||||||
void addComment() {
|
|
||||||
def selectedFiles = view.selectedSharedFiles()
|
|
||||||
if (selectedFiles == null || selectedFiles.isEmpty())
|
|
||||||
return
|
|
||||||
|
|
||||||
Map<String, Object> params = new HashMap<>()
|
|
||||||
params['selectedFiles'] = selectedFiles
|
|
||||||
params['core'] = core
|
|
||||||
mvcGroup.createMVCGroup("add-comment", "Add Comment", params)
|
|
||||||
}
|
|
||||||
|
|
||||||
void saveMuWireSettings() {
|
|
||||||
File f = new File(core.home, "MuWire.properties")
|
|
||||||
f.withOutputStream {
|
|
||||||
core.muOptions.write(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void mvcGroupInit(Map<String, String> args) {
|
void mvcGroupInit(Map<String, String> args) {
|
||||||
|
@@ -1,48 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
@ArtifactProviderFor(GriffonController)
|
|
||||||
class MuWireStatusController {
|
|
||||||
@MVCMember @Nonnull
|
|
||||||
MuWireStatusModel model
|
|
||||||
@MVCMember @Nonnull
|
|
||||||
MuWireStatusView view
|
|
||||||
|
|
||||||
@ControllerAction
|
|
||||||
void refresh() {
|
|
||||||
Core core = application.context.get("core")
|
|
||||||
|
|
||||||
int incoming = 0
|
|
||||||
int outgoing = 0
|
|
||||||
core.connectionManager.getConnections().each {
|
|
||||||
if (it.incoming)
|
|
||||||
incoming++
|
|
||||||
else
|
|
||||||
outgoing++
|
|
||||||
}
|
|
||||||
model.incomingConnections = incoming
|
|
||||||
model.outgoingConnections = outgoing
|
|
||||||
|
|
||||||
model.knownHosts = core.hostCache.hosts.size()
|
|
||||||
model.failingHosts = core.hostCache.countFailingHosts()
|
|
||||||
model.hopelessHosts = core.hostCache.countHopelessHosts()
|
|
||||||
|
|
||||||
|
|
||||||
model.sharedFiles = core.fileManager.fileToSharedFile.size()
|
|
||||||
|
|
||||||
model.downloads = core.downloadManager.downloaders.size()
|
|
||||||
}
|
|
||||||
|
|
||||||
@ControllerAction
|
|
||||||
void close() {
|
|
||||||
view.dialog.setVisible(false)
|
|
||||||
mvcGroup.destroy()
|
|
||||||
}
|
|
||||||
}
|
|
@@ -4,15 +4,9 @@ import griffon.core.artifact.GriffonController
|
|||||||
import griffon.core.controller.ControllerAction
|
import griffon.core.controller.ControllerAction
|
||||||
import griffon.inject.MVCMember
|
import griffon.inject.MVCMember
|
||||||
import griffon.metadata.ArtifactProviderFor
|
import griffon.metadata.ArtifactProviderFor
|
||||||
import groovy.util.logging.Log
|
|
||||||
|
|
||||||
import java.util.logging.Level
|
|
||||||
|
|
||||||
import javax.annotation.Nonnull
|
import javax.annotation.Nonnull
|
||||||
import javax.swing.JFileChooser
|
|
||||||
|
|
||||||
import com.muwire.core.Core
|
import com.muwire.core.Core
|
||||||
import com.muwire.core.MuWireSettings
|
|
||||||
|
|
||||||
@ArtifactProviderFor(GriffonController)
|
@ArtifactProviderFor(GriffonController)
|
||||||
class OptionsController {
|
class OptionsController {
|
||||||
@@ -25,7 +19,6 @@ class OptionsController {
|
|||||||
void save() {
|
void save() {
|
||||||
String text
|
String text
|
||||||
Core core = application.context.get("core")
|
Core core = application.context.get("core")
|
||||||
MuWireSettings settings = application.context.get("muwire-settings")
|
|
||||||
|
|
||||||
def i2pProps = core.i2pOptions
|
def i2pProps = core.i2pOptions
|
||||||
|
|
||||||
@@ -45,17 +38,6 @@ class OptionsController {
|
|||||||
model.outboundLength = text
|
model.outboundLength = text
|
||||||
i2pProps["outbound.length"] = text
|
i2pProps["outbound.length"] = text
|
||||||
|
|
||||||
if (settings.embeddedRouter) {
|
|
||||||
text = view.i2pNTCPPortField.text
|
|
||||||
model.i2pNTCPPort = text
|
|
||||||
i2pProps["i2np.ntcp.port"] = text
|
|
||||||
|
|
||||||
text = view.i2pUDPPortField.text
|
|
||||||
model.i2pUDPPort = text
|
|
||||||
i2pProps["i2np.udp.port"] = text
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
File i2pSettingsFile = new File(core.home, "i2p.properties")
|
File i2pSettingsFile = new File(core.home, "i2p.properties")
|
||||||
i2pSettingsFile.withOutputStream {
|
i2pSettingsFile.withOutputStream {
|
||||||
i2pProps.store(it,"")
|
i2pProps.store(it,"")
|
||||||
@@ -64,68 +46,20 @@ class OptionsController {
|
|||||||
text = view.retryField.text
|
text = view.retryField.text
|
||||||
model.downloadRetryInterval = text
|
model.downloadRetryInterval = text
|
||||||
|
|
||||||
|
def settings = application.context.get("muwire-settings")
|
||||||
settings.downloadRetryInterval = Integer.valueOf(text)
|
settings.downloadRetryInterval = Integer.valueOf(text)
|
||||||
|
|
||||||
text = view.updateField.text
|
text = view.updateField.text
|
||||||
model.updateCheckInterval = text
|
model.updateCheckInterval = text
|
||||||
settings.updateCheckInterval = Integer.valueOf(text)
|
settings.updateCheckInterval = Integer.valueOf(text)
|
||||||
|
|
||||||
boolean searchComments = view.searchCommentsCheckbox.model.isSelected()
|
|
||||||
model.searchComments = searchComments
|
|
||||||
settings.searchComments = searchComments
|
|
||||||
|
|
||||||
boolean autoDownloadUpdate = view.autoDownloadUpdateCheckbox.model.isSelected()
|
|
||||||
model.autoDownloadUpdate = autoDownloadUpdate
|
|
||||||
settings.autoDownloadUpdate = autoDownloadUpdate
|
|
||||||
|
|
||||||
|
|
||||||
boolean shareDownloaded = view.shareDownloadedCheckbox.model.isSelected()
|
|
||||||
model.shareDownloadedFiles = shareDownloaded
|
|
||||||
settings.shareDownloadedFiles = shareDownloaded
|
|
||||||
|
|
||||||
boolean shareHidden = view.shareHiddenCheckbox.model.isSelected()
|
|
||||||
model.shareHiddenFiles = shareHidden
|
|
||||||
settings.shareHiddenFiles = shareHidden
|
|
||||||
|
|
||||||
boolean browseFiles = view.browseFilesCheckbox.model.isSelected()
|
|
||||||
model.browseFiles = browseFiles
|
|
||||||
settings.browseFiles = browseFiles
|
|
||||||
|
|
||||||
text = view.speedSmoothSecondsField.text
|
|
||||||
model.speedSmoothSeconds = Integer.valueOf(text)
|
|
||||||
settings.speedSmoothSeconds = Integer.valueOf(text)
|
|
||||||
|
|
||||||
String downloadLocation = model.downloadLocation
|
|
||||||
settings.downloadLocation = new File(downloadLocation)
|
|
||||||
|
|
||||||
String incompleteLocation = model.incompleteLocation
|
|
||||||
settings.incompleteLocation = new File(incompleteLocation)
|
|
||||||
|
|
||||||
if (settings.embeddedRouter) {
|
|
||||||
text = view.inBwField.text
|
|
||||||
model.inBw = text
|
|
||||||
settings.inBw = Integer.valueOf(text)
|
|
||||||
text = view.outBwField.text
|
|
||||||
model.outBw = text
|
|
||||||
settings.outBw = Integer.valueOf(text)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
boolean onlyTrusted = view.allowUntrustedCheckbox.model.isSelected()
|
boolean onlyTrusted = view.allowUntrustedCheckbox.model.isSelected()
|
||||||
model.onlyTrusted = onlyTrusted
|
model.onlyTrusted = onlyTrusted
|
||||||
settings.setAllowUntrusted(!onlyTrusted)
|
settings.setAllowUntrusted(!onlyTrusted)
|
||||||
|
|
||||||
boolean searchExtraHop = view.searchExtraHopCheckbox.model.isSelected()
|
boolean shareDownloaded = view.shareDownloadedCheckbox.model.isSelected()
|
||||||
model.searchExtraHop = searchExtraHop
|
model.shareDownloadedFiles = shareDownloaded
|
||||||
settings.searchExtraHop = searchExtraHop
|
settings.shareDownloadedFiles = shareDownloaded
|
||||||
|
|
||||||
boolean trustLists = view.allowTrustListsCheckbox.model.isSelected()
|
|
||||||
model.trustLists = trustLists
|
|
||||||
settings.allowTrustLists = trustLists
|
|
||||||
|
|
||||||
String trustListInterval = view.trustListIntervalField.text
|
|
||||||
model.trustListInterval = trustListInterval
|
|
||||||
settings.trustListInterval = Integer.parseInt(trustListInterval)
|
|
||||||
|
|
||||||
File settingsFile = new File(core.home, "MuWire.properties")
|
File settingsFile = new File(core.home, "MuWire.properties")
|
||||||
settingsFile.withOutputStream {
|
settingsFile.withOutputStream {
|
||||||
@@ -143,8 +77,9 @@ class OptionsController {
|
|||||||
model.font = text
|
model.font = text
|
||||||
uiSettings.font = text
|
uiSettings.font = text
|
||||||
|
|
||||||
uiSettings.autoFontSize = model.automaticFontSize
|
boolean showMonitor = view.monitorCheckbox.model.isSelected()
|
||||||
uiSettings.fontSize = Integer.parseInt(view.fontSizeField.text)
|
model.showMonitor = showMonitor
|
||||||
|
uiSettings.showMonitor = showMonitor
|
||||||
|
|
||||||
boolean clearCancelledDownloads = view.clearCancelledDownloadsCheckbox.model.isSelected()
|
boolean clearCancelledDownloads = view.clearCancelledDownloadsCheckbox.model.isSelected()
|
||||||
model.clearCancelledDownloads = clearCancelledDownloads
|
model.clearCancelledDownloads = clearCancelledDownloads
|
||||||
@@ -158,6 +93,10 @@ class OptionsController {
|
|||||||
model.excludeLocalResult = excludeLocalResult
|
model.excludeLocalResult = excludeLocalResult
|
||||||
uiSettings.excludeLocalResult = excludeLocalResult
|
uiSettings.excludeLocalResult = excludeLocalResult
|
||||||
|
|
||||||
|
boolean showSearchHashes = view.showSearchHashesCheckbox.model.isSelected()
|
||||||
|
model.showSearchHashes = showSearchHashes
|
||||||
|
uiSettings.showSearchHashes = showSearchHashes
|
||||||
|
|
||||||
File uiSettingsFile = new File(core.home, "gui.properties")
|
File uiSettingsFile = new File(core.home, "gui.properties")
|
||||||
uiSettingsFile.withOutputStream {
|
uiSettingsFile.withOutputStream {
|
||||||
uiSettings.write(it)
|
uiSettings.write(it)
|
||||||
@@ -171,37 +110,4 @@ class OptionsController {
|
|||||||
view.d.setVisible(false)
|
view.d.setVisible(false)
|
||||||
mvcGroup.destroy()
|
mvcGroup.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ControllerAction
|
|
||||||
void downloadLocation() {
|
|
||||||
def chooser = new JFileChooser()
|
|
||||||
chooser.setFileHidingEnabled(false)
|
|
||||||
chooser.setDialogTitle("Select location for downloaded files")
|
|
||||||
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY)
|
|
||||||
int rv = chooser.showOpenDialog(null)
|
|
||||||
if (rv == JFileChooser.APPROVE_OPTION)
|
|
||||||
model.downloadLocation = chooser.getSelectedFile().getAbsolutePath()
|
|
||||||
}
|
|
||||||
|
|
||||||
@ControllerAction
|
|
||||||
void incompleteLocation() {
|
|
||||||
def chooser = new JFileChooser()
|
|
||||||
chooser.setFileHidingEnabled(false)
|
|
||||||
chooser.setDialogTitle("Select location for downloaded files")
|
|
||||||
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY)
|
|
||||||
int rv = chooser.showOpenDialog(null)
|
|
||||||
if (rv == JFileChooser.APPROVE_OPTION)
|
|
||||||
model.incompleteLocation = chooser.getSelectedFile().getAbsolutePath()
|
|
||||||
}
|
|
||||||
|
|
||||||
@ControllerAction
|
|
||||||
void automaticFontAction() {
|
|
||||||
model.automaticFontSize = true
|
|
||||||
model.customFontSize = 12
|
|
||||||
}
|
|
||||||
|
|
||||||
@ControllerAction
|
|
||||||
void customFontAction() {
|
|
||||||
model.automaticFontSize = false
|
|
||||||
}
|
|
||||||
}
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user