Compare commits
140 Commits
partial-pi
...
browse-hos
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2b04374e23 | ||
![]() |
383addbc37 | ||
![]() |
cc39cd7f8e | ||
![]() |
83665d7524 | ||
![]() |
94340480b4 | ||
![]() |
8850d49c63 | ||
![]() |
f0f9d840f0 | ||
![]() |
7f4cd4f331 | ||
![]() |
e6162503f6 | ||
![]() |
7a5d71dc36 | ||
![]() |
6fa39a5e35 | ||
![]() |
c5ae804f61 | ||
![]() |
d7695b448d | ||
![]() |
946d9c8f32 | ||
![]() |
02441ca1e3 | ||
![]() |
5fa21b2360 | ||
![]() |
d4c08f4fe6 | ||
![]() |
942de287c6 | ||
![]() |
d0299f80c6 | ||
![]() |
1227cf9263 | ||
![]() |
a05575485f | ||
![]() |
f5bccd8126 | ||
![]() |
70fb789abf | ||
![]() |
feb712c253 | ||
![]() |
d22b403e2a | ||
![]() |
a24982e0df | ||
![]() |
6c26019164 | ||
![]() |
965fa79bbf | ||
![]() |
60ddb85461 | ||
![]() |
c7284623bc | ||
![]() |
3e7f2aa70a | ||
![]() |
4f436a636c | ||
![]() |
b49dbc30c3 | ||
![]() |
c25d314e1c | ||
![]() |
b28587a275 | ||
![]() |
8b8e5d59be | ||
![]() |
70bbe1f636 | ||
![]() |
337605dc0f | ||
![]() |
14bdfa6b2e | ||
![]() |
ed3f9da773 | ||
![]() |
251080d08f | ||
![]() |
f530ab999d | ||
![]() |
4133384e48 | ||
![]() |
600fc98868 | ||
![]() |
129eeb3b88 | ||
![]() |
20b51b78a0 | ||
![]() |
33fe755b60 | ||
![]() |
8b0668a134 | ||
![]() |
730d2202fd | ||
![]() |
69906a986d | ||
![]() |
5bc8fa8633 | ||
![]() |
7de7c9d8f3 | ||
![]() |
e943f6019d | ||
![]() |
2eec7bec5b | ||
![]() |
c36110cf76 | ||
![]() |
abe28517bc | ||
![]() |
15bc4c064d | ||
![]() |
91d771944b | ||
![]() |
e09c456a13 | ||
![]() |
d9c1067226 | ||
![]() |
eda3e7ad3a | ||
![]() |
e9798c7eaa | ||
![]() |
66bb4eef5b | ||
![]() |
55f260b3f4 | ||
![]() |
32d4c3965e | ||
![]() |
de1534d837 | ||
![]() |
7b58e8a88a | ||
![]() |
8a03b89985 | ||
![]() |
1d97374857 | ||
![]() |
549e8c2d98 | ||
![]() |
b54d24db0d | ||
![]() |
fa12e84345 | ||
![]() |
6430ff2691 | ||
![]() |
591313c81c | ||
![]() |
ce7b6a0c65 | ||
![]() |
5c4d4c4580 | ||
![]() |
4cb864ff9f | ||
![]() |
417675ad07 | ||
![]() |
9513e5ba3c | ||
![]() |
85610cf169 | ||
![]() |
e8322384b8 | ||
![]() |
179279ed30 | ||
![]() |
ae79f0fded | ||
![]() |
ed878b3762 | ||
![]() |
623cca0ef2 | ||
![]() |
eaa883c3ba | ||
![]() |
7ae8076865 | ||
![]() |
b1aa92661c | ||
![]() |
9ed94c8376 | ||
![]() |
fa6aea1abe | ||
![]() |
0de84e704b | ||
![]() |
a767dda044 | ||
![]() |
56e9235d7b | ||
![]() |
2fba9a74ce | ||
![]() |
2bb6826906 | ||
![]() |
9f339629a9 | ||
![]() |
58d4207f94 | ||
![]() |
32577a28dc | ||
![]() |
f7b43304d4 | ||
![]() |
dcbe09886d | ||
![]() |
5a54b2dcda | ||
![]() |
581293b24f | ||
![]() |
cd072b9f76 | ||
![]() |
6b74fc5956 | ||
![]() |
3de2f872bb | ||
![]() |
fcde917d08 | ||
![]() |
4ded065010 | ||
![]() |
18a1c7091a | ||
![]() |
46aee19f80 | ||
![]() |
92dd7064c6 | ||
![]() |
b2e4dda677 | ||
![]() |
e77a2c8961 | ||
![]() |
ee2fd2ef68 | ||
![]() |
3f95d2bf1d | ||
![]() |
1390983732 | ||
![]() |
ce660cefe9 | ||
![]() |
72b81eb886 | ||
![]() |
57d593a68a | ||
![]() |
39a81a3376 | ||
![]() |
fd0bf17c24 | ||
![]() |
ac12bff69b | ||
![]() |
feef773bac | ||
![]() |
239d8f12a7 | ||
![]() |
8bbc61a7cb | ||
![]() |
7f31c4477f | ||
![]() |
6bad67c1bf | ||
![]() |
c76e6dc99f | ||
![]() |
acf9db0db3 | ||
![]() |
69b4f0b547 | ||
![]() |
80e165b505 | ||
![]() |
bcce55b873 | ||
![]() |
d5c92560db | ||
![]() |
f827c1c9bf | ||
![]() |
88c5f1a02d | ||
![]() |
d8e44f5f39 | ||
![]() |
72ff47ffe5 | ||
![]() |
066ee2c96d | ||
![]() |
0a8016dea7 | ||
![]() |
db36367b11 | ||
![]() |
b6c9ccb7f6 |
22
README.md
22
README.md
@@ -4,11 +4,11 @@ MuWire is an easy to use file-sharing program which offers anonymity using [I2P
|
|||||||
|
|
||||||
It is inspired by the LimeWire Gnutella client and developped by a former LimeWire developer.
|
It is inspired by the LimeWire Gnutella client and developped by a former LimeWire developer.
|
||||||
|
|
||||||
The current stable release - 0.4.6 is avaiable for download at https://muwire.com. You can find technical documentation in the "doc" folder.
|
The current stable release - 0.4.15 is avaiable for download at https://muwire.com. You can find technical documentation in the "doc" folder.
|
||||||
|
|
||||||
### Building
|
### Building
|
||||||
|
|
||||||
You need JRE 8 or newer. After installing that and setting up the appropriate paths, just type
|
You need JDK 8 or newer. After installing that and setting up the appropriate paths, just type
|
||||||
|
|
||||||
```
|
```
|
||||||
./gradlew clean assemble
|
./gradlew clean assemble
|
||||||
@@ -19,13 +19,23 @@ If you want to run the unit tests, type
|
|||||||
./gradlew clean build
|
./gradlew clean build
|
||||||
```
|
```
|
||||||
|
|
||||||
Some of the UI tests will fail because they haven't been written yet :-/
|
If you want to build binary bundles that do not depend on Java or I2P, see the https://github.com/zlatinb/muwire-pkg project
|
||||||
|
|
||||||
### Running
|
### Running
|
||||||
|
|
||||||
After you build the application, look inside `gui/build/distributions`. Untar/unzip one of the `shadow` files and then run the jar contained inside by typing `java -jar MuWire-x.y.z.jar` in a terminal or command prompt.
|
After you build the application, look inside `gui/build/distributions`. Untar/unzip one of the `shadow` files and then run the jar contained inside by typing `java -jar gui-x.y.z.jar` in a terminal or command prompt.
|
||||||
|
|
||||||
If you have an I2P router running on the same machine that is all you need to do. If you use a custom I2CP host and port, create a file `$HOME/.MuWire/i2p.properties` and put `i2cp.tcp.host=<host>` and `i2cp.tcp.port=<port>` in there.
|
If you have an I2P router running on the same machine that is all you need to do. If you use a custom I2CP host and port, create a file `i2p.properties` and put `i2cp.tcp.host=<host>` and `i2cp.tcp.port=<port>` in there. On Windows that file should go into `%HOME%\AppData\Roaming\MuWire`, on Mac into `$HOME/Library/Application Support/MuWire` and on Linux `$HOME/.MuWire`
|
||||||
|
|
||||||
If you do not have an I2P router, pass the following switch to the Java process: `-DembeddedRouter=true`. This will launch MuWire's embedded router. Be aware that this causes startup to take a lot longer.
|
[Default I2CP port]\: `7654`
|
||||||
|
|
||||||
|
### GPG Fingerprint
|
||||||
|
|
||||||
|
```
|
||||||
|
471B 9FD4 5517 A5ED 101F C57D A728 3207 2D52 5E41
|
||||||
|
```
|
||||||
|
|
||||||
|
You can find the full key at https://keybase.io/zlatinb
|
||||||
|
|
||||||
|
|
||||||
|
[Default I2CP port]: https://geti2p.net/en/docs/ports
|
||||||
|
7
TODO.md
7
TODO.md
@@ -12,10 +12,6 @@ This reduces query traffic by not sending last hop queries to peers that definit
|
|||||||
|
|
||||||
This helps with scalability
|
This helps with scalability
|
||||||
|
|
||||||
##### Content Control Panel
|
|
||||||
|
|
||||||
To allow every user to not route queries for content they do not like. This is mostly GUI work, the backend part is simple
|
|
||||||
|
|
||||||
##### Web UI, REST Interface, etc.
|
##### Web UI, REST Interface, etc.
|
||||||
|
|
||||||
Basically any non-gui non-cli user interface
|
Basically any non-gui non-cli user interface
|
||||||
@@ -27,5 +23,4 @@ To enable parsing of metadata from known file types and the user editing it or a
|
|||||||
### Small Items
|
### Small Items
|
||||||
|
|
||||||
* Wrapper of some kind for in-place upgrades
|
* Wrapper of some kind for in-place upgrades
|
||||||
* Download file sequentially
|
* Automatic adjustment of number of I2P tunnels
|
||||||
* Multiple-selection download, Ctrl-A
|
|
||||||
|
@@ -2,7 +2,7 @@ subprojects {
|
|||||||
apply plugin: 'groovy'
|
apply plugin: 'groovy'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile 'net.i2p:i2p:0.9.41'
|
compile 'net.i2p:i2p:0.9.42'
|
||||||
compile 'org.codehaus.groovy:groovy-all:2.4.15'
|
compile 'org.codehaus.groovy:groovy-all:2.4.15'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -35,7 +35,7 @@ class Cli {
|
|||||||
|
|
||||||
Core core
|
Core core
|
||||||
try {
|
try {
|
||||||
core = new Core(props, home, "0.4.7")
|
core = new Core(props, home, "0.5.0")
|
||||||
} catch (Exception bad) {
|
} catch (Exception bad) {
|
||||||
bad.printStackTrace(System.out)
|
bad.printStackTrace(System.out)
|
||||||
println "Failed to initialize core, exiting"
|
println "Failed to initialize core, exiting"
|
||||||
|
@@ -53,7 +53,7 @@ class CliDownloader {
|
|||||||
|
|
||||||
Core core
|
Core core
|
||||||
try {
|
try {
|
||||||
core = new Core(props, home, "0.4.7")
|
core = new Core(props, home, "0.5.0")
|
||||||
} catch (Exception bad) {
|
} catch (Exception bad) {
|
||||||
bad.printStackTrace(System.out)
|
bad.printStackTrace(System.out)
|
||||||
println "Failed to initialize core, exiting"
|
println "Failed to initialize core, exiting"
|
||||||
|
@@ -2,9 +2,9 @@ apply plugin : 'application'
|
|||||||
mainClassName = 'com.muwire.core.Core'
|
mainClassName = 'com.muwire.core.Core'
|
||||||
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties']
|
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties']
|
||||||
dependencies {
|
dependencies {
|
||||||
compile 'net.i2p:router:0.9.41'
|
compile 'net.i2p:router:0.9.42'
|
||||||
compile 'net.i2p.client:mstreaming:0.9.41'
|
compile 'net.i2p.client:mstreaming:0.9.42'
|
||||||
compile 'net.i2p.client:streaming:0.9.41'
|
compile 'net.i2p.client:streaming:0.9.42'
|
||||||
|
|
||||||
testCompile 'org.junit.jupiter:junit-jupiter-api:5.4.2'
|
testCompile 'org.junit.jupiter:junit-jupiter-api:5.4.2'
|
||||||
testCompile 'junit:junit:4.12'
|
testCompile 'junit:junit:4.12'
|
||||||
|
@@ -1,13 +0,0 @@
|
|||||||
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 String SPLIT_PATTERN = "[\\*\\+\\-,\\.:;\\(\\)=_/\\\\\\!\\\"\\\'\\\$%\\|\\[\\]\\{\\}\\?]"
|
|
||||||
}
|
|
@@ -28,6 +28,8 @@ import com.muwire.core.files.FileSharedEvent
|
|||||||
import com.muwire.core.files.FileUnsharedEvent
|
import com.muwire.core.files.FileUnsharedEvent
|
||||||
import com.muwire.core.files.HasherService
|
import com.muwire.core.files.HasherService
|
||||||
import com.muwire.core.files.PersisterService
|
import com.muwire.core.files.PersisterService
|
||||||
|
import com.muwire.core.files.UICommentEvent
|
||||||
|
import com.muwire.core.files.UIPersistFilesEvent
|
||||||
import com.muwire.core.files.AllFilesLoadedEvent
|
import com.muwire.core.files.AllFilesLoadedEvent
|
||||||
import com.muwire.core.files.DirectoryUnsharedEvent
|
import com.muwire.core.files.DirectoryUnsharedEvent
|
||||||
import com.muwire.core.files.DirectoryWatcher
|
import com.muwire.core.files.DirectoryWatcher
|
||||||
@@ -35,11 +37,13 @@ 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.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.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
|
||||||
@@ -48,6 +52,8 @@ import com.muwire.core.trust.TrustSubscriptionEvent
|
|||||||
import com.muwire.core.update.UpdateClient
|
import com.muwire.core.update.UpdateClient
|
||||||
import com.muwire.core.upload.UploadManager
|
import com.muwire.core.upload.UploadManager
|
||||||
import com.muwire.core.util.MuWireLogManager
|
import com.muwire.core.util.MuWireLogManager
|
||||||
|
import com.muwire.core.content.ContentControlEvent
|
||||||
|
import com.muwire.core.content.ContentManager
|
||||||
|
|
||||||
import groovy.util.logging.Log
|
import groovy.util.logging.Log
|
||||||
import net.i2p.I2PAppContext
|
import net.i2p.I2PAppContext
|
||||||
@@ -90,6 +96,7 @@ public class Core {
|
|||||||
private final DirectoryWatcher directoryWatcher
|
private final DirectoryWatcher directoryWatcher
|
||||||
final FileManager fileManager
|
final FileManager fileManager
|
||||||
final UploadManager uploadManager
|
final UploadManager uploadManager
|
||||||
|
final ContentManager contentManager
|
||||||
|
|
||||||
private final Router router
|
private final Router router
|
||||||
|
|
||||||
@@ -132,6 +139,7 @@ public class Core {
|
|||||||
} else {
|
} else {
|
||||||
log.info("launching embedded router")
|
log.info("launching embedded router")
|
||||||
Properties routerProps = new Properties()
|
Properties routerProps = new Properties()
|
||||||
|
routerProps.setProperty("i2p.dir.base", home.getAbsolutePath())
|
||||||
routerProps.setProperty("i2p.dir.config", home.getAbsolutePath())
|
routerProps.setProperty("i2p.dir.config", home.getAbsolutePath())
|
||||||
routerProps.setProperty("router.excludePeerCaps", "KLM")
|
routerProps.setProperty("router.excludePeerCaps", "KLM")
|
||||||
routerProps.setProperty("i2np.inboundKBytesPerSecond", String.valueOf(props.inBw))
|
routerProps.setProperty("i2np.inboundKBytesPerSecond", String.valueOf(props.inBw))
|
||||||
@@ -211,14 +219,16 @@ public class Core {
|
|||||||
eventBus.register(FileUnsharedEvent.class, fileManager)
|
eventBus.register(FileUnsharedEvent.class, fileManager)
|
||||||
eventBus.register(SearchEvent.class, fileManager)
|
eventBus.register(SearchEvent.class, fileManager)
|
||||||
eventBus.register(DirectoryUnsharedEvent.class, fileManager)
|
eventBus.register(DirectoryUnsharedEvent.class, fileManager)
|
||||||
|
eventBus.register(UICommentEvent.class, fileManager)
|
||||||
|
|
||||||
log.info("initializing mesh manager")
|
log.info("initializing mesh manager")
|
||||||
MeshManager meshManager = new MeshManager(fileManager, home, props)
|
MeshManager meshManager = new MeshManager(fileManager, home, props)
|
||||||
eventBus.register(SourceDiscoveredEvent.class, meshManager)
|
eventBus.register(SourceDiscoveredEvent.class, meshManager)
|
||||||
|
|
||||||
log.info "initializing persistence service"
|
log.info "initializing persistence service"
|
||||||
persisterService = new PersisterService(new File(home, "files.json"), eventBus, 15000, fileManager)
|
persisterService = new PersisterService(new File(home, "files.json"), eventBus, 60000, fileManager)
|
||||||
eventBus.register(UILoadedEvent.class, persisterService)
|
eventBus.register(UILoadedEvent.class, persisterService)
|
||||||
|
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")
|
||||||
@@ -247,7 +257,7 @@ public class Core {
|
|||||||
I2PConnector i2pConnector = new I2PConnector(socketManager)
|
I2PConnector i2pConnector = new I2PConnector(socketManager)
|
||||||
|
|
||||||
log.info "initializing results sender"
|
log.info "initializing results sender"
|
||||||
ResultsSender resultsSender = new ResultsSender(eventBus, i2pConnector, me)
|
ResultsSender resultsSender = new ResultsSender(eventBus, i2pConnector, me, props)
|
||||||
|
|
||||||
log.info "initializing search manager"
|
log.info "initializing search manager"
|
||||||
SearchManager searchManager = new SearchManager(eventBus, me, resultsSender)
|
SearchManager searchManager = new SearchManager(eventBus, me, resultsSender)
|
||||||
@@ -273,22 +283,34 @@ 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, connectionEstablisher)
|
i2pAcceptor, hostCache, trustService, searchManager, uploadManager, fileManager, connectionEstablisher)
|
||||||
|
|
||||||
log.info("initializing directory watcher")
|
log.info("initializing directory watcher")
|
||||||
directoryWatcher = new DirectoryWatcher(eventBus, fileManager)
|
directoryWatcher = new DirectoryWatcher(eventBus, fileManager, home, props)
|
||||||
eventBus.register(FileSharedEvent.class, directoryWatcher)
|
eventBus.register(FileSharedEvent.class, directoryWatcher)
|
||||||
eventBus.register(AllFilesLoadedEvent.class, directoryWatcher)
|
eventBus.register(AllFilesLoadedEvent.class, directoryWatcher)
|
||||||
eventBus.register(DirectoryUnsharedEvent.class, directoryWatcher)
|
eventBus.register(DirectoryUnsharedEvent.class, directoryWatcher)
|
||||||
|
|
||||||
log.info("initializing hasher service")
|
log.info("initializing hasher service")
|
||||||
hasherService = new HasherService(new FileHasher(), eventBus, fileManager)
|
hasherService = new HasherService(new FileHasher(), eventBus, fileManager, props)
|
||||||
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")
|
log.info("initializing trust subscriber")
|
||||||
trustSubscriber = new TrustSubscriber(eventBus, i2pConnector, props)
|
trustSubscriber = new TrustSubscriber(eventBus, i2pConnector, props)
|
||||||
eventBus.register(UILoadedEvent.class, trustSubscriber)
|
eventBus.register(UILoadedEvent.class, trustSubscriber)
|
||||||
eventBus.register(TrustSubscriptionEvent.class, trustSubscriber)
|
eventBus.register(TrustSubscriptionEvent.class, trustSubscriber)
|
||||||
|
|
||||||
|
log.info("initializing content manager")
|
||||||
|
contentManager = new ContentManager()
|
||||||
|
eventBus.register(ContentControlEvent.class, contentManager)
|
||||||
|
eventBus.register(QueryEvent.class, contentManager)
|
||||||
|
|
||||||
|
log.info("initializing browse manager")
|
||||||
|
BrowseManager browseManager = new BrowseManager(i2pConnector, eventBus)
|
||||||
|
eventBus.register(UIBrowseEvent.class, browseManager)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startServices() {
|
public void startServices() {
|
||||||
@@ -353,7 +375,7 @@ public class Core {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Core core = new Core(props, home, "0.4.7")
|
Core core = new Core(props, home, "0.5.0")
|
||||||
core.startServices()
|
core.startServices()
|
||||||
|
|
||||||
// ... at the end, sleep or execute script
|
// ... at the end, sleep or execute script
|
||||||
|
@@ -48,4 +48,9 @@ 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,11 +6,13 @@ import com.muwire.core.hostcache.CrawlerResponse
|
|||||||
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.util.ConcurrentHashSet
|
||||||
|
|
||||||
class MuWireSettings {
|
class MuWireSettings {
|
||||||
|
|
||||||
final boolean isLeaf
|
final boolean isLeaf
|
||||||
boolean allowUntrusted
|
boolean allowUntrusted
|
||||||
|
boolean searchExtraHop
|
||||||
boolean allowTrustLists
|
boolean allowTrustLists
|
||||||
int trustListInterval
|
int trustListInterval
|
||||||
Set<Persona> trustSubscriptions
|
Set<Persona> trustSubscriptions
|
||||||
@@ -22,12 +24,17 @@ class MuWireSettings {
|
|||||||
File downloadLocation
|
File downloadLocation
|
||||||
CrawlerResponse crawlerResponse
|
CrawlerResponse crawlerResponse
|
||||||
boolean shareDownloadedFiles
|
boolean shareDownloadedFiles
|
||||||
|
boolean shareHiddenFiles
|
||||||
|
boolean searchComments
|
||||||
|
boolean browseFiles
|
||||||
Set<String> watchedDirectories
|
Set<String> watchedDirectories
|
||||||
float downloadSequentialRatio
|
float downloadSequentialRatio
|
||||||
int hostClearInterval
|
int hostClearInterval, hostHopelessInterval, hostRejectInterval
|
||||||
int meshExpiration
|
int meshExpiration
|
||||||
boolean embeddedRouter
|
boolean embeddedRouter
|
||||||
int inBw, outBw
|
int inBw, outBw
|
||||||
|
Set<String> watchedKeywords
|
||||||
|
Set<String> watchedRegexes
|
||||||
|
|
||||||
MuWireSettings() {
|
MuWireSettings() {
|
||||||
this(new Properties())
|
this(new Properties())
|
||||||
@@ -36,29 +43,33 @@ 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.getProperty("allowUntrusted","true"))
|
||||||
|
searchExtraHop = Boolean.valueOf(props.getProperty("searchExtraHop","false"))
|
||||||
allowTrustLists = Boolean.valueOf(props.getProperty("allowTrustLists","true"))
|
allowTrustLists = Boolean.valueOf(props.getProperty("allowTrustLists","true"))
|
||||||
trustListInterval = Integer.valueOf(props.getProperty("trustListInterval","1"))
|
trustListInterval = Integer.valueOf(props.getProperty("trustListInterval","1"))
|
||||||
crawlerResponse = CrawlerResponse.valueOf(props.get("crawlerResponse","REGISTERED"))
|
crawlerResponse = CrawlerResponse.valueOf(props.get("crawlerResponse","REGISTERED"))
|
||||||
nickname = props.getProperty("nickname","MuWireUser")
|
nickname = props.getProperty("nickname","MuWireUser")
|
||||||
downloadLocation = new File((String)props.getProperty("downloadLocation",
|
downloadLocation = new File((String)props.getProperty("downloadLocation",
|
||||||
System.getProperty("user.home")))
|
System.getProperty("user.home")))
|
||||||
downloadRetryInterval = Integer.parseInt(props.getProperty("downloadRetryInterval","1"))
|
downloadRetryInterval = Integer.parseInt(props.getProperty("downloadRetryInterval","60"))
|
||||||
updateCheckInterval = Integer.parseInt(props.getProperty("updateCheckInterval","24"))
|
updateCheckInterval = Integer.parseInt(props.getProperty("updateCheckInterval","24"))
|
||||||
autoDownloadUpdate = Boolean.parseBoolean(props.getProperty("autoDownloadUpdate","true"))
|
autoDownloadUpdate = Boolean.parseBoolean(props.getProperty("autoDownloadUpdate","true"))
|
||||||
updateType = props.getProperty("updateType","jar")
|
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"))
|
||||||
downloadSequentialRatio = Float.valueOf(props.getProperty("downloadSequentialRatio","0.8"))
|
downloadSequentialRatio = Float.valueOf(props.getProperty("downloadSequentialRatio","0.8"))
|
||||||
hostClearInterval = Integer.valueOf(props.getProperty("hostClearInterval","60"))
|
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"))
|
meshExpiration = Integer.valueOf(props.getProperty("meshExpiration","60"))
|
||||||
embeddedRouter = Boolean.valueOf(props.getProperty("embeddedRouter","false"))
|
embeddedRouter = Boolean.valueOf(props.getProperty("embeddedRouter","false"))
|
||||||
inBw = Integer.valueOf(props.getProperty("inBw","256"))
|
inBw = Integer.valueOf(props.getProperty("inBw","256"))
|
||||||
outBw = Integer.valueOf(props.getProperty("outBw","128"))
|
outBw = Integer.valueOf(props.getProperty("outBw","128"))
|
||||||
|
searchComments = Boolean.valueOf(props.getProperty("searchComments","true"))
|
||||||
|
browseFiles = Boolean.valueOf(props.getProperty("browseFiles","true"))
|
||||||
|
|
||||||
watchedDirectories = new HashSet<>()
|
watchedDirectories = readEncodedSet(props, "watchedDirectories")
|
||||||
if (props.containsKey("watchedDirectories")) {
|
watchedKeywords = readEncodedSet(props, "watchedKeywords")
|
||||||
String[] encoded = props.getProperty("watchedDirectories").split(",")
|
watchedRegexes = readEncodedSet(props, "watchedRegexes")
|
||||||
encoded.each { watchedDirectories << DataUtil.readi18nString(Base64.decode(it)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
trustSubscriptions = new HashSet<>()
|
trustSubscriptions = new HashSet<>()
|
||||||
if (props.containsKey("trustSubscriptions")) {
|
if (props.containsKey("trustSubscriptions")) {
|
||||||
@@ -66,12 +77,15 @@ class MuWireSettings {
|
|||||||
trustSubscriptions.add(new Persona(new ByteArrayInputStream(Base64.decode(it))))
|
trustSubscriptions.add(new Persona(new ByteArrayInputStream(Base64.decode(it))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void write(OutputStream out) throws IOException {
|
void write(OutputStream out) throws IOException {
|
||||||
Properties props = new Properties()
|
Properties props = new Properties()
|
||||||
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("allowTrustLists", String.valueOf(allowTrustLists))
|
||||||
props.setProperty("trustListInterval", String.valueOf(trustListInterval))
|
props.setProperty("trustListInterval", String.valueOf(trustListInterval))
|
||||||
props.setProperty("crawlerResponse", crawlerResponse.toString())
|
props.setProperty("crawlerResponse", crawlerResponse.toString())
|
||||||
@@ -82,19 +96,21 @@ class MuWireSettings {
|
|||||||
props.setProperty("autoDownloadUpdate", String.valueOf(autoDownloadUpdate))
|
props.setProperty("autoDownloadUpdate", String.valueOf(autoDownloadUpdate))
|
||||||
props.setProperty("updateType",String.valueOf(updateType))
|
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("downloadSequentialRatio", String.valueOf(downloadSequentialRatio))
|
props.setProperty("downloadSequentialRatio", String.valueOf(downloadSequentialRatio))
|
||||||
props.setProperty("hostClearInterval", String.valueOf(hostClearInterval))
|
props.setProperty("hostClearInterval", String.valueOf(hostClearInterval))
|
||||||
|
props.setProperty("hostHopelessInterval", String.valueOf(hostHopelessInterval))
|
||||||
|
props.setProperty("hostRejectInterval", String.valueOf(hostRejectInterval))
|
||||||
props.setProperty("meshExpiration", String.valueOf(meshExpiration))
|
props.setProperty("meshExpiration", String.valueOf(meshExpiration))
|
||||||
props.setProperty("embeddedRouter", String.valueOf(embeddedRouter))
|
props.setProperty("embeddedRouter", String.valueOf(embeddedRouter))
|
||||||
props.setProperty("inBw", String.valueOf(inBw))
|
props.setProperty("inBw", String.valueOf(inBw))
|
||||||
props.setProperty("outBw", String.valueOf(outBw))
|
props.setProperty("outBw", String.valueOf(outBw))
|
||||||
|
props.setProperty("searchComments", String.valueOf(searchComments))
|
||||||
|
props.setProperty("browseFiles", String.valueOf(browseFiles))
|
||||||
|
|
||||||
if (!watchedDirectories.isEmpty()) {
|
writeEncodedSet(watchedDirectories, "watchedDirectories", props)
|
||||||
String encoded = watchedDirectories.stream().
|
writeEncodedSet(watchedKeywords, "watchedKeywords", props)
|
||||||
map({Base64.encode(DataUtil.encodei18nString(it))}).
|
writeEncodedSet(watchedRegexes, "watchedRegexes", props)
|
||||||
collect(Collectors.joining(","))
|
|
||||||
props.setProperty("watchedDirectories", encoded)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!trustSubscriptions.isEmpty()) {
|
if (!trustSubscriptions.isEmpty()) {
|
||||||
String encoded = trustSubscriptions.stream().
|
String encoded = trustSubscriptions.stream().
|
||||||
@@ -106,6 +122,24 @@ class MuWireSettings {
|
|||||||
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
|
||||||
}
|
}
|
||||||
|
7
core/src/main/groovy/com/muwire/core/SplitPattern.groovy
Normal file
7
core/src/main/groovy/com/muwire/core/SplitPattern.groovy
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package com.muwire.core
|
||||||
|
|
||||||
|
class SplitPattern {
|
||||||
|
|
||||||
|
public static final String SPLIT_PATTERN = "[\\*\\+\\-,\\.:;\\(\\)=_/\\\\\\!\\\"\\\'\\\$%\\|\\[\\]\\{\\}\\?]";
|
||||||
|
|
||||||
|
}
|
@@ -132,6 +132,7 @@ 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
|
||||||
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()
|
||||||
@@ -209,11 +210,15 @@ 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
|
||||||
|
|
||||||
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)
|
||||||
QueryEvent event = new QueryEvent ( searchEvent : searchEvent,
|
QueryEvent event = new QueryEvent ( searchEvent : searchEvent,
|
||||||
replyTo : replyTo,
|
replyTo : replyTo,
|
||||||
originator : originator,
|
originator : originator,
|
||||||
|
@@ -10,6 +10,7 @@ import java.util.zip.InflaterInputStream
|
|||||||
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
|
||||||
@@ -17,6 +18,7 @@ import com.muwire.core.upload.UploadManager
|
|||||||
import com.muwire.core.util.DataUtil
|
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.UIResultBatchEvent
|
||||||
import com.muwire.core.search.UIResultEvent
|
import com.muwire.core.search.UIResultEvent
|
||||||
@@ -37,6 +39,7 @@ 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
|
||||||
@@ -47,7 +50,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,
|
||||||
ConnectionEstablisher establisher) {
|
FileManager fileManager, ConnectionEstablisher establisher) {
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.manager = manager
|
this.manager = manager
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
@@ -55,6 +58,7 @@ 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
|
||||||
|
|
||||||
@@ -129,6 +133,9 @@ class ConnectionAcceptor {
|
|||||||
case (byte)'T':
|
case (byte)'T':
|
||||||
processTRUST(e)
|
processTRUST(e)
|
||||||
break
|
break
|
||||||
|
case (byte)'B':
|
||||||
|
processBROWSE(e)
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
throw new Exception("Invalid read $read")
|
throw new Exception("Invalid read $read")
|
||||||
}
|
}
|
||||||
@@ -247,43 +254,85 @@ class ConnectionAcceptor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processTRUST(Endpoint e) {
|
private void processBROWSE(Endpoint e) {
|
||||||
byte[] RUST = new byte[6]
|
try {
|
||||||
DataInputStream dis = new DataInputStream(e.getInputStream())
|
byte [] rowse = new byte[7]
|
||||||
dis.readFully(RUST)
|
DataInputStream dis = new DataInputStream(e.getInputStream())
|
||||||
if (RUST != "RUST\r\n".getBytes(StandardCharsets.US_ASCII))
|
dis.readFully(rowse)
|
||||||
throw new IOException("Invalid TRUST connection")
|
if (rowse != "ROWSE\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
String header
|
throw new IOException("Invalid BROWSE connection")
|
||||||
while ((header = DataUtil.readTillRN(dis)) != ""); // ignore headers for now
|
String header
|
||||||
|
while ((header = DataUtil.readTillRN(dis)) != ""); // ignore headers for now
|
||||||
|
|
||||||
OutputStream os = e.getOutputStream()
|
OutputStream os = e.getOutputStream()
|
||||||
if (!settings.allowTrustLists) {
|
if (!settings.browseFiles) {
|
||||||
os.write("403 Not Allowed\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
os.write("403 Not Allowed\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
os.flush()
|
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(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()
|
||||||
|
} finally {
|
||||||
e.close()
|
e.close()
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
os.write("200 OK\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
private void processTRUST(Endpoint e) {
|
||||||
List<Persona> good = new ArrayList<>(trustService.good.values())
|
try {
|
||||||
int size = Math.min(Short.MAX_VALUE * 2, good.size())
|
byte[] RUST = new byte[6]
|
||||||
good = good.subList(0, size)
|
DataInputStream dis = new DataInputStream(e.getInputStream())
|
||||||
DataOutputStream dos = new DataOutputStream(os)
|
dis.readFully(RUST)
|
||||||
dos.writeShort(size)
|
if (RUST != "RUST\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
good.each {
|
throw new IOException("Invalid TRUST connection")
|
||||||
it.write(dos)
|
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()
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Persona> bad = new ArrayList<>(trustService.bad.values())
|
|
||||||
size = Math.min(Short.MAX_VALUE * 2, bad.size())
|
|
||||||
bad = bad.subList(0, size)
|
|
||||||
dos.writeShort(size)
|
|
||||||
bad.each {
|
|
||||||
it.write(dos)
|
|
||||||
}
|
|
||||||
|
|
||||||
dos.flush()
|
|
||||||
e.close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -31,7 +31,7 @@ class ConnectionEstablisher {
|
|||||||
final HostCache hostCache
|
final HostCache hostCache
|
||||||
|
|
||||||
final Timer timer
|
final Timer timer
|
||||||
final ExecutorService executor
|
final ExecutorService executor, closer
|
||||||
|
|
||||||
final Set inProgress = new ConcurrentHashSet()
|
final Set inProgress = new ConcurrentHashSet()
|
||||||
|
|
||||||
@@ -51,6 +51,8 @@ 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() {
|
||||||
@@ -60,6 +62,7 @@ class ConnectionEstablisher {
|
|||||||
void stop() {
|
void stop() {
|
||||||
timer.cancel()
|
timer.cancel()
|
||||||
executor.shutdownNow()
|
executor.shutdownNow()
|
||||||
|
closer.shutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
private void connectIfNeeded() {
|
private void connectIfNeeded() {
|
||||||
@@ -120,8 +123,10 @@ class ConnectionEstablisher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void fail(Endpoint endpoint) {
|
private void fail(Endpoint endpoint) {
|
||||||
endpoint.close()
|
closer.execute {
|
||||||
eventBus.publish(new ConnectionEvent(endpoint: endpoint, incoming: false, leaf: false, status: ConnectionAttemptStatus.FAILED))
|
endpoint.close()
|
||||||
|
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) {
|
||||||
@@ -175,7 +180,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
|
||||||
e.close()
|
closer.execute({e.close()} as Runnable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,9 @@
|
|||||||
|
package com.muwire.core.content
|
||||||
|
|
||||||
|
import com.muwire.core.Event
|
||||||
|
|
||||||
|
class ContentControlEvent extends Event {
|
||||||
|
String term
|
||||||
|
boolean regex
|
||||||
|
boolean add
|
||||||
|
}
|
@@ -0,0 +1,30 @@
|
|||||||
|
package com.muwire.core.content
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
|
import com.muwire.core.search.QueryEvent
|
||||||
|
|
||||||
|
import net.i2p.util.ConcurrentHashSet
|
||||||
|
|
||||||
|
class ContentManager {
|
||||||
|
|
||||||
|
Set<Matcher> matchers = new ConcurrentHashSet()
|
||||||
|
|
||||||
|
void onContentControlEvent(ContentControlEvent e) {
|
||||||
|
Matcher m
|
||||||
|
if (e.regex)
|
||||||
|
m = new RegexMatcher(e.term)
|
||||||
|
else
|
||||||
|
m = new KeywordMatcher(e.term)
|
||||||
|
if (e.add)
|
||||||
|
matchers.add(m)
|
||||||
|
else
|
||||||
|
matchers.remove(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
void onQueryEvent(QueryEvent e) {
|
||||||
|
if (e.searchEvent.searchTerms == null)
|
||||||
|
return
|
||||||
|
matchers.each { it.process(e) }
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,36 @@
|
|||||||
|
package com.muwire.core.content
|
||||||
|
|
||||||
|
class KeywordMatcher extends Matcher {
|
||||||
|
private final String keyword
|
||||||
|
KeywordMatcher(String keyword) {
|
||||||
|
this.keyword = keyword
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean match(List<String> searchTerms) {
|
||||||
|
boolean found = false
|
||||||
|
searchTerms.each {
|
||||||
|
if (keyword == it)
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
found
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTerm() {
|
||||||
|
keyword
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
keyword.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (!(o instanceof KeywordMatcher))
|
||||||
|
return false
|
||||||
|
KeywordMatcher other = (KeywordMatcher) o
|
||||||
|
keyword.equals(other.keyword)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,9 @@
|
|||||||
|
package com.muwire.core.content
|
||||||
|
|
||||||
|
import com.muwire.core.Persona
|
||||||
|
|
||||||
|
class Match {
|
||||||
|
Persona persona
|
||||||
|
String [] keywords
|
||||||
|
long timestamp
|
||||||
|
}
|
20
core/src/main/groovy/com/muwire/core/content/Matcher.groovy
Normal file
20
core/src/main/groovy/com/muwire/core/content/Matcher.groovy
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package com.muwire.core.content
|
||||||
|
|
||||||
|
import com.muwire.core.search.QueryEvent
|
||||||
|
|
||||||
|
abstract class Matcher {
|
||||||
|
final List<Match> matches = Collections.synchronizedList(new ArrayList<>())
|
||||||
|
final Set<UUID> uuids = new HashSet<>()
|
||||||
|
|
||||||
|
protected abstract boolean match(List<String> searchTerms);
|
||||||
|
|
||||||
|
public abstract String getTerm();
|
||||||
|
|
||||||
|
public void process(QueryEvent qe) {
|
||||||
|
def terms = qe.searchEvent.searchTerms
|
||||||
|
if (match(terms) && uuids.add(qe.searchEvent.uuid)) {
|
||||||
|
long now = System.currentTimeMillis()
|
||||||
|
matches << new Match(persona : qe.originator, keywords : terms, timestamp : now)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,35 @@
|
|||||||
|
package com.muwire.core.content
|
||||||
|
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
import java.util.stream.Collectors
|
||||||
|
|
||||||
|
class RegexMatcher extends Matcher {
|
||||||
|
private final Pattern pattern
|
||||||
|
RegexMatcher(String pattern) {
|
||||||
|
this.pattern = Pattern.compile(pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean match(List<String> keywords) {
|
||||||
|
String combined = keywords.join(" ")
|
||||||
|
return pattern.matcher(combined).find()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTerm() {
|
||||||
|
pattern.pattern()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
pattern.pattern().hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (!(o instanceof RegexMatcher))
|
||||||
|
return false
|
||||||
|
RegexMatcher other = (RegexMatcher) o
|
||||||
|
pattern.pattern() == other.pattern.pattern()
|
||||||
|
}
|
||||||
|
}
|
@@ -74,7 +74,7 @@ public class DownloadManager {
|
|||||||
destinations.addAll(e.sources)
|
destinations.addAll(e.sources)
|
||||||
destinations.remove(me.destination)
|
destinations.remove(me.destination)
|
||||||
|
|
||||||
Pieces pieces = getPieces(infohash, size, pieceSize)
|
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,
|
||||||
@@ -123,7 +123,11 @@ public class DownloadManager {
|
|||||||
infoHash = new InfoHash(root)
|
infoHash = new InfoHash(root)
|
||||||
}
|
}
|
||||||
|
|
||||||
Pieces pieces = getPieces(infoHash, (long)json.length, json.pieceSizePow2)
|
boolean sequential = false
|
||||||
|
if (json.sequential != null)
|
||||||
|
sequential = json.sequential
|
||||||
|
|
||||||
|
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, pieces)
|
||||||
@@ -137,12 +141,12 @@ public class DownloadManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Pieces getPieces(InfoHash infoHash, long length, int pieceSizePow2) {
|
private Pieces getPieces(InfoHash infoHash, long length, int pieceSizePow2, boolean sequential) {
|
||||||
int pieceSize = 0x1 << pieceSizePow2
|
int pieceSize = 0x1 << pieceSizePow2
|
||||||
int nPieces = (int)(length / pieceSize)
|
int nPieces = (int)(length / pieceSize)
|
||||||
if (length % pieceSize != 0)
|
if (length % pieceSize != 0)
|
||||||
nPieces++
|
nPieces++
|
||||||
Mesh mesh = meshManager.getOrCreate(infoHash, nPieces)
|
Mesh mesh = meshManager.getOrCreate(infoHash, nPieces, sequential)
|
||||||
mesh.pieces
|
mesh.pieces
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,6 +192,9 @@ public class DownloadManager {
|
|||||||
json.hashRoot = Base64.encode(infoHash.getRoot())
|
json.hashRoot = Base64.encode(infoHash.getRoot())
|
||||||
|
|
||||||
json.paused = downloader.paused
|
json.paused = downloader.paused
|
||||||
|
|
||||||
|
json.sequential = downloader.pieces.ratio == 0f
|
||||||
|
|
||||||
writer.println(JsonOutput.toJson(json))
|
writer.println(JsonOutput.toJson(json))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -79,11 +79,12 @@ class DownloadSession {
|
|||||||
return false
|
return false
|
||||||
int piece = pieceAndPosition[0]
|
int piece = pieceAndPosition[0]
|
||||||
int position = pieceAndPosition[1]
|
int position = pieceAndPosition[1]
|
||||||
|
boolean steal = pieceAndPosition[2] == 1
|
||||||
boolean unclaim = true
|
boolean unclaim = true
|
||||||
|
|
||||||
log.info("will download piece $piece from position $position")
|
log.info("will download piece $piece from position $position steal $steal")
|
||||||
|
|
||||||
long pieceStart = piece * pieceSize
|
long pieceStart = piece * ((long)pieceSize)
|
||||||
long end = Math.min(fileLength, pieceStart + pieceSize) - 1
|
long end = Math.min(fileLength, pieceStart + pieceSize) - 1
|
||||||
long start = pieceStart + position
|
long start = pieceStart + position
|
||||||
String root = Base64.encode(infoHash.getRoot())
|
String root = Base64.encode(infoHash.getRoot())
|
||||||
@@ -208,7 +209,7 @@ class DownloadSession {
|
|||||||
pieces.markDownloaded(piece)
|
pieces.markDownloaded(piece)
|
||||||
unclaim = false
|
unclaim = false
|
||||||
} finally {
|
} finally {
|
||||||
if (unclaim)
|
if (unclaim && !steal)
|
||||||
pieces.unclaim(piece)
|
pieces.unclaim(piece)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
@@ -314,6 +314,10 @@ public class Downloader {
|
|||||||
piecesFileClosed = true
|
piecesFileClosed = true
|
||||||
piecesFile.delete()
|
piecesFile.delete()
|
||||||
}
|
}
|
||||||
|
activeWorkers.values().each {
|
||||||
|
if (it.destination != destination)
|
||||||
|
it.cancel()
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
Files.move(incompleteFile.toPath(), file.toPath(), StandardCopyOption.ATOMIC_MOVE)
|
Files.move(incompleteFile.toPath(), file.toPath(), StandardCopyOption.ATOMIC_MOVE)
|
||||||
} catch (AtomicMoveNotSupportedException e) {
|
} catch (AtomicMoveNotSupportedException e) {
|
||||||
@@ -322,7 +326,7 @@ public class Downloader {
|
|||||||
}
|
}
|
||||||
eventBus.publish(
|
eventBus.publish(
|
||||||
new FileDownloadedEvent(
|
new FileDownloadedEvent(
|
||||||
downloadedFile : new DownloadedFile(file, getInfoHash(), pieceSizePow2, successfulDestinations),
|
downloadedFile : new DownloadedFile(file.getCanonicalFile(), getInfoHash(), pieceSizePow2, successfulDestinations),
|
||||||
downloader : Downloader.this))
|
downloader : Downloader.this))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -20,14 +20,20 @@ class Pieces {
|
|||||||
|
|
||||||
synchronized int[] claim() {
|
synchronized int[] claim() {
|
||||||
int claimedCardinality = claimed.cardinality()
|
int claimedCardinality = claimed.cardinality()
|
||||||
if (claimedCardinality == nPieces)
|
if (claimedCardinality == nPieces) {
|
||||||
return null
|
// steal
|
||||||
|
int downloadedCardinality = done.cardinality()
|
||||||
|
if (downloadedCardinality == nPieces)
|
||||||
|
return null
|
||||||
|
int rv = done.nextClearBit(0)
|
||||||
|
return [rv, partials.getOrDefault(rv, 0), 1]
|
||||||
|
}
|
||||||
|
|
||||||
// if fuller than ratio just do sequential
|
// if fuller than ratio just do sequential
|
||||||
if ( (1.0f * claimedCardinality) / nPieces > ratio) {
|
if ( (1.0f * claimedCardinality) / nPieces >= ratio) {
|
||||||
int rv = claimed.nextClearBit(0)
|
int rv = claimed.nextClearBit(0)
|
||||||
claimed.set(rv)
|
claimed.set(rv)
|
||||||
return [rv, partials.getOrDefault(rv, 0)]
|
return [rv, partials.getOrDefault(rv, 0), 0]
|
||||||
}
|
}
|
||||||
|
|
||||||
while(true) {
|
while(true) {
|
||||||
@@ -35,20 +41,29 @@ class Pieces {
|
|||||||
if (claimed.get(start))
|
if (claimed.get(start))
|
||||||
continue
|
continue
|
||||||
claimed.set(start)
|
claimed.set(start)
|
||||||
return [start, partials.getOrDefault(start,0)]
|
return [start, partials.getOrDefault(start,0), 0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized int[] claim(Set<Integer> available) {
|
synchronized int[] claim(Set<Integer> available) {
|
||||||
for (int i = claimed.nextSetBit(0); i >= 0; i = claimed.nextSetBit(i+1))
|
for (int i = done.nextSetBit(0); i >= 0; i = done.nextSetBit(i+1))
|
||||||
available.remove(i)
|
available.remove(i)
|
||||||
if (available.isEmpty())
|
if (available.isEmpty())
|
||||||
return -1
|
return null
|
||||||
List<Integer> toList = available.toList()
|
Set<Integer> availableCopy = new HashSet<>(available)
|
||||||
Collections.shuffle(toList)
|
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]
|
int rv = toList[0]
|
||||||
claimed.set(rv)
|
claimed.set(rv)
|
||||||
[rv, partials.getOrDefault(rv, 0)]
|
[rv, partials.getOrDefault(rv, 0), 0]
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized def getDownloaded() {
|
synchronized def getDownloaded() {
|
||||||
|
@@ -10,4 +10,5 @@ class UIDownloadEvent extends Event {
|
|||||||
UIResultEvent[] result
|
UIResultEvent[] result
|
||||||
Set<Destination> sources
|
Set<Destination> sources
|
||||||
File target
|
File target
|
||||||
|
boolean sequential
|
||||||
}
|
}
|
||||||
|
@@ -13,6 +13,7 @@ import java.nio.file.WatchService
|
|||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
import groovy.util.logging.Log
|
import groovy.util.logging.Log
|
||||||
@@ -31,6 +32,8 @@ class DirectoryWatcher {
|
|||||||
kinds = [ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE]
|
kinds = [ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final File home
|
||||||
|
private final MuWireSettings muOptions
|
||||||
private final EventBus eventBus
|
private final EventBus eventBus
|
||||||
private final FileManager fileManager
|
private final FileManager fileManager
|
||||||
private final Thread watcherThread, publisherThread
|
private final Thread watcherThread, publisherThread
|
||||||
@@ -39,7 +42,9 @@ class DirectoryWatcher {
|
|||||||
private WatchService watchService
|
private WatchService watchService
|
||||||
private volatile boolean shutdown
|
private volatile boolean shutdown
|
||||||
|
|
||||||
DirectoryWatcher(EventBus eventBus, FileManager fileManager) {
|
DirectoryWatcher(EventBus eventBus, FileManager fileManager, File home, MuWireSettings muOptions) {
|
||||||
|
this.home = home
|
||||||
|
this.muOptions = muOptions
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.fileManager = fileManager
|
this.fileManager = fileManager
|
||||||
this.watcherThread = new Thread({watch() } as Runnable, "directory-watcher")
|
this.watcherThread = new Thread({watch() } as Runnable, "directory-watcher")
|
||||||
@@ -64,15 +69,28 @@ class DirectoryWatcher {
|
|||||||
void onFileSharedEvent(FileSharedEvent e) {
|
void onFileSharedEvent(FileSharedEvent e) {
|
||||||
if (!e.file.isDirectory())
|
if (!e.file.isDirectory())
|
||||||
return
|
return
|
||||||
Path path = e.file.getCanonicalFile().toPath()
|
File canonical = e.file.getCanonicalFile()
|
||||||
|
Path path = canonical.toPath()
|
||||||
WatchKey wk = path.register(watchService, kinds)
|
WatchKey wk = path.register(watchService, kinds)
|
||||||
watchedDirectories.put(e.file, wk)
|
watchedDirectories.put(canonical, wk)
|
||||||
|
|
||||||
|
if (muOptions.watchedDirectories.add(canonical.toString()))
|
||||||
|
saveMuSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
void onDirectoryUnsharedEvent(DirectoryUnsharedEvent e) {
|
void onDirectoryUnsharedEvent(DirectoryUnsharedEvent e) {
|
||||||
WatchKey wk = watchedDirectories.remove(e.directory)
|
WatchKey wk = watchedDirectories.remove(e.directory)
|
||||||
wk?.cancel()
|
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() {
|
private void watch() {
|
||||||
|
@@ -8,8 +8,10 @@ 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 {
|
||||||
@@ -20,6 +22,7 @@ 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) {
|
||||||
@@ -62,6 +65,18 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,9 +102,45 @@ 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)
|
||||||
@@ -112,10 +163,15 @@ 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 { files.addAll nameToFiles.getOrDefault(it, []) }
|
names.each {
|
||||||
|
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)
|
||||||
|
|
||||||
|
@@ -4,6 +4,7 @@ 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 {
|
||||||
@@ -11,12 +12,15 @@ 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) {
|
HasherService(FileHasher hasher, EventBus eventBus, FileManager fileManager, MuWireSettings settings) {
|
||||||
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() {
|
||||||
@@ -24,13 +28,24 @@ class HasherService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onFileSharedEvent(FileSharedEvent evt) {
|
void onFileSharedEvent(FileSharedEvent evt) {
|
||||||
if (fileManager.fileToSharedFile.containsKey(evt.file.getCanonicalFile()))
|
File canonical = evt.file.getCanonicalFile()
|
||||||
|
if (!settings.shareHiddenFiles && canonical.isHidden())
|
||||||
return
|
return
|
||||||
executor.execute( { -> process(evt.file) } as Runnable)
|
if (fileManager.fileToSharedFile.containsKey(canonical))
|
||||||
|
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 {eventBus.publish new FileSharedEvent(file: it) }
|
||||||
} else {
|
} else {
|
||||||
|
@@ -3,6 +3,9 @@ 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
|
||||||
|
|
||||||
@@ -28,13 +31,16 @@ 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", true)
|
timer = new Timer("file persister timer", true)
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
@@ -45,8 +51,15 @@ class PersisterService extends Service {
|
|||||||
timer.schedule({load()} as TimerTask, 1)
|
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 {
|
||||||
@@ -56,6 +69,9 @@ 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,44 +125,44 @@ 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() {
|
||||||
def sharedFiles = fileManager.getSharedFiles()
|
persisterExecutor.submit( {
|
||||||
|
def sharedFiles = fileManager.getSharedFiles()
|
||||||
|
|
||||||
File tmp = File.createTempFile("muwire-files", "tmp")
|
File tmp = File.createTempFile("muwire-files", "tmp")
|
||||||
tmp.deleteOnExit()
|
tmp.deleteOnExit()
|
||||||
tmp.withPrintWriter { writer ->
|
tmp.withPrintWriter { writer ->
|
||||||
sharedFiles.each { k, v ->
|
sharedFiles.each { k, v ->
|
||||||
def json = toJson(k,v)
|
def json = toJson(k,v)
|
||||||
json = JsonOutput.toJson(json)
|
json = JsonOutput.toJson(json)
|
||||||
writer.println json
|
writer.println json
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
Files.copy(tmp.toPath(), location.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
||||||
Files.copy(tmp.toPath(), location.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
tmp.delete()
|
||||||
tmp.delete()
|
} as Runnable)
|
||||||
}
|
}
|
||||||
|
|
||||||
private def toJson(File f, SharedFile sf) {
|
private def toJson(File f, SharedFile sf) {
|
||||||
def json = [:]
|
def json = [:]
|
||||||
json.file = Base64.encode DataUtil.encodei18nString(f.getCanonicalFile().toString())
|
json.file = sf.getB64EncodedFileName()
|
||||||
json.length = f.length()
|
json.length = sf.getCachedLength()
|
||||||
InfoHash ih = sf.getInfoHash()
|
InfoHash ih = sf.getInfoHash()
|
||||||
json.infoHash = Base64.encode ih.getRoot()
|
json.infoHash = sf.getB64EncodedHashRoot()
|
||||||
json.pieceSize = sf.getPieceSize()
|
json.pieceSize = sf.getPieceSize()
|
||||||
byte [] tmp = new byte [32]
|
json.hashList = sf.getB64EncodedHashList()
|
||||||
json.hashList = []
|
json.comment = sf.getComment()
|
||||||
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())
|
||||||
|
@@ -0,0 +1,9 @@
|
|||||||
|
package com.muwire.core.files
|
||||||
|
|
||||||
|
import com.muwire.core.Event
|
||||||
|
import com.muwire.core.SharedFile
|
||||||
|
|
||||||
|
class UICommentEvent extends Event {
|
||||||
|
SharedFile sharedFile
|
||||||
|
String oldComment
|
||||||
|
}
|
@@ -0,0 +1,6 @@
|
|||||||
|
package com.muwire.core.files
|
||||||
|
|
||||||
|
import com.muwire.core.Event
|
||||||
|
|
||||||
|
class UIPersistFilesEvent extends Event {
|
||||||
|
}
|
@@ -6,8 +6,12 @@ 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"),
|
||||||
new Destination("JC63wJNOqSJmymkj4~UJWywBTvDGikKMoYP0HX2Wz9c5l3otXSkwnxWAFL4cKr~Ygh3BNNi2t93vuLIiI1W8AsE42kR~PwRx~Y-WvIHXR6KUejRmOp-n8WidtjKg9k4aDy428uSOedqXDxys5mpoeQXwDsv1CoPTTwnmb1GWFy~oTGIsCguCl~aJWGnqiKarPO3GJQ~ev-NbvAQzUfC3HeP1e6pdI5CGGjExahTCID5UjpJw8GaDXWlGmYWWH303Xu4x-vAHQy1dJLsOBCn8dZravsn5BKJk~j0POUon45CCx-~NYtaPe0Itt9cMdD2ciC76Rep1D0X0sm1SjlSs8sZ52KmF3oaLZ6OzgI9QLMIyBUrfi41sK5I0qTuUVBAkvW1xr~L-20dYJ9TrbOaOb2-vDIfKaxVi6xQOuhgQDiSBhd3qv2m0xGu-BM9DQYfNA0FdMjnZmqjmji9RMavzQSsVFIbQGLbrLepiEFlb7TseCK5UtRp8TxnG7L4gbYevBQAEAAcAAA==")
|
// 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,21 +7,35 @@ class Host {
|
|||||||
private static final int MAX_FAILURES = 3
|
private static final int MAX_FAILURES = 3
|
||||||
|
|
||||||
final Destination destination
|
final Destination destination
|
||||||
private final int clearInterval
|
private final int clearInterval, hopelessInterval, rejectionInterval
|
||||||
int failures,successes
|
int failures,successes
|
||||||
long lastAttempt
|
long lastAttempt
|
||||||
|
long lastSuccessfulAttempt
|
||||||
|
long lastRejection
|
||||||
|
|
||||||
public Host(Destination destination, int clearInterval) {
|
public Host(Destination destination, int clearInterval, int hopelessInterval, int rejectionInterval) {
|
||||||
this.destination = destination
|
this.destination = destination
|
||||||
this.clearInterval = clearInterval
|
this.clearInterval = clearInterval
|
||||||
|
this.hopelessInterval = hopelessInterval
|
||||||
|
this.rejectionInterval = rejectionInterval
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void onConnect() {
|
private void connectSuccessful() {
|
||||||
failures = 0
|
failures = 0
|
||||||
successes++
|
successes++
|
||||||
lastAttempt = System.currentTimeMillis()
|
lastAttempt = System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
synchronized void onConnect() {
|
||||||
|
connectSuccessful()
|
||||||
|
lastSuccessfulAttempt = lastAttempt
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized void onReject() {
|
||||||
|
connectSuccessful()
|
||||||
|
lastRejection = lastAttempt;
|
||||||
|
}
|
||||||
|
|
||||||
synchronized void onFailure() {
|
synchronized void onFailure() {
|
||||||
failures++
|
failures++
|
||||||
successes = 0
|
successes = 0
|
||||||
@@ -40,7 +54,17 @@ class Host {
|
|||||||
failures = 0
|
failures = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void canTryAgain() {
|
synchronized boolean canTryAgain() {
|
||||||
System.currentTimeMillis() - lastAttempt > (clearInterval * 60 * 1000)
|
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)
|
Host host = new Host(e.destination, settings.hostClearInterval, settings.hostHopelessInterval, settings.hostRejectInterval)
|
||||||
if (allowHost(host)) {
|
if (allowHost(host)) {
|
||||||
hosts.put(e.destination, host)
|
hosts.put(e.destination, host)
|
||||||
}
|
}
|
||||||
@@ -64,15 +64,17 @@ 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)
|
host = new Host(dest, settings.hostClearInterval, settings.hostHopelessInterval, settings.hostRejectInterval)
|
||||||
hosts.put(dest, host)
|
hosts.put(dest, host)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(e.status) {
|
switch(e.status) {
|
||||||
case ConnectionAttemptStatus.SUCCESSFUL:
|
case ConnectionAttemptStatus.SUCCESSFUL:
|
||||||
case ConnectionAttemptStatus.REJECTED:
|
|
||||||
host.onConnect()
|
host.onConnect()
|
||||||
break
|
break
|
||||||
|
case ConnectionAttemptStatus.REJECTED:
|
||||||
|
host.onReject()
|
||||||
|
break
|
||||||
case ConnectionAttemptStatus.FAILED:
|
case ConnectionAttemptStatus.FAILED:
|
||||||
host.onFailure()
|
host.onFailure()
|
||||||
break
|
break
|
||||||
@@ -82,6 +84,10 @@ 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,11 +112,15 @@ class HostCache extends Service {
|
|||||||
storage.eachLine {
|
storage.eachLine {
|
||||||
def entry = slurper.parseText(it)
|
def entry = slurper.parseText(it)
|
||||||
Destination dest = new Destination(entry.destination)
|
Destination dest = new Destination(entry.destination)
|
||||||
Host host = new Host(dest, settings.hostClearInterval)
|
Host host = new Host(dest, settings.hostClearInterval, settings.hostHopelessInterval, settings.hostRejectInterval)
|
||||||
host.failures = Integer.valueOf(String.valueOf(entry.failures))
|
host.failures = Integer.valueOf(String.valueOf(entry.failures))
|
||||||
host.successes = Integer.valueOf(String.valueOf(entry.successes))
|
host.successes = Integer.valueOf(String.valueOf(entry.successes))
|
||||||
if (entry.lastAttempt != null)
|
if (entry.lastAttempt != null)
|
||||||
host.lastAttempt = entry.lastAttempt
|
host.lastAttempt = entry.lastAttempt
|
||||||
|
if (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)
|
||||||
}
|
}
|
||||||
@@ -120,8 +130,6 @@ class HostCache extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean allowHost(Host host) {
|
private boolean allowHost(Host host) {
|
||||||
if (host.isFailed() && !host.canTryAgain())
|
|
||||||
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)
|
||||||
@@ -140,12 +148,14 @@ 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)) {
|
if (allowHost(host) && !host.isHopeless()) {
|
||||||
def map = [:]
|
def map = [:]
|
||||||
map.destination = dest.toBase64()
|
map.destination = dest.toBase64()
|
||||||
map.failures = host.failures
|
map.failures = host.failures
|
||||||
map.successes = host.successes
|
map.successes = host.successes
|
||||||
map.lastAttempt = host.lastAttempt
|
map.lastAttempt = host.lastAttempt
|
||||||
|
map.lastSuccessfulAttempt = host.lastSuccessfulAttempt
|
||||||
|
map.lastRejection = host.lastRejection
|
||||||
def json = JsonOutput.toJson(map)
|
def json = JsonOutput.toJson(map)
|
||||||
writer.println json
|
writer.println json
|
||||||
}
|
}
|
||||||
|
@@ -33,11 +33,12 @@ class MeshManager {
|
|||||||
meshes.get(infoHash)
|
meshes.get(infoHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
Mesh getOrCreate(InfoHash infoHash, int nPieces) {
|
Mesh getOrCreate(InfoHash infoHash, int nPieces, boolean sequential) {
|
||||||
synchronized(meshes) {
|
synchronized(meshes) {
|
||||||
if (meshes.containsKey(infoHash))
|
if (meshes.containsKey(infoHash))
|
||||||
return meshes.get(infoHash)
|
return meshes.get(infoHash)
|
||||||
Pieces pieces = new Pieces(nPieces, settings.downloadSequentialRatio)
|
float ratio = sequential ? 0f : settings.downloadSequentialRatio
|
||||||
|
Pieces pieces = new Pieces(nPieces, ratio)
|
||||||
if (fileManager.rootToFiles.containsKey(infoHash)) {
|
if (fileManager.rootToFiles.containsKey(infoHash)) {
|
||||||
for (int i = 0; i < nPieces; i++)
|
for (int i = 0; i < nPieces; i++)
|
||||||
pieces.markDownloaded(i)
|
pieces.markDownloaded(i)
|
||||||
|
@@ -0,0 +1,86 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
@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")
|
||||||
|
|
||||||
|
// 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))
|
||||||
|
|
||||||
|
JsonSlurper slurper = new JsonSlurper()
|
||||||
|
DataInputStream dis = new DataInputStream(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)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,5 @@
|
|||||||
|
package com.muwire.core.search;
|
||||||
|
|
||||||
|
public enum BrowseStatus {
|
||||||
|
CONNECTING, FETCHING, FINISHED, FAILED
|
||||||
|
}
|
@@ -0,0 +1,7 @@
|
|||||||
|
package com.muwire.core.search
|
||||||
|
|
||||||
|
import com.muwire.core.Event
|
||||||
|
|
||||||
|
class BrowseStatusEvent extends Event {
|
||||||
|
BrowseStatus status
|
||||||
|
}
|
@@ -91,12 +91,22 @@ class ResultsParser {
|
|||||||
if (json.sources != null)
|
if (json.sources != null)
|
||||||
sources = json.sources.stream().map({new Destination(it)}).collect(Collectors.toSet())
|
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 = true
|
||||||
|
|
||||||
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,
|
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,6 +4,7 @@ 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
|
||||||
@@ -17,6 +18,7 @@ import java.util.stream.Collectors
|
|||||||
import com.muwire.core.DownloadedFile
|
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
|
||||||
@@ -42,11 +44,13 @@ 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) {
|
ResultsSender(EventBus eventBus, I2PConnector connector, Persona me, MuWireSettings settings) {
|
||||||
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) {
|
void sendResults(UUID uuid, SharedFile[] results, Destination target, boolean oobInfohash) {
|
||||||
@@ -60,13 +64,18 @@ class ResultsSender {
|
|||||||
Set<Destination> suggested = Collections.emptySet()
|
Set<Destination> suggested = Collections.emptySet()
|
||||||
if (it instanceof DownloadedFile)
|
if (it instanceof DownloadedFile)
|
||||||
suggested = it.sources
|
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
|
sources : suggested,
|
||||||
|
comment : comment
|
||||||
)
|
)
|
||||||
eventBus.publish(uiResultEvent)
|
eventBus.publish(uiResultEvent)
|
||||||
}
|
}
|
||||||
@@ -85,7 +94,6 @@ class ResultsSender {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
byte [] tmp = new byte[InfoHash.SIZE]
|
|
||||||
JsonOutput jsonOutput = new JsonOutput()
|
JsonOutput jsonOutput = new JsonOutput()
|
||||||
Endpoint endpoint = null;
|
Endpoint endpoint = null;
|
||||||
try {
|
try {
|
||||||
@@ -95,33 +103,7 @@ class ResultsSender {
|
|||||||
me.write(os)
|
me.write(os)
|
||||||
os.writeShort((short)results.length)
|
os.writeShort((short)results.length)
|
||||||
results.each {
|
results.each {
|
||||||
byte [] name = it.getFile().getName().getBytes(StandardCharsets.UTF_8)
|
def obj = sharedFileToObj(it, settings.browseFiles)
|
||||||
def baos = new ByteArrayOutputStream()
|
|
||||||
def daos = new DataOutputStream(baos)
|
|
||||||
daos.writeShort((short) name.length)
|
|
||||||
daos.write(name)
|
|
||||||
daos.flush()
|
|
||||||
String encodedName = Base64.encode(baos.toByteArray())
|
|
||||||
def obj = [:]
|
|
||||||
obj.type = "Result"
|
|
||||||
obj.version = oobInfohash ? 2 : 1
|
|
||||||
obj.name = encodedName
|
|
||||||
obj.infohash = Base64.encode(it.getInfoHash().getRoot())
|
|
||||||
obj.size = it.getFile().length()
|
|
||||||
obj.pieceSize = it.getPieceSize()
|
|
||||||
if (!oobInfohash) {
|
|
||||||
byte [] hashList = it.getInfoHash().getHashList()
|
|
||||||
def hashListB64 = []
|
|
||||||
for (int i = 0; i < hashList.length / InfoHash.SIZE; i++) {
|
|
||||||
System.arraycopy(hashList, InfoHash.SIZE * i, tmp, 0, InfoHash.SIZE)
|
|
||||||
hashListB64 << Base64.encode(tmp)
|
|
||||||
}
|
|
||||||
obj.hashList = hashListB64
|
|
||||||
}
|
|
||||||
|
|
||||||
if (it instanceof DownloadedFile)
|
|
||||||
obj.sources = it.sources.stream().map({dest -> dest.toBase64()}).collect(Collectors.toSet())
|
|
||||||
|
|
||||||
def json = jsonOutput.toJson(obj)
|
def json = jsonOutput.toJson(obj)
|
||||||
os.writeShort((short)json.length())
|
os.writeShort((short)json.length())
|
||||||
os.write(json.getBytes(StandardCharsets.US_ASCII))
|
os.write(json.getBytes(StandardCharsets.US_ASCII))
|
||||||
@@ -135,4 +117,30 @@ class ResultsSender {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static def sharedFileToObj(SharedFile sf, boolean browseFiles) {
|
||||||
|
byte [] name = sf.getFile().getName().getBytes(StandardCharsets.UTF_8)
|
||||||
|
def baos = new ByteArrayOutputStream()
|
||||||
|
def daos = new DataOutputStream(baos)
|
||||||
|
daos.writeShort((short) name.length)
|
||||||
|
daos.write(name)
|
||||||
|
daos.flush()
|
||||||
|
String encodedName = Base64.encode(baos.toByteArray())
|
||||||
|
def obj = [:]
|
||||||
|
obj.type = "Result"
|
||||||
|
obj.version = 2
|
||||||
|
obj.name = encodedName
|
||||||
|
obj.infohash = Base64.encode(sf.getInfoHash().getRoot())
|
||||||
|
obj.size = sf.getCachedLength()
|
||||||
|
obj.pieceSize = sf.getPieceSize()
|
||||||
|
|
||||||
|
if (sf instanceof DownloadedFile)
|
||||||
|
obj.sources = sf.sources.stream().map({dest -> dest.toBase64()}).collect(Collectors.toSet())
|
||||||
|
|
||||||
|
if (sf.getComment() != null)
|
||||||
|
obj.comment = sf.getComment()
|
||||||
|
|
||||||
|
obj.browse = browseFiles
|
||||||
|
obj
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,11 +9,12 @@ class SearchEvent extends Event {
|
|||||||
byte [] searchHash
|
byte [] searchHash
|
||||||
UUID uuid
|
UUID uuid
|
||||||
boolean oobInfohash
|
boolean oobInfohash
|
||||||
|
boolean searchComments
|
||||||
|
|
||||||
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"
|
"searchTerms: $searchTerms searchHash:$infoHash, uuid:$uuid oobInfohash:$oobInfohash searchComments:$searchComments"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
package com.muwire.core.search
|
package com.muwire.core.search
|
||||||
|
|
||||||
import com.muwire.core.Constants
|
import com.muwire.core.SplitPattern
|
||||||
|
|
||||||
class SearchIndex {
|
class SearchIndex {
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ class SearchIndex {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static String[] split(String source) {
|
private static String[] split(String source) {
|
||||||
source = source.replaceAll(Constants.SPLIT_PATTERN, " ").toLowerCase()
|
source = source.replaceAll(SplitPattern.SPLIT_PATTERN, " ").toLowerCase()
|
||||||
String [] split = source.split(" ")
|
String [] split = source.split(" ")
|
||||||
def rv = []
|
def rv = []
|
||||||
split.each { if (it.length() > 0) rv << it }
|
split.each { if (it.length() > 0) rv << it }
|
||||||
|
@@ -0,0 +1,8 @@
|
|||||||
|
package com.muwire.core.search
|
||||||
|
|
||||||
|
import com.muwire.core.Event
|
||||||
|
import com.muwire.core.Persona
|
||||||
|
|
||||||
|
class UIBrowseEvent extends Event {
|
||||||
|
Persona host
|
||||||
|
}
|
@@ -14,6 +14,8 @@ class UIResultEvent extends Event {
|
|||||||
long size
|
long size
|
||||||
InfoHash infohash
|
InfoHash infohash
|
||||||
int pieceSize
|
int pieceSize
|
||||||
|
String comment
|
||||||
|
boolean browse
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
|
@@ -3,5 +3,5 @@ package com.muwire.core.update
|
|||||||
import net.i2p.data.Destination
|
import net.i2p.data.Destination
|
||||||
|
|
||||||
class UpdateServers {
|
class UpdateServers {
|
||||||
static final Destination UPDATE_SERVER = new Destination("pSWieSRB3czCl3Zz4WpKp4Z8tjv-05zbogRDS7SEnKcSdWOupVwjzQ92GsgQh1VqgoSRk1F8dpZOnHxxz5HFy9D7ri0uFdkMyXdSKoB7IgkkvCfTAyEmeaPwSYnurF3Zk7u286E7YG2rZkQZgJ77tow7ZS0mxFB7Z0Ti-VkZ9~GeGePW~howwNm4iSQACZA0DyTpI8iv5j4I0itPCQRgaGziob~Vfvjk49nd8N4jtaDGo9cEcafikVzQ2OgBgYWL6LRbrrItwuGqsDvITUHWaElUYIDhRQYUq8gYiUA6rwAJputfhFU0J7lIxFR9vVY7YzRvcFckfr0DNI4VQVVlPnRPkUxQa--BlldMaCIppWugjgKLwqiSiHywKpSMlBWgY2z1ry4ueEBo1WEP-mEf88wRk4cFQBCKtctCQnIG2GsnATqTl-VGUAsuzeNWZiFSwXiTy~gQ094yWx-K06fFZUDt4CMiLZVhGlixiInD~34FCRC9LVMtFcqiFB2M-Ql2AAAA")
|
static final Destination UPDATE_SERVER = new Destination("VJYAiCPZHNLraWvLkeRLxRiT4PHAqNqRO1nH240r7u1noBw8Pa~-lJOhKR7CccPkEN8ejSi4H6XjqKYLC8BKLVLeOgnAbedUVx81MV7DETPDdPEGV4RVu6YDFri7-tJOeqauGHxtlXT44YWuR69xKrTG3u4~iTWgxKnlBDht9Q3aVpSPFD2KqEizfVxolqXI0zmAZ2xMi8jfl0oe4GbgHrD9hR2FYj6yKfdqcUgHVobY4kDdJt-u31QqwWdsQMEj8Y3tR2XcNaITEVPiAjoKgBrYwB4jddWPNaT4XdHz76d9p9Iqes7dhOKq3OKpk6kg-bfIKiEOiA1mY49fn5h8pNShTqV7QBhh4CE4EDT3Szl~WsLdrlHUKJufSi7erEMh3coF7HORpF1wah2Xw7q470t~b8dKGKi7N7xQsqhGruDm66PH9oE9Kt9WBVBq2zORdPRtRM61I7EnrwDlbOkL0y~XpvQ3JKUQKdBQ3QsOJt8CHlhHHXMMbvqhntR61RSDBQAEAAcAAA==")
|
||||||
}
|
}
|
||||||
|
@@ -83,7 +83,7 @@ class ContentUploader extends Uploader {
|
|||||||
String xHave = DataUtil.encodeXHave(mesh.pieces.getDownloaded(), mesh.pieces.nPieces)
|
String xHave = DataUtil.encodeXHave(mesh.pieces.getDownloaded(), mesh.pieces.nPieces)
|
||||||
endpoint.getOutputStream().write("X-Have: $xHave\r\n".getBytes(StandardCharsets.US_ASCII))
|
endpoint.getOutputStream().write("X-Have: $xHave\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
|
|
||||||
Set<Persona> sources = mesh.getRandom(3, toExclude)
|
Set<Persona> sources = mesh.getRandom(9, toExclude)
|
||||||
if (!sources.isEmpty()) {
|
if (!sources.isEmpty()) {
|
||||||
String xAlts = sources.stream().map({ it.toBase64() }).collect(Collectors.joining(","))
|
String xAlts = sources.stream().map({ it.toBase64() }).collect(Collectors.joining(","))
|
||||||
endpoint.getOutputStream().write("X-Alt: $xAlts\r\n".getBytes(StandardCharsets.US_ASCII))
|
endpoint.getOutputStream().write("X-Alt: $xAlts\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
|
@@ -92,7 +92,7 @@ public class UploadManager {
|
|||||||
pieceSize = downloader.pieceSizePow2
|
pieceSize = downloader.pieceSizePow2
|
||||||
} else {
|
} else {
|
||||||
SharedFile sharedFile = sharedFiles.iterator().next();
|
SharedFile sharedFile = sharedFiles.iterator().next();
|
||||||
mesh = meshManager.getOrCreate(request.infoHash, sharedFile.NPieces)
|
mesh = meshManager.getOrCreate(request.infoHash, sharedFile.NPieces, false)
|
||||||
file = sharedFile.file
|
file = sharedFile.file
|
||||||
pieceSize = sharedFile.pieceSize
|
pieceSize = sharedFile.pieceSize
|
||||||
}
|
}
|
||||||
@@ -217,7 +217,7 @@ public class UploadManager {
|
|||||||
pieceSize = downloader.pieceSizePow2
|
pieceSize = downloader.pieceSizePow2
|
||||||
} else {
|
} else {
|
||||||
SharedFile sharedFile = sharedFiles.iterator().next();
|
SharedFile sharedFile = sharedFiles.iterator().next();
|
||||||
mesh = meshManager.getOrCreate(request.infoHash, sharedFile.NPieces)
|
mesh = meshManager.getOrCreate(request.infoHash, sharedFile.NPieces, false)
|
||||||
file = sharedFile.file
|
file = sharedFile.file
|
||||||
pieceSize = sharedFile.pieceSize
|
pieceSize = sharedFile.pieceSize
|
||||||
}
|
}
|
||||||
|
11
core/src/main/java/com/muwire/core/Constants.java
Normal file
11
core/src/main/java/com/muwire/core/Constants.java
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
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;
|
||||||
|
}
|
@@ -2,6 +2,12 @@ package com.muwire.core;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
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 {
|
||||||
|
|
||||||
@@ -12,12 +18,28 @@ public class SharedFile {
|
|||||||
private final String cachedPath;
|
private final String cachedPath;
|
||||||
private final long cachedLength;
|
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 {
|
public SharedFile(File file, InfoHash infoHash, int pieceSize) throws IOException {
|
||||||
this.file = file;
|
this.file = file;
|
||||||
this.infoHash = infoHash;
|
this.infoHash = infoHash;
|
||||||
this.pieceSize = pieceSize;
|
this.pieceSize = pieceSize;
|
||||||
this.cachedPath = file.getAbsolutePath();
|
this.cachedPath = file.getAbsolutePath();
|
||||||
this.cachedLength = file.length();
|
this.cachedLength = file.length();
|
||||||
|
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() {
|
||||||
@@ -41,6 +63,18 @@ public class SharedFile {
|
|||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getB64EncodedFileName() {
|
||||||
|
return b64EncodedFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getB64EncodedHashRoot() {
|
||||||
|
return b64EncodedHashRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getB64EncodedHashList() {
|
||||||
|
return b64EncodedHashList;
|
||||||
|
}
|
||||||
|
|
||||||
public String getCachedPath() {
|
public String getCachedPath() {
|
||||||
return cachedPath;
|
return cachedPath;
|
||||||
}
|
}
|
||||||
@@ -49,6 +83,14 @@ public class SharedFile {
|
|||||||
return cachedLength;
|
return cachedLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setComment(String comment) {
|
||||||
|
this.comment = comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getComment() {
|
||||||
|
return comment;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return file.hashCode() ^ infoHash.hashCode();
|
return file.hashCode() ^ infoHash.hashCode();
|
||||||
|
@@ -1,122 +1,134 @@
|
|||||||
package com.muwire.core.util
|
package com.muwire.core.util;
|
||||||
|
|
||||||
import java.lang.reflect.Field
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.lang.reflect.Method
|
import java.io.DataOutputStream;
|
||||||
import java.nio.ByteBuffer
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets
|
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 com.muwire.core.Constants;
|
||||||
|
|
||||||
import net.i2p.data.Base64
|
import net.i2p.data.Base64;
|
||||||
|
|
||||||
class DataUtil {
|
public class DataUtil {
|
||||||
|
|
||||||
private final static int MAX_SHORT = (0x1 << 16) - 1
|
private final static int MAX_SHORT = (0x1 << 16) - 1;
|
||||||
|
|
||||||
static void writeUnsignedShort(int value, OutputStream os) {
|
static void writeUnsignedShort(int value, OutputStream os) throws IOException {
|
||||||
if (value > MAX_SHORT || value < 0)
|
if (value > MAX_SHORT || value < 0)
|
||||||
throw new IllegalArgumentException("$value invalid")
|
throw new IllegalArgumentException("$value invalid");
|
||||||
|
|
||||||
byte lsb = (byte) (value & 0xFF)
|
byte lsb = (byte) (value & 0xFF);
|
||||||
byte msb = (byte) (value >> 8)
|
byte msb = (byte) (value >> 8);
|
||||||
|
|
||||||
os.write(msb)
|
os.write(msb);
|
||||||
os.write(lsb)
|
os.write(lsb);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final static int MAX_HEADER = 0x7FFFFF
|
private final static int MAX_HEADER = 0x7FFFFF;
|
||||||
|
|
||||||
static void packHeader(int length, byte [] header) {
|
static void packHeader(int length, byte [] header) {
|
||||||
if (header.length != 3)
|
if (header.length != 3)
|
||||||
throw new IllegalArgumentException("header length $header.length")
|
throw new IllegalArgumentException("header length $header.length");
|
||||||
if (length < 0 || length > MAX_HEADER)
|
if (length < 0 || length > MAX_HEADER)
|
||||||
throw new IllegalArgumentException("length $length")
|
throw new IllegalArgumentException("length $length");
|
||||||
|
|
||||||
header[2] = (byte) (length & 0xFF)
|
header[2] = (byte) (length & 0xFF);
|
||||||
header[1] = (byte) ((length >> 8) & 0xFF)
|
header[1] = (byte) ((length >> 8) & 0xFF);
|
||||||
header[0] = (byte) ((length >> 16) & 0x7F)
|
header[0] = (byte) ((length >> 16) & 0x7F);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int readLength(byte [] header) {
|
static int readLength(byte [] header) {
|
||||||
if (header.length != 3)
|
if (header.length != 3)
|
||||||
throw new IllegalArgumentException("header length $header.length")
|
throw new IllegalArgumentException("header length $header.length");
|
||||||
|
|
||||||
return (((int)(header[0] & 0x7F)) << 16) |
|
return (((int)(header[0] & 0x7F)) << 16) |
|
||||||
(((int)(header[1] & 0xFF) << 8)) |
|
(((int)(header[1] & 0xFF) << 8)) |
|
||||||
((int)header[2] & 0xFF)
|
((int)header[2] & 0xFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
static String readi18nString(byte [] encoded) {
|
static String readi18nString(byte [] encoded) {
|
||||||
if (encoded.length < 2)
|
if (encoded.length < 2)
|
||||||
throw new IllegalArgumentException("encoding too short $encoded.length")
|
throw new IllegalArgumentException("encoding too short $encoded.length");
|
||||||
int length = ((encoded[0] & 0xFF) << 8) | (encoded[1] & 0xFF)
|
int length = ((encoded[0] & 0xFF) << 8) | (encoded[1] & 0xFF);
|
||||||
if (encoded.length != length + 2)
|
if (encoded.length != length + 2)
|
||||||
throw new IllegalArgumentException("encoding doesn't match length, expected $length found $encoded.length")
|
throw new IllegalArgumentException("encoding doesn't match length, expected $length found $encoded.length");
|
||||||
byte [] string = new byte[length]
|
byte [] string = new byte[length];
|
||||||
System.arraycopy(encoded, 2, string, 0, length)
|
System.arraycopy(encoded, 2, string, 0, length);
|
||||||
new String(string, StandardCharsets.UTF_8)
|
return new String(string, StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
static byte[] encodei18nString(String string) {
|
public static byte[] encodei18nString(String string) {
|
||||||
byte [] utf8 = string.getBytes(StandardCharsets.UTF_8)
|
byte [] utf8 = string.getBytes(StandardCharsets.UTF_8);
|
||||||
if (utf8.length > Short.MAX_VALUE)
|
if (utf8.length > Short.MAX_VALUE)
|
||||||
throw new IllegalArgumentException("String in utf8 too long $utf8.length")
|
throw new IllegalArgumentException("String in utf8 too long $utf8.length");
|
||||||
def baos = new ByteArrayOutputStream()
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
def daos = new DataOutputStream(baos)
|
DataOutputStream daos = new DataOutputStream(baos);
|
||||||
daos.writeShort((short) utf8.length)
|
try {
|
||||||
daos.write(utf8)
|
daos.writeShort((short) utf8.length);
|
||||||
daos.close()
|
daos.write(utf8);
|
||||||
baos.toByteArray()
|
daos.close();
|
||||||
|
} catch (IOException impossible) {
|
||||||
|
throw new IllegalStateException(impossible);
|
||||||
|
}
|
||||||
|
return baos.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String readTillRN(InputStream is) {
|
public static String readTillRN(InputStream is) throws IOException {
|
||||||
def baos = new ByteArrayOutputStream()
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
while(baos.size() < (Constants.MAX_HEADER_SIZE)) {
|
while(baos.size() < (Constants.MAX_HEADER_SIZE)) {
|
||||||
byte read = is.read()
|
int read = is.read();
|
||||||
if (read == -1)
|
if (read == -1)
|
||||||
throw new IOException()
|
throw new IOException();
|
||||||
if (read == '\r') {
|
if (read == '\r') {
|
||||||
if (is.read() != '\n')
|
if (is.read() != '\n')
|
||||||
throw new IOException("invalid header")
|
throw new IOException("invalid header");
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
baos.write(read)
|
baos.write(read);
|
||||||
}
|
}
|
||||||
new String(baos.toByteArray(), StandardCharsets.US_ASCII)
|
return new String(baos.toByteArray(), StandardCharsets.US_ASCII);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String encodeXHave(List<Integer> pieces, int totalPieces) {
|
public static String encodeXHave(List<Integer> pieces, int totalPieces) {
|
||||||
int bytes = totalPieces / 8
|
int bytes = totalPieces / 8;
|
||||||
if (totalPieces % 8 != 0)
|
if (totalPieces % 8 != 0)
|
||||||
bytes++
|
bytes++;
|
||||||
byte[] raw = new byte[bytes]
|
byte[] raw = new byte[bytes];
|
||||||
pieces.each {
|
for (int it : pieces) {
|
||||||
int byteIdx = it / 8
|
int byteIdx = it / 8;
|
||||||
int offset = it % 8
|
int offset = it % 8;
|
||||||
int mask = 0x80 >>> offset
|
int mask = 0x80 >>> offset;
|
||||||
raw[byteIdx] |= mask
|
raw[byteIdx] |= mask;
|
||||||
}
|
}
|
||||||
Base64.encode(raw)
|
return Base64.encode(raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Integer> decodeXHave(String xHave) {
|
public static List<Integer> decodeXHave(String xHave) {
|
||||||
byte [] availablePieces = Base64.decode(xHave)
|
byte [] availablePieces = Base64.decode(xHave);
|
||||||
List<Integer> available = new ArrayList<>()
|
List<Integer> available = new ArrayList<>();
|
||||||
availablePieces.eachWithIndex {b, i ->
|
for (int i = 0; i < availablePieces.length; i ++) {
|
||||||
|
byte b = availablePieces[i];
|
||||||
for (int j = 0; j < 8 ; j++) {
|
for (int j = 0; j < 8 ; j++) {
|
||||||
byte mask = 0x80 >>> j
|
byte mask = (byte) (0x80 >>> j);
|
||||||
if ((b & mask) == mask) {
|
if ((b & mask) == mask) {
|
||||||
available.add(i * 8 + j)
|
available.add(i * 8 + j);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
available
|
return available;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Exception findRoot(Exception e) {
|
public static Throwable findRoot(Throwable e) {
|
||||||
while(e.getCause() != null)
|
while(e.getCause() != null)
|
||||||
e = e.getCause()
|
e = e.getCause();
|
||||||
e
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void tryUnmap(ByteBuffer cb) {
|
public static void tryUnmap(ByteBuffer cb) {
|
@@ -4,6 +4,7 @@ import static org.junit.Assert.fail
|
|||||||
|
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
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.EventBus
|
||||||
@@ -180,10 +181,11 @@ class DownloadSessionTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@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(100)
|
downloadThread.join(150)
|
||||||
assert 100 >= (System.currentTimeMillis() - now)
|
assert 100 >= (System.currentTimeMillis() - now)
|
||||||
assert !performed
|
assert !performed
|
||||||
assert available.isEmpty()
|
assert available.isEmpty()
|
||||||
|
@@ -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
|
assert pieces.claim() == [0,0,0]
|
||||||
pieces.markDownloaded(0)
|
pieces.markDownloaded(0)
|
||||||
assert pieces.isComplete()
|
assert pieces.isComplete()
|
||||||
}
|
}
|
||||||
@@ -25,28 +25,28 @@ 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.claim()
|
||||||
assert piece == 0 || piece == 1
|
assert piece[0] == 0 || piece[0] == 1
|
||||||
pieces.markDownloaded(piece)
|
pieces.markDownloaded(piece[0])
|
||||||
assert !pieces.isComplete()
|
assert !pieces.isComplete()
|
||||||
int piece2 = pieces.claim()
|
int[] piece2 = pieces.claim()
|
||||||
assert piece != piece2
|
assert piece[0] != piece2[0]
|
||||||
pieces.markDownloaded(piece2)
|
pieces.markDownloaded(piece2[0])
|
||||||
assert pieces.isComplete()
|
assert pieces.isComplete()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testClaimAvailable() {
|
public void testClaimAvailable() {
|
||||||
pieces = new Pieces(2)
|
pieces = new Pieces(2)
|
||||||
int claimed = pieces.claim([0].toSet())
|
int[] claimed = pieces.claim([0].toSet())
|
||||||
assert claimed == 0
|
assert claimed == [0,0,0]
|
||||||
assert -1 == pieces.claim([0].toSet())
|
assert [0,0,1] == pieces.claim([0].toSet())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testClaimNoneAvailable() {
|
public void testClaimNoneAvailable() {
|
||||||
pieces = new Pieces(20)
|
pieces = new Pieces(20)
|
||||||
int claimed = pieces.claim()
|
int[] claimed = pieces.claim()
|
||||||
assert -1 == pieces.claim([claimed].toSet())
|
assert [0,0,0] == pieces.claim(claimed.toSet())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -25,7 +25,8 @@ class HasherServiceTest {
|
|||||||
void before() {
|
void before() {
|
||||||
eventBus = new EventBus()
|
eventBus = new EventBus()
|
||||||
hasher = new FileHasher()
|
hasher = new FileHasher()
|
||||||
service = new HasherService(hasher, eventBus, new FileManager(eventBus, new MuWireSettings()))
|
def props = 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)
|
eventBus.register(FileSharedEvent.class, service)
|
||||||
service.start()
|
service.start()
|
||||||
|
@@ -72,6 +72,9 @@ 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()
|
||||||
|
|
||||||
@@ -91,6 +94,10 @@ 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))
|
||||||
@@ -104,6 +111,9 @@ 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()
|
||||||
|
|
||||||
@@ -123,6 +133,9 @@ 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))
|
||||||
@@ -139,6 +152,14 @@ 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))
|
||||||
@@ -158,6 +179,10 @@ 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))
|
||||||
|
|
||||||
@@ -183,6 +208,10 @@ 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))
|
||||||
|
|
||||||
@@ -214,6 +243,10 @@ 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))
|
||||||
@@ -229,6 +262,11 @@ 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)
|
||||||
@@ -260,6 +298,10 @@ 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
|
||||||
|
@@ -9,6 +9,9 @@ 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 {
|
||||||
|
|
||||||
@@ -52,7 +55,13 @@ class UploaderTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void startUpload() {
|
private void startUpload() {
|
||||||
uploader = new ContentUploader(file, request, endpoint)
|
def hasher = new FileHasher()
|
||||||
|
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()
|
||||||
@@ -81,6 +90,7 @@ class UploaderTest {
|
|||||||
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]
|
||||||
@@ -96,6 +106,7 @@ class UploaderTest {
|
|||||||
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]
|
||||||
@@ -111,6 +122,7 @@ class UploaderTest {
|
|||||||
request = new ContentRequest(range : new Range(0,20))
|
request = new ContentRequest(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()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,6 +135,7 @@ class UploaderTest {
|
|||||||
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)
|
||||||
|
@@ -1,8 +1,20 @@
|
|||||||
group = com.muwire
|
group = com.muwire
|
||||||
version = 0.4.7
|
version = 0.5.0
|
||||||
groovyVersion = 2.4.15
|
groovyVersion = 2.4.15
|
||||||
slf4jVersion = 1.7.25
|
slf4jVersion = 1.7.25
|
||||||
spockVersion = 1.1-groovy-2.4
|
spockVersion = 1.1-groovy-2.4
|
||||||
|
grailsVersion=4.0.0
|
||||||
|
gorm.version=7.0.2.RELEASE
|
||||||
|
|
||||||
sourceCompatibility=1.8
|
sourceCompatibility=1.8
|
||||||
targetCompatibility=1.8
|
targetCompatibility=1.8
|
||||||
|
|
||||||
|
# plugin properties
|
||||||
|
author = zab@mail.i2p
|
||||||
|
signer = zab@mail.i2p
|
||||||
|
i2pVersion=0.9.41
|
||||||
|
keystorePassword=changeit
|
||||||
|
websiteURL=http://muwire.i2p
|
||||||
|
updateURLsu3=http://muwire.i2p/MuWire.su3
|
||||||
|
|
||||||
|
pack200=true
|
||||||
|
34
gradlew
vendored
34
gradlew
vendored
@@ -11,21 +11,21 @@
|
|||||||
PRG="$0"
|
PRG="$0"
|
||||||
# Need this for relative symlinks.
|
# Need this for relative symlinks.
|
||||||
while [ -h "$PRG" ] ; do
|
while [ -h "$PRG" ] ; do
|
||||||
ls=`ls -ld "$PRG"`
|
ls=$(ls -ld "$PRG")
|
||||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
link=$(expr "$ls" : '.*-> \(.*\)$')
|
||||||
if expr "$link" : '/.*' > /dev/null; then
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
PRG="$link"
|
PRG="$link"
|
||||||
else
|
else
|
||||||
PRG=`dirname "$PRG"`"/$link"
|
PRG=$(dirname "$PRG")"/$link"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
SAVED="`pwd`"
|
SAVED="$(pwd)"
|
||||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
cd "$(dirname "$PRG")/" >/dev/null
|
||||||
APP_HOME="`pwd -P`"
|
APP_HOME="$(pwd -P)"
|
||||||
cd "$SAVED" >/dev/null
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
APP_NAME="Gradle"
|
APP_NAME="Gradle"
|
||||||
APP_BASE_NAME=`basename "$0"`
|
APP_BASE_NAME=$(basename "$0")
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
DEFAULT_JVM_OPTS=""
|
DEFAULT_JVM_OPTS=""
|
||||||
@@ -49,7 +49,7 @@ cygwin=false
|
|||||||
msys=false
|
msys=false
|
||||||
darwin=false
|
darwin=false
|
||||||
nonstop=false
|
nonstop=false
|
||||||
case "`uname`" in
|
case "$(uname)" in
|
||||||
CYGWIN* )
|
CYGWIN* )
|
||||||
cygwin=true
|
cygwin=true
|
||||||
;;
|
;;
|
||||||
@@ -90,7 +90,7 @@ fi
|
|||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
MAX_FD_LIMIT=`ulimit -H -n`
|
MAX_FD_LIMIT=$(ulimit -H -n)
|
||||||
if [ $? -eq 0 ] ; then
|
if [ $? -eq 0 ] ; then
|
||||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
MAX_FD="$MAX_FD_LIMIT"
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
@@ -111,12 +111,12 @@ fi
|
|||||||
|
|
||||||
# For Cygwin, switch paths to Windows format before running java
|
# For Cygwin, switch paths to Windows format before running java
|
||||||
if $cygwin ; then
|
if $cygwin ; then
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
APP_HOME=$(cygpath --path --mixed "$APP_HOME")
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
CLASSPATH=$(cygpath --path --mixed "$CLASSPATH")
|
||||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
JAVACMD=$(cygpath --unix "$JAVACMD")
|
||||||
|
|
||||||
# We build the pattern for arguments to be converted via cygpath
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
ROOTDIRSRAW=$(find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null)
|
||||||
SEP=""
|
SEP=""
|
||||||
for dir in $ROOTDIRSRAW ; do
|
for dir in $ROOTDIRSRAW ; do
|
||||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
@@ -130,13 +130,13 @@ if $cygwin ; then
|
|||||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
i=0
|
i=0
|
||||||
for arg in "$@" ; do
|
for arg in "$@" ; do
|
||||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
CHECK=$(echo "$arg"|egrep -c "$OURCYGPATTERN" -)
|
||||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
CHECK2=$(echo "$arg"|egrep -c "^-") ### Determine if an option
|
||||||
|
|
||||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
eval $(echo args$i)=$(cygpath --path --ignore --mixed "$arg")
|
||||||
else
|
else
|
||||||
eval `echo args$i`="\"$arg\""
|
eval $(echo args$i)="\"$arg\""
|
||||||
fi
|
fi
|
||||||
i=$((i+1))
|
i=$((i+1))
|
||||||
done
|
done
|
||||||
|
@@ -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'
|
||||||
@@ -119,6 +119,7 @@ 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")
|
||||||
@@ -138,4 +139,5 @@ task jacocoRootReport(dependsOn: jacocoRootMerge, type: JacocoReport) {
|
|||||||
xml.destination = file("${buildDir}/reports/jacoco/root/root.xml")
|
xml.destination = file("${buildDir}/reports/jacoco/root/root.xml")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
@@ -41,4 +41,24 @@ mvcGroups {
|
|||||||
view = 'com.muwire.gui.TrustListView'
|
view = 'com.muwire.gui.TrustListView'
|
||||||
controller = 'com.muwire.gui.TrustListController'
|
controller = 'com.muwire.gui.TrustListController'
|
||||||
}
|
}
|
||||||
|
'content-panel' {
|
||||||
|
model = 'com.muwire.gui.ContentPanelModel'
|
||||||
|
view = 'com.muwire.gui.ContentPanelView'
|
||||||
|
controller = 'com.muwire.gui.ContentPanelController'
|
||||||
|
}
|
||||||
|
'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'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,45 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,95 @@
|
|||||||
|
package com.muwire.gui
|
||||||
|
|
||||||
|
import griffon.core.artifact.GriffonController
|
||||||
|
import griffon.core.controller.ControllerAction
|
||||||
|
import griffon.inject.MVCMember
|
||||||
|
import griffon.metadata.ArtifactProviderFor
|
||||||
|
import net.i2p.data.Base64
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull
|
||||||
|
|
||||||
|
import com.muwire.core.EventBus
|
||||||
|
import com.muwire.core.download.UIDownloadEvent
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onUIResultEvent(UIResultEvent e) {
|
||||||
|
runInsideUIAsync {
|
||||||
|
model.results << e
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,105 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -13,10 +13,11 @@ import javax.annotation.Nonnull
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.swing.JTable
|
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.Persona
|
||||||
import com.muwire.core.SharedFile
|
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
|
||||||
@@ -24,6 +25,7 @@ import com.muwire.core.download.UIDownloadPausedEvent
|
|||||||
import com.muwire.core.download.UIDownloadResumedEvent
|
import com.muwire.core.download.UIDownloadResumedEvent
|
||||||
import com.muwire.core.files.DirectoryUnsharedEvent
|
import com.muwire.core.files.DirectoryUnsharedEvent
|
||||||
import com.muwire.core.files.FileUnsharedEvent
|
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.RemoteTrustList
|
||||||
@@ -59,6 +61,7 @@ class MainFrameController {
|
|||||||
Map<String, Object> params = new HashMap<>()
|
Map<String, Object> params = new HashMap<>()
|
||||||
params["search-terms"] = search
|
params["search-terms"] = search
|
||||||
params["uuid"] = uuid.toString()
|
params["uuid"] = uuid.toString()
|
||||||
|
params["core"] = core
|
||||||
def group = mvcGroup.createMVCGroup("SearchTab", uuid.toString(), params)
|
def group = mvcGroup.createMVCGroup("SearchTab", uuid.toString(), params)
|
||||||
model.results[uuid.toString()] = group
|
model.results[uuid.toString()] = group
|
||||||
|
|
||||||
@@ -78,13 +81,15 @@ class MainFrameController {
|
|||||||
searchEvent = new SearchEvent(searchHash : root, uuid : uuid, oobInfohash: true)
|
searchEvent = new SearchEvent(searchHash : root, uuid : uuid, oobInfohash: true)
|
||||||
} else {
|
} else {
|
||||||
// this can be improved a lot
|
// this can be improved a lot
|
||||||
def replaced = search.toLowerCase().trim().replaceAll(Constants.SPLIT_PATTERN, " ")
|
def replaced = search.toLowerCase().trim().replaceAll(SplitPattern.SPLIT_PATTERN, " ")
|
||||||
def terms = replaced.split(" ")
|
def terms = replaced.split(" ")
|
||||||
def nonEmpty = []
|
def nonEmpty = []
|
||||||
terms.each { if (it.length() > 0) nonEmpty << it }
|
terms.each { if (it.length() > 0) nonEmpty << it }
|
||||||
searchEvent = new SearchEvent(searchTerms : nonEmpty, uuid : uuid, oobInfohash: true)
|
searchEvent = new SearchEvent(searchTerms : nonEmpty, uuid : uuid, oobInfohash: true,
|
||||||
|
searchComments : core.muOptions.searchComments)
|
||||||
}
|
}
|
||||||
core.eventBus.publish(new QueryEvent(searchEvent : searchEvent, firstHop : true,
|
boolean firstHop = core.muOptions.allowUntrusted || core.muOptions.searchExtraHop
|
||||||
|
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))
|
||||||
}
|
}
|
||||||
@@ -96,6 +101,7 @@ class MainFrameController {
|
|||||||
Map<String, Object> params = new HashMap<>()
|
Map<String, Object> params = new HashMap<>()
|
||||||
params["search-terms"] = tabTitle
|
params["search-terms"] = tabTitle
|
||||||
params["uuid"] = uuid.toString()
|
params["uuid"] = uuid.toString()
|
||||||
|
params["core"] = core
|
||||||
def group = mvcGroup.createMVCGroup("SearchTab", uuid.toString(), params)
|
def group = mvcGroup.createMVCGroup("SearchTab", uuid.toString(), params)
|
||||||
model.results[uuid.toString()] = group
|
model.results[uuid.toString()] = group
|
||||||
|
|
||||||
@@ -106,20 +112,6 @@ class MainFrameController {
|
|||||||
originator : core.me))
|
originator : core.me))
|
||||||
}
|
}
|
||||||
|
|
||||||
private def selectedResult() {
|
|
||||||
def selected = builder.getVariable("result-tabs").getSelectedComponent()
|
|
||||||
def group = selected.getClientProperty("mvc-group")
|
|
||||||
def table = selected.getClientProperty("results-table")
|
|
||||||
int row = table.getSelectedRow()
|
|
||||||
if (row == -1)
|
|
||||||
return
|
|
||||||
def sortEvt = group.view.lastSortEvent
|
|
||||||
if (sortEvt != null) {
|
|
||||||
row = group.view.resultsTable.rowSorter.convertRowIndexToModel(row)
|
|
||||||
}
|
|
||||||
group.model.results[row]
|
|
||||||
}
|
|
||||||
|
|
||||||
private int selectedDownload() {
|
private int selectedDownload() {
|
||||||
def downloadsTable = builder.getVariable("downloads-table")
|
def downloadsTable = builder.getVariable("downloads-table")
|
||||||
def selected = downloadsTable.getSelectedRow()
|
def selected = downloadsTable.getSelectedRow()
|
||||||
@@ -130,39 +122,21 @@ class MainFrameController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ControllerAction
|
@ControllerAction
|
||||||
void download() {
|
void trustPersonaFromSearch() {
|
||||||
def result = selectedResult()
|
int selected = builder.getVariable("searches-table").getSelectedRow()
|
||||||
if (result == null)
|
if (selected < 0)
|
||||||
return
|
return
|
||||||
|
Persona p = model.searches[selected].originator
|
||||||
if (!model.canDownload(result.infohash))
|
core.eventBus.publish( new TrustEvent(persona : p, level : TrustLevel.TRUSTED) )
|
||||||
return
|
|
||||||
|
|
||||||
def file = new File(application.context.get("muwire-settings").downloadLocation, result.name)
|
|
||||||
|
|
||||||
def selected = builder.getVariable("result-tabs").getSelectedComponent()
|
|
||||||
def group = selected.getClientProperty("mvc-group")
|
|
||||||
|
|
||||||
def resultsBucket = group.model.hashBucket[result.infohash]
|
|
||||||
def sources = group.model.sourcesBucket[result.infohash]
|
|
||||||
|
|
||||||
core.eventBus.publish(new UIDownloadEvent(result : resultsBucket, sources: sources, target : file))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ControllerAction
|
@ControllerAction
|
||||||
void trust() {
|
void distrustPersonaFromSearch() {
|
||||||
def result = selectedResult()
|
int selected = builder.getVariable("searches-table").getSelectedRow()
|
||||||
if (result == null)
|
if (selected < 0)
|
||||||
return // TODO disable button
|
return
|
||||||
core.eventBus.publish( new TrustEvent(persona : result.sender, level : TrustLevel.TRUSTED))
|
Persona p = model.searches[selected].originator
|
||||||
}
|
core.eventBus.publish( new TrustEvent(persona : p, level : TrustLevel.DISTRUSTED) )
|
||||||
|
|
||||||
@ControllerAction
|
|
||||||
void distrust() {
|
|
||||||
def result = selectedResult()
|
|
||||||
if (result == null)
|
|
||||||
return // TODO disable button
|
|
||||||
core.eventBus.publish( new TrustEvent(persona : result.sender, level : TrustLevel.DISTRUSTED))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ControllerAction
|
@ControllerAction
|
||||||
@@ -187,6 +161,23 @@ class MainFrameController {
|
|||||||
core.eventBus.publish(new UIDownloadPausedEvent())
|
core.eventBus.publish(new UIDownloadPausedEvent())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ControllerAction
|
||||||
|
void clear() {
|
||||||
|
def toRemove = []
|
||||||
|
model.downloads.each {
|
||||||
|
if (it.downloader.getCurrentState() == Downloader.DownloadState.CANCELLED) {
|
||||||
|
toRemove << it
|
||||||
|
} else if (it.downloader.getCurrentState() == Downloader.DownloadState.FINISHED) {
|
||||||
|
toRemove << it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
toRemove.each {
|
||||||
|
model.downloads.remove(it)
|
||||||
|
}
|
||||||
|
model.clearButtonEnabled = false
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private void markTrust(String tableName, TrustLevel level, def list) {
|
private void markTrust(String tableName, TrustLevel level, def list) {
|
||||||
int row = view.getSelectedTrustTablesRow(tableName)
|
int row = view.getSelectedTrustTablesRow(tableName)
|
||||||
if (row < 0)
|
if (row < 0)
|
||||||
@@ -281,22 +272,25 @@ class MainFrameController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void unshareSelectedFile() {
|
void unshareSelectedFile() {
|
||||||
SharedFile sf = view.selectedSharedFile()
|
def sf = view.selectedSharedFiles()
|
||||||
if (sf == null)
|
if (sf == null)
|
||||||
return
|
return
|
||||||
core.eventBus.publish(new FileUnsharedEvent(unsharedFile : sf))
|
sf.each {
|
||||||
|
core.eventBus.publish(new FileUnsharedEvent(unsharedFile : it))
|
||||||
|
}
|
||||||
|
core.eventBus.publish(new UIPersistFilesEvent())
|
||||||
}
|
}
|
||||||
|
|
||||||
void stopWatchingDirectory() {
|
@ControllerAction
|
||||||
String directory = mvcGroup.view.getSelectedWatchedDirectory()
|
void addComment() {
|
||||||
if (directory == null)
|
def selectedFiles = view.selectedSharedFiles()
|
||||||
|
if (selectedFiles == null || selectedFiles.isEmpty())
|
||||||
return
|
return
|
||||||
core.muOptions.watchedDirectories.remove(directory)
|
|
||||||
saveMuWireSettings()
|
|
||||||
core.eventBus.publish(new DirectoryUnsharedEvent(directory : new File(directory)))
|
|
||||||
|
|
||||||
model.watched.remove(directory)
|
Map<String, Object> params = new HashMap<>()
|
||||||
builder.getVariable("watched-directories-table").model.fireTableDataChanged()
|
params['selectedFiles'] = selectedFiles
|
||||||
|
params['core'] = core
|
||||||
|
mvcGroup.createMVCGroup("add-comment", "Add Comment", params)
|
||||||
}
|
}
|
||||||
|
|
||||||
void saveMuWireSettings() {
|
void saveMuWireSettings() {
|
||||||
|
@@ -70,6 +70,10 @@ class OptionsController {
|
|||||||
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()
|
boolean autoDownloadUpdate = view.autoDownloadUpdateCheckbox.model.isSelected()
|
||||||
model.autoDownloadUpdate = autoDownloadUpdate
|
model.autoDownloadUpdate = autoDownloadUpdate
|
||||||
settings.autoDownloadUpdate = autoDownloadUpdate
|
settings.autoDownloadUpdate = autoDownloadUpdate
|
||||||
@@ -79,6 +83,14 @@ class OptionsController {
|
|||||||
model.shareDownloadedFiles = shareDownloaded
|
model.shareDownloadedFiles = shareDownloaded
|
||||||
settings.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
|
||||||
|
|
||||||
String downloadLocation = model.downloadLocation
|
String downloadLocation = model.downloadLocation
|
||||||
settings.downloadLocation = new File(downloadLocation)
|
settings.downloadLocation = new File(downloadLocation)
|
||||||
|
|
||||||
@@ -96,6 +108,10 @@ class OptionsController {
|
|||||||
model.onlyTrusted = onlyTrusted
|
model.onlyTrusted = onlyTrusted
|
||||||
settings.setAllowUntrusted(!onlyTrusted)
|
settings.setAllowUntrusted(!onlyTrusted)
|
||||||
|
|
||||||
|
boolean searchExtraHop = view.searchExtraHopCheckbox.model.isSelected()
|
||||||
|
model.searchExtraHop = searchExtraHop
|
||||||
|
settings.searchExtraHop = searchExtraHop
|
||||||
|
|
||||||
boolean trustLists = view.allowTrustListsCheckbox.model.isSelected()
|
boolean trustLists = view.allowTrustListsCheckbox.model.isSelected()
|
||||||
model.trustLists = trustLists
|
model.trustLists = trustLists
|
||||||
settings.allowTrustLists = trustLists
|
settings.allowTrustLists = trustLists
|
||||||
@@ -120,9 +136,8 @@ class OptionsController {
|
|||||||
model.font = text
|
model.font = text
|
||||||
uiSettings.font = text
|
uiSettings.font = text
|
||||||
|
|
||||||
// boolean showMonitor = view.monitorCheckbox.model.isSelected()
|
uiSettings.autoFontSize = model.automaticFontSize
|
||||||
// model.showMonitor = showMonitor
|
uiSettings.fontSize = Integer.parseInt(view.fontSizeField.text)
|
||||||
// uiSettings.showMonitor = showMonitor
|
|
||||||
|
|
||||||
boolean clearCancelledDownloads = view.clearCancelledDownloadsCheckbox.model.isSelected()
|
boolean clearCancelledDownloads = view.clearCancelledDownloadsCheckbox.model.isSelected()
|
||||||
model.clearCancelledDownloads = clearCancelledDownloads
|
model.clearCancelledDownloads = clearCancelledDownloads
|
||||||
@@ -136,10 +151,6 @@ 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)
|
||||||
@@ -164,4 +175,15 @@ class OptionsController {
|
|||||||
if (rv == JFileChooser.APPROVE_OPTION)
|
if (rv == JFileChooser.APPROVE_OPTION)
|
||||||
model.downloadLocation = chooser.getSelectedFile().getAbsolutePath()
|
model.downloadLocation = chooser.getSelectedFile().getAbsolutePath()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ControllerAction
|
||||||
|
void automaticFontAction() {
|
||||||
|
model.automaticFontSize = true
|
||||||
|
model.customFontSize = 12
|
||||||
|
}
|
||||||
|
|
||||||
|
@ControllerAction
|
||||||
|
void customFontAction() {
|
||||||
|
model.automaticFontSize = false
|
||||||
|
}
|
||||||
}
|
}
|
@@ -6,6 +6,99 @@ import griffon.inject.MVCMember
|
|||||||
import griffon.metadata.ArtifactProviderFor
|
import griffon.metadata.ArtifactProviderFor
|
||||||
import javax.annotation.Nonnull
|
import javax.annotation.Nonnull
|
||||||
|
|
||||||
|
import com.muwire.core.Core
|
||||||
|
import com.muwire.core.Persona
|
||||||
|
import com.muwire.core.download.UIDownloadEvent
|
||||||
|
import com.muwire.core.search.UIResultEvent
|
||||||
|
import com.muwire.core.trust.TrustEvent
|
||||||
|
import com.muwire.core.trust.TrustLevel
|
||||||
|
|
||||||
@ArtifactProviderFor(GriffonController)
|
@ArtifactProviderFor(GriffonController)
|
||||||
class SearchTabController {
|
class SearchTabController {
|
||||||
}
|
|
||||||
|
@MVCMember @Nonnull
|
||||||
|
SearchTabModel model
|
||||||
|
@MVCMember @Nonnull
|
||||||
|
SearchTabView view
|
||||||
|
|
||||||
|
Core core
|
||||||
|
|
||||||
|
private def selectedResults() {
|
||||||
|
int[] rows = view.resultsTable.getSelectedRows()
|
||||||
|
if (rows.length == 0)
|
||||||
|
return null
|
||||||
|
def sortEvt = view.lastSortEvent
|
||||||
|
if (sortEvt != null) {
|
||||||
|
for (int i = 0; i < rows.length; i++) {
|
||||||
|
rows[i] = view.resultsTable.rowSorter.convertRowIndexToModel(rows[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<UIResultEvent> results = new ArrayList<>()
|
||||||
|
rows.each { results.add(model.results[it]) }
|
||||||
|
results
|
||||||
|
}
|
||||||
|
|
||||||
|
@ControllerAction
|
||||||
|
void download() {
|
||||||
|
def results = selectedResults()
|
||||||
|
if (results == null)
|
||||||
|
return
|
||||||
|
|
||||||
|
results.removeAll {
|
||||||
|
!mvcGroup.parentGroup.model.canDownload(it.infohash)
|
||||||
|
}
|
||||||
|
|
||||||
|
results.each { result ->
|
||||||
|
def file = new File(application.context.get("muwire-settings").downloadLocation, result.name)
|
||||||
|
|
||||||
|
def resultsBucket = model.hashBucket[result.infohash]
|
||||||
|
def sources = model.sourcesBucket[result.infohash]
|
||||||
|
|
||||||
|
core.eventBus.publish(new UIDownloadEvent(result : resultsBucket, sources: sources,
|
||||||
|
target : file, sequential : view.sequentialDownloadCheckbox.model.isSelected()))
|
||||||
|
}
|
||||||
|
mvcGroup.parentGroup.view.showDownloadsWindow.call()
|
||||||
|
}
|
||||||
|
|
||||||
|
@ControllerAction
|
||||||
|
void trust() {
|
||||||
|
int row = view.selectedSenderRow()
|
||||||
|
if (row < 0)
|
||||||
|
return
|
||||||
|
def sender = model.senders[row]
|
||||||
|
core.eventBus.publish( new TrustEvent(persona : sender, level : TrustLevel.TRUSTED))
|
||||||
|
}
|
||||||
|
|
||||||
|
@ControllerAction
|
||||||
|
void distrust() {
|
||||||
|
int row = view.selectedSenderRow()
|
||||||
|
if (row < 0)
|
||||||
|
return
|
||||||
|
def sender = model.senders[row]
|
||||||
|
core.eventBus.publish( new TrustEvent(persona : sender, level : TrustLevel.DISTRUSTED))
|
||||||
|
}
|
||||||
|
|
||||||
|
@ControllerAction
|
||||||
|
void neutral() {
|
||||||
|
int row = view.selectedSenderRow()
|
||||||
|
if (row < 0)
|
||||||
|
return
|
||||||
|
def sender = model.senders[row]
|
||||||
|
core.eventBus.publish( new TrustEvent(persona : sender, level : TrustLevel.NEUTRAL))
|
||||||
|
}
|
||||||
|
|
||||||
|
@ControllerAction
|
||||||
|
void browse() {
|
||||||
|
int selectedSender = view.selectedSenderRow()
|
||||||
|
if (selectedSender < 0)
|
||||||
|
return
|
||||||
|
Persona sender = model.senders[selectedSender]
|
||||||
|
|
||||||
|
String groupId = sender.getHumanReadableName()
|
||||||
|
Map<String,Object> params = new HashMap<>()
|
||||||
|
params['host'] = sender
|
||||||
|
params['eventBus'] = core.eventBus
|
||||||
|
|
||||||
|
mvcGroup.createMVCGroup("browse", groupId, params)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,19 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
@ArtifactProviderFor(GriffonController)
|
||||||
|
class ShowCommentController {
|
||||||
|
@MVCMember @Nonnull
|
||||||
|
ShowCommentView view
|
||||||
|
|
||||||
|
@ControllerAction
|
||||||
|
void dismiss() {
|
||||||
|
view.dialog.setVisible(false)
|
||||||
|
mvcGroup.destroy()
|
||||||
|
}
|
||||||
|
}
|
@@ -10,15 +10,19 @@ import com.muwire.gui.UISettings
|
|||||||
|
|
||||||
import javax.annotation.Nonnull
|
import javax.annotation.Nonnull
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.swing.JLabel
|
||||||
import javax.swing.JTable
|
import javax.swing.JTable
|
||||||
import javax.swing.LookAndFeel
|
import javax.swing.LookAndFeel
|
||||||
import javax.swing.UIManager
|
import javax.swing.UIManager
|
||||||
|
import javax.swing.plaf.FontUIResource
|
||||||
|
|
||||||
import static griffon.util.GriffonApplicationUtils.isMacOSX
|
import static griffon.util.GriffonApplicationUtils.isMacOSX
|
||||||
import static groovy.swing.SwingBuilder.lookAndFeel
|
import static groovy.swing.SwingBuilder.lookAndFeel
|
||||||
|
|
||||||
import java.awt.Font
|
import java.awt.Font
|
||||||
|
import java.awt.Toolkit
|
||||||
import java.util.logging.Level
|
import java.util.logging.Level
|
||||||
|
import java.util.logging.LogManager
|
||||||
|
|
||||||
@Log
|
@Log
|
||||||
class Initialize extends AbstractLifecycleHandler {
|
class Initialize extends AbstractLifecycleHandler {
|
||||||
@@ -29,6 +33,16 @@ class Initialize extends AbstractLifecycleHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
void execute() {
|
void execute() {
|
||||||
|
|
||||||
|
if (System.getProperty("java.util.logging.config.file") == null) {
|
||||||
|
log.info("No config file specified, so turning off logging")
|
||||||
|
def names = LogManager.getLogManager().getLoggerNames()
|
||||||
|
while(names.hasMoreElements()) {
|
||||||
|
def name = names.nextElement()
|
||||||
|
LogManager.getLogManager().getLogger(name).setLevel(Level.OFF)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log.info "Loading home dir"
|
log.info "Loading home dir"
|
||||||
def portableHome = System.getProperty("portable.home")
|
def portableHome = System.getProperty("portable.home")
|
||||||
def home = portableHome == null ?
|
def home = portableHome == null ?
|
||||||
@@ -43,7 +57,7 @@ class Initialize extends AbstractLifecycleHandler {
|
|||||||
|
|
||||||
application.context.put("muwire-home", home.getAbsolutePath())
|
application.context.put("muwire-home", home.getAbsolutePath())
|
||||||
|
|
||||||
System.getProperties().setProperty("awt.useSystemAAFontSettings", "true")
|
System.getProperties().setProperty("awt.useSystemAAFontSettings", "gasp")
|
||||||
|
|
||||||
def guiPropsFile = new File(home, "gui.properties")
|
def guiPropsFile = new File(home, "gui.properties")
|
||||||
UISettings uiSettings
|
UISettings uiSettings
|
||||||
@@ -52,25 +66,43 @@ class Initialize extends AbstractLifecycleHandler {
|
|||||||
guiPropsFile.withInputStream { props.load(it) }
|
guiPropsFile.withInputStream { props.load(it) }
|
||||||
uiSettings = new UISettings(props)
|
uiSettings = new UISettings(props)
|
||||||
|
|
||||||
|
def lnf
|
||||||
log.info("settting user-specified lnf $uiSettings.lnf")
|
log.info("settting user-specified lnf $uiSettings.lnf")
|
||||||
try {
|
try {
|
||||||
lookAndFeel(uiSettings.lnf)
|
lnf = lookAndFeel(uiSettings.lnf)
|
||||||
} catch (Throwable bad) {
|
} catch (Throwable bad) {
|
||||||
log.log(Level.WARNING,"couldn't set desired look and feeel, switching to defaults", bad)
|
log.log(Level.WARNING,"couldn't set desired look and feel, switching to defaults", bad)
|
||||||
uiSettings.lnf = lookAndFeel("system","gtk","metal").getID()
|
lnf = lookAndFeel("system","gtk","metal")
|
||||||
|
uiSettings.lnf = lnf.getID()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uiSettings.font != null) {
|
if (uiSettings.font != null || uiSettings.autoFontSize || uiSettings.fontSize > 0) {
|
||||||
log.info("setting user-specified font $uiSettings.font")
|
|
||||||
Font font = new Font(uiSettings.font, Font.PLAIN, 12)
|
FontUIResource defaultFont = lnf.getDefaults().getFont("Label.font")
|
||||||
def defaults = UIManager.getDefaults()
|
|
||||||
defaults.put("Button.font", font)
|
String fontName
|
||||||
defaults.put("RadioButton.font", font)
|
if (uiSettings.font != null)
|
||||||
defaults.put("Label.font", font)
|
fontName = uiSettings.font
|
||||||
defaults.put("CheckBox.font", font)
|
else
|
||||||
defaults.put("Table.font", font)
|
fontName = defaultFont.getName()
|
||||||
defaults.put("TableHeader.font", font)
|
|
||||||
// TODO: add others
|
int fontSize = defaultFont.getSize()
|
||||||
|
if (uiSettings.autoFontSize) {
|
||||||
|
int resolution = Toolkit.getDefaultToolkit().getScreenResolution()
|
||||||
|
fontSize = resolution / 9;
|
||||||
|
} else {
|
||||||
|
fontSize = uiSettings.fontSize
|
||||||
|
}
|
||||||
|
|
||||||
|
FontUIResource font = new FontUIResource(fontName, Font.PLAIN, fontSize)
|
||||||
|
|
||||||
|
def keys = lnf.getDefaults().keys()
|
||||||
|
while(keys.hasMoreElements()) {
|
||||||
|
def key = keys.nextElement()
|
||||||
|
def value = lnf.getDefaults().get(key)
|
||||||
|
if (value instanceof FontUIResource)
|
||||||
|
lnf.getDefaults().put(key, font)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Properties props = new Properties()
|
Properties props = new Properties()
|
||||||
@@ -86,6 +118,8 @@ class Initialize extends AbstractLifecycleHandler {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LookAndFeel chosen = lookAndFeel('system', 'gtk')
|
LookAndFeel chosen = lookAndFeel('system', 'gtk')
|
||||||
|
if (chosen == null)
|
||||||
|
chosen = lookAndFeel('metal')
|
||||||
uiSettings.lnf = chosen.getID()
|
uiSettings.lnf = chosen.getID()
|
||||||
log.info("ended up applying $chosen.name")
|
log.info("ended up applying $chosen.name")
|
||||||
}
|
}
|
||||||
|
12
gui/griffon-app/models/com/muwire/gui/AddCommentModel.groovy
Normal file
12
gui/griffon-app/models/com/muwire/gui/AddCommentModel.groovy
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package com.muwire.gui
|
||||||
|
|
||||||
|
import com.muwire.core.SharedFile
|
||||||
|
|
||||||
|
import griffon.core.artifact.GriffonModel
|
||||||
|
import griffon.transform.Observable
|
||||||
|
import griffon.metadata.ArtifactProviderFor
|
||||||
|
|
||||||
|
@ArtifactProviderFor(GriffonModel)
|
||||||
|
class AddCommentModel {
|
||||||
|
List<SharedFile> selectedFiles
|
||||||
|
}
|
19
gui/griffon-app/models/com/muwire/gui/BrowseModel.groovy
Normal file
19
gui/griffon-app/models/com/muwire/gui/BrowseModel.groovy
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package com.muwire.gui
|
||||||
|
|
||||||
|
import com.muwire.core.Persona
|
||||||
|
|
||||||
|
import griffon.core.artifact.GriffonModel
|
||||||
|
import griffon.transform.Observable
|
||||||
|
import griffon.metadata.ArtifactProviderFor
|
||||||
|
|
||||||
|
import com.muwire.core.search.BrowseStatus
|
||||||
|
|
||||||
|
@ArtifactProviderFor(GriffonModel)
|
||||||
|
class BrowseModel {
|
||||||
|
Persona host
|
||||||
|
@Observable BrowseStatus status
|
||||||
|
@Observable boolean downloadActionEnabled
|
||||||
|
@Observable boolean viewCommentActionEnabled
|
||||||
|
|
||||||
|
def results = []
|
||||||
|
}
|
@@ -0,0 +1,59 @@
|
|||||||
|
package com.muwire.gui
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull
|
||||||
|
|
||||||
|
import com.muwire.core.Core
|
||||||
|
import com.muwire.core.EventBus
|
||||||
|
import com.muwire.core.content.ContentControlEvent
|
||||||
|
import com.muwire.core.content.ContentManager
|
||||||
|
|
||||||
|
import griffon.core.artifact.GriffonModel
|
||||||
|
import griffon.inject.MVCMember
|
||||||
|
import griffon.transform.Observable
|
||||||
|
import griffon.metadata.ArtifactProviderFor
|
||||||
|
|
||||||
|
@ArtifactProviderFor(GriffonModel)
|
||||||
|
class ContentPanelModel {
|
||||||
|
|
||||||
|
@MVCMember @Nonnull
|
||||||
|
ContentPanelView view
|
||||||
|
|
||||||
|
Core core
|
||||||
|
|
||||||
|
private ContentManager contentManager
|
||||||
|
|
||||||
|
def rules = []
|
||||||
|
def hits = []
|
||||||
|
|
||||||
|
@Observable boolean regex
|
||||||
|
@Observable boolean deleteButtonEnabled
|
||||||
|
@Observable boolean trustButtonsEnabled
|
||||||
|
|
||||||
|
void mvcGroupInit(Map<String,String> args) {
|
||||||
|
contentManager = application.context.get("core").contentManager
|
||||||
|
rules.addAll(contentManager.matchers)
|
||||||
|
core.eventBus.register(ContentControlEvent.class, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
void mvcGroupDestroy() {
|
||||||
|
core.eventBus.unregister(ContentControlEvent.class, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
void refresh() {
|
||||||
|
int selectedRule = view.getSelectedRule()
|
||||||
|
rules.clear()
|
||||||
|
rules.addAll(contentManager.matchers)
|
||||||
|
hits.clear()
|
||||||
|
view.rulesTable.model.fireTableDataChanged()
|
||||||
|
view.hitsTable.model.fireTableDataChanged()
|
||||||
|
if (selectedRule >= 0) {
|
||||||
|
view.rulesTable.selectionModel.setSelectionInterval(selectedRule,selectedRule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onContentControlEvent(ContentControlEvent e) {
|
||||||
|
runInsideUIAsync {
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,6 +1,8 @@
|
|||||||
package com.muwire.gui
|
package com.muwire.gui
|
||||||
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.Paths
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@@ -8,18 +10,24 @@ import javax.annotation.Nonnull
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.swing.JOptionPane
|
import javax.swing.JOptionPane
|
||||||
import javax.swing.JTable
|
import javax.swing.JTable
|
||||||
|
import javax.swing.tree.DefaultMutableTreeNode
|
||||||
|
import javax.swing.tree.DefaultTreeModel
|
||||||
|
import javax.swing.tree.TreeNode
|
||||||
|
|
||||||
import com.muwire.core.Core
|
import com.muwire.core.Core
|
||||||
import com.muwire.core.InfoHash
|
import com.muwire.core.InfoHash
|
||||||
import com.muwire.core.MuWireSettings
|
import com.muwire.core.MuWireSettings
|
||||||
import com.muwire.core.Persona
|
import com.muwire.core.Persona
|
||||||
import com.muwire.core.RouterDisconnectedEvent
|
import com.muwire.core.RouterDisconnectedEvent
|
||||||
|
import com.muwire.core.SharedFile
|
||||||
import com.muwire.core.connection.ConnectionAttemptStatus
|
import com.muwire.core.connection.ConnectionAttemptStatus
|
||||||
import com.muwire.core.connection.ConnectionEvent
|
import com.muwire.core.connection.ConnectionEvent
|
||||||
import com.muwire.core.connection.DisconnectionEvent
|
import com.muwire.core.connection.DisconnectionEvent
|
||||||
|
import com.muwire.core.content.ContentControlEvent
|
||||||
import com.muwire.core.download.DownloadStartedEvent
|
import com.muwire.core.download.DownloadStartedEvent
|
||||||
import com.muwire.core.download.Downloader
|
import com.muwire.core.download.Downloader
|
||||||
import com.muwire.core.files.AllFilesLoadedEvent
|
import com.muwire.core.files.AllFilesLoadedEvent
|
||||||
|
import com.muwire.core.files.DirectoryUnsharedEvent
|
||||||
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.FileHashingEvent
|
||||||
@@ -56,6 +64,8 @@ class MainFrameModel {
|
|||||||
FactoryBuilderSupport builder
|
FactoryBuilderSupport builder
|
||||||
@MVCMember @Nonnull
|
@MVCMember @Nonnull
|
||||||
MainFrameController controller
|
MainFrameController controller
|
||||||
|
@MVCMember @Nonnull
|
||||||
|
MainFrameView view
|
||||||
@Inject @Nonnull GriffonApplication application
|
@Inject @Nonnull GriffonApplication application
|
||||||
@Observable boolean coreInitialized = false
|
@Observable boolean coreInitialized = false
|
||||||
@Observable boolean routerPresent
|
@Observable boolean routerPresent
|
||||||
@@ -63,8 +73,11 @@ class MainFrameModel {
|
|||||||
def results = new ConcurrentHashMap<>()
|
def results = new ConcurrentHashMap<>()
|
||||||
def downloads = []
|
def downloads = []
|
||||||
def uploads = []
|
def uploads = []
|
||||||
def shared = []
|
boolean treeVisible = true
|
||||||
def watched = []
|
def shared
|
||||||
|
def sharedTree
|
||||||
|
def treeRoot
|
||||||
|
final Map<SharedFile, TreeNode> fileToNode = new HashMap<>()
|
||||||
def connectionList = []
|
def connectionList = []
|
||||||
def searches = new LinkedList()
|
def searches = new LinkedList()
|
||||||
def trusted = []
|
def trusted = []
|
||||||
@@ -75,12 +88,12 @@ class MainFrameModel {
|
|||||||
@Observable String me
|
@Observable String me
|
||||||
@Observable int loadedFiles
|
@Observable int loadedFiles
|
||||||
@Observable File hashingFile
|
@Observable File hashingFile
|
||||||
@Observable boolean downloadActionEnabled
|
|
||||||
@Observable boolean trustButtonsEnabled
|
|
||||||
@Observable boolean cancelButtonEnabled
|
@Observable boolean cancelButtonEnabled
|
||||||
@Observable boolean retryButtonEnabled
|
@Observable boolean retryButtonEnabled
|
||||||
@Observable boolean pauseButtonEnabled
|
@Observable boolean pauseButtonEnabled
|
||||||
|
@Observable boolean clearButtonEnabled
|
||||||
@Observable String resumeButtonText
|
@Observable String resumeButtonText
|
||||||
|
@Observable boolean addCommentButtonEnabled
|
||||||
@Observable boolean subscribeButtonEnabled
|
@Observable boolean subscribeButtonEnabled
|
||||||
@Observable boolean markNeutralFromTrustedButtonEnabled
|
@Observable boolean markNeutralFromTrustedButtonEnabled
|
||||||
@Observable boolean markDistrustedButtonEnabled
|
@Observable boolean markDistrustedButtonEnabled
|
||||||
@@ -90,7 +103,13 @@ class MainFrameModel {
|
|||||||
@Observable boolean updateButtonEnabled
|
@Observable boolean updateButtonEnabled
|
||||||
@Observable boolean unsubscribeButtonEnabled
|
@Observable boolean unsubscribeButtonEnabled
|
||||||
|
|
||||||
private final Set<InfoHash> infoHashes = new HashSet<>()
|
@Observable boolean searchesPaneButtonEnabled
|
||||||
|
@Observable boolean downloadsPaneButtonEnabled
|
||||||
|
@Observable boolean uploadsPaneButtonEnabled
|
||||||
|
@Observable boolean monitorPaneButtonEnabled
|
||||||
|
@Observable boolean trustPaneButtonEnabled
|
||||||
|
|
||||||
|
@Observable Downloader downloader
|
||||||
|
|
||||||
private final Set<InfoHash> downloadInfoHashes = new HashSet<>()
|
private final Set<InfoHash> downloadInfoHashes = new HashSet<>()
|
||||||
|
|
||||||
@@ -103,7 +122,12 @@ class MainFrameModel {
|
|||||||
void updateTablePreservingSelection(String tableName) {
|
void updateTablePreservingSelection(String tableName) {
|
||||||
def downloadTable = builder.getVariable(tableName)
|
def downloadTable = builder.getVariable(tableName)
|
||||||
int selectedRow = downloadTable.getSelectedRow()
|
int selectedRow = downloadTable.getSelectedRow()
|
||||||
downloadTable.model.fireTableDataChanged()
|
while(true) {
|
||||||
|
try {
|
||||||
|
downloadTable.model.fireTableDataChanged()
|
||||||
|
break
|
||||||
|
} catch (IllegalArgumentException iae) {} // caused by underlying model changing while table is sorted
|
||||||
|
}
|
||||||
downloadTable.selectionModel.setSelectionInterval(selectedRow,selectedRow)
|
downloadTable.selectionModel.setSelectionInterval(selectedRow,selectedRow)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,6 +135,10 @@ class MainFrameModel {
|
|||||||
|
|
||||||
uiSettings = application.context.get("ui-settings")
|
uiSettings = application.context.get("ui-settings")
|
||||||
|
|
||||||
|
shared = []
|
||||||
|
treeRoot = new DefaultMutableTreeNode()
|
||||||
|
sharedTree = new DefaultTreeModel(treeRoot)
|
||||||
|
|
||||||
Timer timer = new Timer("download-pumper", true)
|
Timer timer = new Timer("download-pumper", true)
|
||||||
timer.schedule({
|
timer.schedule({
|
||||||
runInsideUIAsync {
|
runInsideUIAsync {
|
||||||
@@ -118,17 +146,26 @@ class MainFrameModel {
|
|||||||
return
|
return
|
||||||
|
|
||||||
// remove cancelled or finished downloads
|
// remove cancelled or finished downloads
|
||||||
def toRemove = []
|
if (!clearButtonEnabled || uiSettings.clearCancelledDownloads || uiSettings.clearFinishedDownloads) {
|
||||||
downloads.each {
|
def toRemove = []
|
||||||
if (uiSettings.clearCancelledDownloads &&
|
downloads.each {
|
||||||
it.downloader.getCurrentState() == Downloader.DownloadState.CANCELLED)
|
if (it.downloader.getCurrentState() == Downloader.DownloadState.CANCELLED) {
|
||||||
toRemove << it
|
if (uiSettings.clearCancelledDownloads) {
|
||||||
if (uiSettings.clearFinishedDownloads &&
|
toRemove << it
|
||||||
it.downloader.getCurrentState() == Downloader.DownloadState.FINISHED)
|
} else {
|
||||||
toRemove << it
|
clearButtonEnabled = true
|
||||||
}
|
}
|
||||||
toRemove.each {
|
} else if (it.downloader.getCurrentState() == Downloader.DownloadState.FINISHED) {
|
||||||
downloads.remove(it)
|
if (uiSettings.clearFinishedDownloads) {
|
||||||
|
toRemove << it
|
||||||
|
} else {
|
||||||
|
clearButtonEnabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
toRemove.each {
|
||||||
|
downloads.remove(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.getVariable("uploads-table")?.model.fireTableDataChanged()
|
builder.getVariable("uploads-table")?.model.fireTableDataChanged()
|
||||||
@@ -164,12 +201,19 @@ class MainFrameModel {
|
|||||||
core.eventBus.register(UpdateDownloadedEvent.class, this)
|
core.eventBus.register(UpdateDownloadedEvent.class, this)
|
||||||
core.eventBus.register(TrustSubscriptionUpdatedEvent.class, this)
|
core.eventBus.register(TrustSubscriptionUpdatedEvent.class, this)
|
||||||
|
|
||||||
|
core.muOptions.watchedKeywords.each {
|
||||||
|
core.eventBus.publish(new ContentControlEvent(term : it, regex: false, add: true))
|
||||||
|
}
|
||||||
|
core.muOptions.watchedRegexes.each {
|
||||||
|
core.eventBus.publish(new ContentControlEvent(term : it, regex: true, add: true))
|
||||||
|
}
|
||||||
|
|
||||||
timer.schedule({
|
timer.schedule({
|
||||||
if (core.shutdown.get())
|
if (core.shutdown.get())
|
||||||
return
|
return
|
||||||
int retryInterval = core.muOptions.downloadRetryInterval
|
int retryInterval = core.muOptions.downloadRetryInterval
|
||||||
if (retryInterval > 0) {
|
if (retryInterval > 0) {
|
||||||
retryInterval *= 60000
|
retryInterval *= 1000
|
||||||
long now = System.currentTimeMillis()
|
long now = System.currentTimeMillis()
|
||||||
if (now - lastRetryTime > retryInterval) {
|
if (now - lastRetryTime > retryInterval) {
|
||||||
lastRetryTime = now
|
lastRetryTime = now
|
||||||
@@ -185,13 +229,19 @@ class MainFrameModel {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 60000, 60000)
|
}, 1000, 1000)
|
||||||
|
|
||||||
runInsideUIAsync {
|
runInsideUIAsync {
|
||||||
trusted.addAll(core.trustService.good.values())
|
trusted.addAll(core.trustService.good.values())
|
||||||
distrusted.addAll(core.trustService.bad.values())
|
distrusted.addAll(core.trustService.bad.values())
|
||||||
|
|
||||||
resumeButtonText = "Retry"
|
resumeButtonText = "Retry"
|
||||||
|
|
||||||
|
searchesPaneButtonEnabled = false
|
||||||
|
downloadsPaneButtonEnabled = true
|
||||||
|
uploadsPaneButtonEnabled = true
|
||||||
|
monitorPaneButtonEnabled = true
|
||||||
|
trustPaneButtonEnabled = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -199,9 +249,7 @@ class MainFrameModel {
|
|||||||
|
|
||||||
void onAllFilesLoadedEvent(AllFilesLoadedEvent e) {
|
void onAllFilesLoadedEvent(AllFilesLoadedEvent e) {
|
||||||
runInsideUIAsync {
|
runInsideUIAsync {
|
||||||
watched.addAll(core.muOptions.watchedDirectories)
|
core.muOptions.watchedDirectories.each { core.eventBus.publish(new FileSharedEvent(file : new File(it))) }
|
||||||
builder.getVariable("watched-directories-table").model.fireTableDataChanged()
|
|
||||||
watched.each { core.eventBus.publish(new FileSharedEvent(file : new File(it))) }
|
|
||||||
|
|
||||||
core.muOptions.trustSubscriptions.each {
|
core.muOptions.trustSubscriptions.each {
|
||||||
core.eventBus.publish(new TrustSubscriptionEvent(persona : it, subscribe : true))
|
core.eventBus.publish(new TrustSubscriptionEvent(persona : it, subscribe : true))
|
||||||
@@ -269,7 +317,6 @@ class MainFrameModel {
|
|||||||
|
|
||||||
void onFileHashingEvent(FileHashingEvent e) {
|
void onFileHashingEvent(FileHashingEvent e) {
|
||||||
runInsideUIAsync {
|
runInsideUIAsync {
|
||||||
loadedFiles = shared.size()
|
|
||||||
hashingFile = e.hashingFile
|
hashingFile = e.hashingFile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -280,38 +327,51 @@ class MainFrameModel {
|
|||||||
}
|
}
|
||||||
if (e.error != null)
|
if (e.error != null)
|
||||||
return // TODO do something
|
return // TODO do something
|
||||||
if (infoHashes.contains(e.sharedFile.infoHash))
|
|
||||||
return
|
|
||||||
infoHashes.add(e.sharedFile.infoHash)
|
|
||||||
runInsideUIAsync {
|
runInsideUIAsync {
|
||||||
shared << e.sharedFile
|
shared << e.sharedFile
|
||||||
loadedFiles = shared.size()
|
loadedFiles = shared.size()
|
||||||
JTable table = builder.getVariable("shared-files-table")
|
JTable table = builder.getVariable("shared-files-table")
|
||||||
table.model.fireTableDataChanged()
|
table.model.fireTableDataChanged()
|
||||||
|
insertIntoTree(e.sharedFile)
|
||||||
|
loadedFiles = fileToNode.size()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFileLoadedEvent(FileLoadedEvent e) {
|
void onFileLoadedEvent(FileLoadedEvent e) {
|
||||||
if (infoHashes.contains(e.loadedFile.infoHash))
|
|
||||||
return
|
|
||||||
infoHashes.add(e.loadedFile.infoHash)
|
|
||||||
runInsideUIAsync {
|
runInsideUIAsync {
|
||||||
shared << e.loadedFile
|
shared << e.loadedFile
|
||||||
loadedFiles = shared.size()
|
loadedFiles = shared.size()
|
||||||
JTable table = builder.getVariable("shared-files-table")
|
JTable table = builder.getVariable("shared-files-table")
|
||||||
table.model.fireTableDataChanged()
|
table.model.fireTableDataChanged()
|
||||||
|
insertIntoTree(e.loadedFile)
|
||||||
|
loadedFiles = fileToNode.size()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFileUnsharedEvent(FileUnsharedEvent e) {
|
void onFileUnsharedEvent(FileUnsharedEvent e) {
|
||||||
InfoHash infohash = e.unsharedFile.infoHash
|
|
||||||
if (!infoHashes.remove(infohash))
|
|
||||||
return
|
|
||||||
runInsideUIAsync {
|
runInsideUIAsync {
|
||||||
shared.remove(e.unsharedFile)
|
shared.remove(e.unsharedFile)
|
||||||
loadedFiles = shared.size()
|
loadedFiles = shared.size()
|
||||||
JTable table = builder.getVariable("shared-files-table")
|
|
||||||
table.model.fireTableDataChanged()
|
def dmtn = fileToNode.remove(e.unsharedFile)
|
||||||
|
if (dmtn != null) {
|
||||||
|
loadedFiles = fileToNode.size()
|
||||||
|
while (true) {
|
||||||
|
def parent = dmtn.getParent()
|
||||||
|
parent.remove(dmtn)
|
||||||
|
if (parent == treeRoot)
|
||||||
|
break
|
||||||
|
if (parent.getChildCount() == 0) {
|
||||||
|
File file = parent.getUserObject().file
|
||||||
|
if (core.muOptions.watchedDirectories.contains(file.toString()))
|
||||||
|
core.eventBus.publish(new DirectoryUnsharedEvent(directory : parent.getUserObject().file))
|
||||||
|
dmtn = parent
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
view.refreshSharedFiles()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,14 +510,49 @@ class MainFrameModel {
|
|||||||
void onFileDownloadedEvent(FileDownloadedEvent e) {
|
void onFileDownloadedEvent(FileDownloadedEvent e) {
|
||||||
if (!core.muOptions.shareDownloadedFiles)
|
if (!core.muOptions.shareDownloadedFiles)
|
||||||
return
|
return
|
||||||
infoHashes.add(e.downloadedFile.infoHash)
|
|
||||||
runInsideUIAsync {
|
runInsideUIAsync {
|
||||||
shared << e.downloadedFile
|
shared << e.downloadedFile
|
||||||
JTable table = builder.getVariable("shared-files-table")
|
JTable table = builder.getVariable("shared-files-table")
|
||||||
table.model.fireTableDataChanged()
|
table.model.fireTableDataChanged()
|
||||||
|
insertIntoTree(e.downloadedFile)
|
||||||
|
loadedFiles = fileToNode.size()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void insertIntoTree(SharedFile file) {
|
||||||
|
List<File> parents = new ArrayList<>()
|
||||||
|
File tmp = file.file.getParentFile()
|
||||||
|
while(tmp.getParent() != null) {
|
||||||
|
parents << tmp
|
||||||
|
tmp = tmp.getParentFile()
|
||||||
|
}
|
||||||
|
Collections.reverse(parents)
|
||||||
|
TreeNode node = treeRoot
|
||||||
|
for(File path : parents) {
|
||||||
|
boolean exists = false
|
||||||
|
def children = node.children()
|
||||||
|
def child = null
|
||||||
|
while(children.hasMoreElements()) {
|
||||||
|
child = children.nextElement()
|
||||||
|
def userObject = child.getUserObject()
|
||||||
|
if (userObject != null && userObject.file == path) {
|
||||||
|
exists = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!exists) {
|
||||||
|
child = new DefaultMutableTreeNode(new InterimTreeNode(path))
|
||||||
|
node.add(child)
|
||||||
|
}
|
||||||
|
node = child
|
||||||
|
}
|
||||||
|
|
||||||
|
def dmtn = new DefaultMutableTreeNode(file)
|
||||||
|
fileToNode.put(file, dmtn)
|
||||||
|
node.add(dmtn)
|
||||||
|
view.refreshSharedFiles()
|
||||||
|
}
|
||||||
|
|
||||||
private static class UIConnection {
|
private static class UIConnection {
|
||||||
Destination destination
|
Destination destination
|
||||||
boolean incoming
|
boolean incoming
|
||||||
|
@@ -13,7 +13,10 @@ class OptionsModel {
|
|||||||
@Observable String updateCheckInterval
|
@Observable String updateCheckInterval
|
||||||
@Observable boolean autoDownloadUpdate
|
@Observable boolean autoDownloadUpdate
|
||||||
@Observable boolean shareDownloadedFiles
|
@Observable boolean shareDownloadedFiles
|
||||||
|
@Observable boolean shareHiddenFiles
|
||||||
@Observable String downloadLocation
|
@Observable String downloadLocation
|
||||||
|
@Observable boolean searchComments
|
||||||
|
@Observable boolean browseFiles
|
||||||
|
|
||||||
// i2p options
|
// i2p options
|
||||||
@Observable String inboundLength
|
@Observable String inboundLength
|
||||||
@@ -27,6 +30,8 @@ class OptionsModel {
|
|||||||
@Observable boolean showMonitor
|
@Observable boolean showMonitor
|
||||||
@Observable String lnf
|
@Observable String lnf
|
||||||
@Observable String font
|
@Observable String font
|
||||||
|
@Observable boolean automaticFontSize
|
||||||
|
@Observable int customFontSize
|
||||||
@Observable boolean clearCancelledDownloads
|
@Observable boolean clearCancelledDownloads
|
||||||
@Observable boolean clearFinishedDownloads
|
@Observable boolean clearFinishedDownloads
|
||||||
@Observable boolean excludeLocalResult
|
@Observable boolean excludeLocalResult
|
||||||
@@ -38,6 +43,7 @@ class OptionsModel {
|
|||||||
|
|
||||||
// trust options
|
// trust options
|
||||||
@Observable boolean onlyTrusted
|
@Observable boolean onlyTrusted
|
||||||
|
@Observable boolean searchExtraHop
|
||||||
@Observable boolean trustLists
|
@Observable boolean trustLists
|
||||||
@Observable String trustListInterval
|
@Observable String trustListInterval
|
||||||
|
|
||||||
@@ -48,7 +54,10 @@ class OptionsModel {
|
|||||||
updateCheckInterval = settings.updateCheckInterval
|
updateCheckInterval = settings.updateCheckInterval
|
||||||
autoDownloadUpdate = settings.autoDownloadUpdate
|
autoDownloadUpdate = settings.autoDownloadUpdate
|
||||||
shareDownloadedFiles = settings.shareDownloadedFiles
|
shareDownloadedFiles = settings.shareDownloadedFiles
|
||||||
|
shareHiddenFiles = settings.shareHiddenFiles
|
||||||
downloadLocation = settings.downloadLocation.getAbsolutePath()
|
downloadLocation = settings.downloadLocation.getAbsolutePath()
|
||||||
|
searchComments = settings.searchComments
|
||||||
|
browseFiles = settings.browseFiles
|
||||||
|
|
||||||
Core core = application.context.get("core")
|
Core core = application.context.get("core")
|
||||||
inboundLength = core.i2pOptions["inbound.length"]
|
inboundLength = core.i2pOptions["inbound.length"]
|
||||||
@@ -62,6 +71,8 @@ class OptionsModel {
|
|||||||
showMonitor = uiSettings.showMonitor
|
showMonitor = uiSettings.showMonitor
|
||||||
lnf = uiSettings.lnf
|
lnf = uiSettings.lnf
|
||||||
font = uiSettings.font
|
font = uiSettings.font
|
||||||
|
automaticFontSize = uiSettings.autoFontSize
|
||||||
|
customFontSize = uiSettings.fontSize
|
||||||
clearCancelledDownloads = uiSettings.clearCancelledDownloads
|
clearCancelledDownloads = uiSettings.clearCancelledDownloads
|
||||||
clearFinishedDownloads = uiSettings.clearFinishedDownloads
|
clearFinishedDownloads = uiSettings.clearFinishedDownloads
|
||||||
excludeLocalResult = uiSettings.excludeLocalResult
|
excludeLocalResult = uiSettings.excludeLocalResult
|
||||||
@@ -73,6 +84,7 @@ class OptionsModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onlyTrusted = !settings.allowUntrusted()
|
onlyTrusted = !settings.allowUntrusted()
|
||||||
|
searchExtraHop = settings.searchExtraHop
|
||||||
trustLists = settings.allowTrustLists
|
trustLists = settings.allowTrustLists
|
||||||
trustListInterval = String.valueOf(settings.trustListInterval)
|
trustListInterval = String.valueOf(settings.trustListInterval)
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,7 @@ import javax.inject.Inject
|
|||||||
import javax.swing.JTable
|
import javax.swing.JTable
|
||||||
|
|
||||||
import com.muwire.core.Core
|
import com.muwire.core.Core
|
||||||
|
import com.muwire.core.Persona
|
||||||
import com.muwire.core.search.UIResultEvent
|
import com.muwire.core.search.UIResultEvent
|
||||||
|
|
||||||
import griffon.core.artifact.GriffonModel
|
import griffon.core.artifact.GriffonModel
|
||||||
@@ -18,12 +19,18 @@ class SearchTabModel {
|
|||||||
@MVCMember @Nonnull
|
@MVCMember @Nonnull
|
||||||
FactoryBuilderSupport builder
|
FactoryBuilderSupport builder
|
||||||
|
|
||||||
|
@Observable boolean downloadActionEnabled
|
||||||
|
@Observable boolean trustButtonsEnabled
|
||||||
|
@Observable boolean browseActionEnabled
|
||||||
|
|
||||||
Core core
|
Core core
|
||||||
UISettings uiSettings
|
UISettings uiSettings
|
||||||
String uuid
|
String uuid
|
||||||
|
def senders = []
|
||||||
def results = []
|
def results = []
|
||||||
def hashBucket = [:]
|
def hashBucket = [:]
|
||||||
def sourcesBucket = [:]
|
def sourcesBucket = [:]
|
||||||
|
def sendersBucket = new LinkedHashMap<>()
|
||||||
|
|
||||||
|
|
||||||
void mvcGroupInit(Map<String, String> args) {
|
void mvcGroupInit(Map<String, String> args) {
|
||||||
@@ -48,6 +55,15 @@ class SearchTabModel {
|
|||||||
}
|
}
|
||||||
bucket << e
|
bucket << e
|
||||||
|
|
||||||
|
def senderBucket = sendersBucket.get(e.sender)
|
||||||
|
if (senderBucket == null) {
|
||||||
|
senderBucket = []
|
||||||
|
sendersBucket[e.sender] = senderBucket
|
||||||
|
senders.clear()
|
||||||
|
senders.addAll(sendersBucket.keySet())
|
||||||
|
}
|
||||||
|
senderBucket << e
|
||||||
|
|
||||||
Set sourceBucket = sourcesBucket.get(e.infohash)
|
Set sourceBucket = sourcesBucket.get(e.infohash)
|
||||||
if (sourceBucket == null) {
|
if (sourceBucket == null) {
|
||||||
sourceBucket = new HashSet()
|
sourceBucket = new HashSet()
|
||||||
@@ -55,8 +71,7 @@ class SearchTabModel {
|
|||||||
}
|
}
|
||||||
sourceBucket.addAll(e.sources)
|
sourceBucket.addAll(e.sources)
|
||||||
|
|
||||||
results << e
|
JTable table = builder.getVariable("senders-table")
|
||||||
JTable table = builder.getVariable("results-table")
|
|
||||||
table.model.fireTableDataChanged()
|
table.model.fireTableDataChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,6 +88,14 @@ class SearchTabModel {
|
|||||||
hashBucket[it.infohash] = bucket
|
hashBucket[it.infohash] = bucket
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def senderBucket = sendersBucket.get(it.sender)
|
||||||
|
if (senderBucket == null) {
|
||||||
|
senderBucket = []
|
||||||
|
sendersBucket[it.sender] = senderBucket
|
||||||
|
senders.clear()
|
||||||
|
senders.addAll(sendersBucket.keySet())
|
||||||
|
}
|
||||||
|
|
||||||
Set sourceBucket = sourcesBucket.get(it.infohash)
|
Set sourceBucket = sourcesBucket.get(it.infohash)
|
||||||
if (sourceBucket == null) {
|
if (sourceBucket == null) {
|
||||||
sourceBucket = new HashSet()
|
sourceBucket = new HashSet()
|
||||||
@@ -81,9 +104,9 @@ class SearchTabModel {
|
|||||||
sourceBucket.addAll(it.sources)
|
sourceBucket.addAll(it.sources)
|
||||||
|
|
||||||
bucket << it
|
bucket << it
|
||||||
results << it
|
senderBucket << it
|
||||||
}
|
}
|
||||||
JTable table = builder.getVariable("results-table")
|
JTable table = builder.getVariable("senders-table")
|
||||||
table.model.fireTableDataChanged()
|
table.model.fireTableDataChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,12 @@
|
|||||||
|
package com.muwire.gui
|
||||||
|
|
||||||
|
import com.muwire.core.search.UIResultEvent
|
||||||
|
|
||||||
|
import griffon.core.artifact.GriffonModel
|
||||||
|
import griffon.transform.Observable
|
||||||
|
import griffon.metadata.ArtifactProviderFor
|
||||||
|
|
||||||
|
@ArtifactProviderFor(GriffonModel)
|
||||||
|
class ShowCommentModel {
|
||||||
|
UIResultEvent result
|
||||||
|
}
|
BIN
gui/griffon-app/resources/comment.png
Normal file
BIN
gui/griffon-app/resources/comment.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 598 B |
68
gui/griffon-app/views/com/muwire/gui/AddCommentView.groovy
Normal file
68
gui/griffon-app/views/com/muwire/gui/AddCommentView.groovy
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package com.muwire.gui
|
||||||
|
|
||||||
|
import griffon.core.artifact.GriffonView
|
||||||
|
import griffon.inject.MVCMember
|
||||||
|
import griffon.metadata.ArtifactProviderFor
|
||||||
|
import net.i2p.data.Base64
|
||||||
|
|
||||||
|
import javax.swing.JDialog
|
||||||
|
import javax.swing.SwingConstants
|
||||||
|
|
||||||
|
import com.muwire.core.util.DataUtil
|
||||||
|
|
||||||
|
import java.awt.BorderLayout
|
||||||
|
import java.awt.event.WindowAdapter
|
||||||
|
import java.awt.event.WindowEvent
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull
|
||||||
|
|
||||||
|
@ArtifactProviderFor(GriffonView)
|
||||||
|
class AddCommentView {
|
||||||
|
@MVCMember @Nonnull
|
||||||
|
FactoryBuilderSupport builder
|
||||||
|
@MVCMember @Nonnull
|
||||||
|
AddCommentModel model
|
||||||
|
|
||||||
|
def mainFrame
|
||||||
|
def dialog
|
||||||
|
def p
|
||||||
|
def textarea
|
||||||
|
|
||||||
|
void initUI() {
|
||||||
|
mainFrame = application.windowManager.findWindow("main-frame")
|
||||||
|
String title = "Add comment to multiple files"
|
||||||
|
String comment = ""
|
||||||
|
if (model.selectedFiles.size() == 1) {
|
||||||
|
title = "Add comments to " + model.selectedFiles[0].getFile().getName()
|
||||||
|
if (model.selectedFiles[0].comment != null)
|
||||||
|
comment = DataUtil.readi18nString(Base64.decode(model.selectedFiles[0].comment))
|
||||||
|
}
|
||||||
|
dialog = new JDialog(mainFrame, title, true)
|
||||||
|
|
||||||
|
p = builder.panel {
|
||||||
|
borderLayout()
|
||||||
|
panel (constraints : BorderLayout.CENTER) {
|
||||||
|
scrollPane {
|
||||||
|
textarea = textArea(text : comment, rows : 20, columns : 100, editable : true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panel (constraints : BorderLayout.SOUTH) {
|
||||||
|
button(text : "Save", saveAction)
|
||||||
|
button(text : "Cancel", cancelAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void mvcGroupInit(Map<String,String> args) {
|
||||||
|
dialog.getContentPane().add(p)
|
||||||
|
dialog.pack()
|
||||||
|
dialog.setLocationRelativeTo(mainFrame)
|
||||||
|
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
|
||||||
|
dialog.addWindowListener( new WindowAdapter() {
|
||||||
|
public void windowClosed(WindowEvent e) {
|
||||||
|
mvcGroup.destroy()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
dialog.show()
|
||||||
|
}
|
||||||
|
}
|
132
gui/griffon-app/views/com/muwire/gui/BrowseView.groovy
Normal file
132
gui/griffon-app/views/com/muwire/gui/BrowseView.groovy
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
package com.muwire.gui
|
||||||
|
|
||||||
|
import griffon.core.artifact.GriffonView
|
||||||
|
import griffon.inject.MVCMember
|
||||||
|
import griffon.metadata.ArtifactProviderFor
|
||||||
|
|
||||||
|
import javax.swing.JDialog
|
||||||
|
import javax.swing.JLabel
|
||||||
|
import javax.swing.ListSelectionModel
|
||||||
|
import javax.swing.SwingConstants
|
||||||
|
import javax.swing.table.DefaultTableCellRenderer
|
||||||
|
|
||||||
|
import com.muwire.core.search.UIResultEvent
|
||||||
|
|
||||||
|
import java.awt.BorderLayout
|
||||||
|
import java.awt.event.WindowAdapter
|
||||||
|
import java.awt.event.WindowEvent
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull
|
||||||
|
|
||||||
|
@ArtifactProviderFor(GriffonView)
|
||||||
|
class BrowseView {
|
||||||
|
@MVCMember @Nonnull
|
||||||
|
FactoryBuilderSupport builder
|
||||||
|
@MVCMember @Nonnull
|
||||||
|
BrowseModel model
|
||||||
|
@MVCMember @Nonnull
|
||||||
|
BrowseController controller
|
||||||
|
|
||||||
|
def mainFrame
|
||||||
|
def dialog
|
||||||
|
def p
|
||||||
|
def resultsTable
|
||||||
|
def lastSortEvent
|
||||||
|
void initUI() {
|
||||||
|
mainFrame = application.windowManager.findWindow("main-frame")
|
||||||
|
dialog = new JDialog(mainFrame, model.host.getHumanReadableName(), true)
|
||||||
|
dialog.setResizable(true)
|
||||||
|
|
||||||
|
p = builder.panel {
|
||||||
|
borderLayout()
|
||||||
|
panel (constraints : BorderLayout.NORTH) {
|
||||||
|
label(text: "Status:")
|
||||||
|
label(text: bind {model.status.toString()})
|
||||||
|
}
|
||||||
|
scrollPane (constraints : BorderLayout.CENTER){
|
||||||
|
resultsTable = table(autoCreateRowSorter : true) {
|
||||||
|
tableModel(list : model.results) {
|
||||||
|
closureColumn(header: "Name", preferredWidth: 350, type: String, read : {row -> row.name.replace('<','_')})
|
||||||
|
closureColumn(header: "Size", preferredWidth: 20, type: Long, read : {row -> row.size})
|
||||||
|
closureColumn(header: "Comments", preferredWidth: 20, type: Boolean, read : {row -> row.comment != null})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panel (constraints : BorderLayout.SOUTH) {
|
||||||
|
button(text : "Download", enabled : bind {model.downloadActionEnabled}, downloadAction)
|
||||||
|
button(text : "View Comment", enabled : bind{model.viewCommentActionEnabled}, viewCommentAction)
|
||||||
|
button(text : "Dismiss", dismissAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def centerRenderer = new DefaultTableCellRenderer()
|
||||||
|
centerRenderer.setHorizontalAlignment(JLabel.CENTER)
|
||||||
|
resultsTable.setDefaultRenderer(Integer.class,centerRenderer)
|
||||||
|
|
||||||
|
resultsTable.columnModel.getColumn(1).setCellRenderer(new SizeRenderer())
|
||||||
|
|
||||||
|
|
||||||
|
resultsTable.rowSorter.addRowSorterListener({evt -> lastSortEvent = evt})
|
||||||
|
resultsTable.rowSorter.setSortsOnUpdates(true)
|
||||||
|
def selectionModel = resultsTable.getSelectionModel()
|
||||||
|
selectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
|
||||||
|
selectionModel.addListSelectionListener({
|
||||||
|
int[] rows = resultsTable.getSelectedRows()
|
||||||
|
if (rows.length == 0) {
|
||||||
|
model.downloadActionEnabled = false
|
||||||
|
model.viewCommentActionEnabled = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastSortEvent != null) {
|
||||||
|
for (int i = 0; i < rows.length; i ++) {
|
||||||
|
rows[i] = resultsTable.rowSorter.convertRowIndexToModel(rows[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean downloadActionEnabled = true
|
||||||
|
if (rows.length == 1 && model.results[rows[0]].comment != null)
|
||||||
|
model.viewCommentActionEnabled = true
|
||||||
|
else
|
||||||
|
model.viewCommentActionEnabled = false
|
||||||
|
|
||||||
|
rows.each {
|
||||||
|
downloadActionEnabled &= mvcGroup.parentGroup.parentGroup.model.canDownload(model.results[it].infohash)
|
||||||
|
}
|
||||||
|
model.downloadActionEnabled = downloadActionEnabled
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
void mvcGroupInit(Map<String,String> args) {
|
||||||
|
controller.register()
|
||||||
|
|
||||||
|
dialog.getContentPane().add(p)
|
||||||
|
dialog.setSize(700, 400)
|
||||||
|
dialog.setLocationRelativeTo(mainFrame)
|
||||||
|
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
|
||||||
|
dialog.addWindowListener( new WindowAdapter() {
|
||||||
|
public void windowClosed(WindowEvent e) {
|
||||||
|
mvcGroup.destroy()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
dialog.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def selectedResults() {
|
||||||
|
int [] rows = resultsTable.getSelectedRows()
|
||||||
|
if (rows.length == 0)
|
||||||
|
return null
|
||||||
|
if (lastSortEvent != null) {
|
||||||
|
for (int i = 0; i < rows.length; i ++) {
|
||||||
|
rows[i] = resultsTable.rowSorter.convertRowIndexToModel(rows[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<UIResultEvent> rv = new ArrayList<>()
|
||||||
|
for (Integer i : rows)
|
||||||
|
rv << model.results[i]
|
||||||
|
rv
|
||||||
|
}
|
||||||
|
}
|
155
gui/griffon-app/views/com/muwire/gui/ContentPanelView.groovy
Normal file
155
gui/griffon-app/views/com/muwire/gui/ContentPanelView.groovy
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
package com.muwire.gui
|
||||||
|
|
||||||
|
import griffon.core.artifact.GriffonView
|
||||||
|
import griffon.inject.MVCMember
|
||||||
|
import griffon.metadata.ArtifactProviderFor
|
||||||
|
|
||||||
|
import javax.swing.JDialog
|
||||||
|
import javax.swing.JLabel
|
||||||
|
import javax.swing.ListSelectionModel
|
||||||
|
import javax.swing.SwingConstants
|
||||||
|
import javax.swing.table.DefaultTableCellRenderer
|
||||||
|
|
||||||
|
import com.muwire.core.content.Matcher
|
||||||
|
import com.muwire.core.content.RegexMatcher
|
||||||
|
|
||||||
|
import java.awt.BorderLayout
|
||||||
|
import java.awt.event.WindowAdapter
|
||||||
|
import java.awt.event.WindowEvent
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull
|
||||||
|
|
||||||
|
@ArtifactProviderFor(GriffonView)
|
||||||
|
class ContentPanelView {
|
||||||
|
@MVCMember @Nonnull
|
||||||
|
FactoryBuilderSupport builder
|
||||||
|
@MVCMember @Nonnull
|
||||||
|
ContentPanelModel model
|
||||||
|
|
||||||
|
def dialog
|
||||||
|
def mainFrame
|
||||||
|
def mainPanel
|
||||||
|
|
||||||
|
def rulesTable
|
||||||
|
def ruleTextField
|
||||||
|
def lastRulesSortEvent
|
||||||
|
def hitsTable
|
||||||
|
def lastHitsSortEvent
|
||||||
|
|
||||||
|
void initUI() {
|
||||||
|
mainFrame = application.windowManager.findWindow("main-frame")
|
||||||
|
dialog = new JDialog(mainFrame, "Content Control Panel", true)
|
||||||
|
|
||||||
|
mainPanel = builder.panel {
|
||||||
|
gridLayout(rows:1, cols:2)
|
||||||
|
panel {
|
||||||
|
borderLayout()
|
||||||
|
panel (constraints : BorderLayout.NORTH) {
|
||||||
|
label(text : "Rules")
|
||||||
|
}
|
||||||
|
scrollPane (constraints : BorderLayout.CENTER) {
|
||||||
|
rulesTable = table(id : "rules-table", autoCreateRowSorter : true) {
|
||||||
|
tableModel(list : model.rules) {
|
||||||
|
closureColumn(header: "Term", type:String, read: {row -> row.getTerm()})
|
||||||
|
closureColumn(header: "Regex?", type:Boolean, read: {row -> row instanceof RegexMatcher})
|
||||||
|
closureColumn(header: "Hits", type:Integer, read : {row -> row.matches.size()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panel (constraints : BorderLayout.SOUTH) {
|
||||||
|
borderLayout()
|
||||||
|
ruleTextField = textField(constraints: BorderLayout.CENTER, action: addRuleAction)
|
||||||
|
panel (constraints: BorderLayout.EAST) {
|
||||||
|
buttonGroup(id : "ruleType")
|
||||||
|
radioButton(text: "Keyword", selected : true, buttonGroup: ruleType, keywordAction)
|
||||||
|
radioButton(text: "Regex", selected : false, buttonGroup: ruleType, regexAction)
|
||||||
|
button(text : "Add Rule", addRuleAction)
|
||||||
|
button(text : "Delete Rule", enabled : bind {model.deleteButtonEnabled}, deleteRuleAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panel (border : etchedBorder()){
|
||||||
|
borderLayout()
|
||||||
|
panel (constraints : BorderLayout.NORTH) {
|
||||||
|
label(text : "Hits")
|
||||||
|
}
|
||||||
|
scrollPane(constraints : BorderLayout.CENTER) {
|
||||||
|
hitsTable = table(id : "hits-table", autoCreateRowSorter : true) {
|
||||||
|
tableModel(list : model.hits) {
|
||||||
|
closureColumn(header : "Searcher", type : String, read : {row -> row.persona.getHumanReadableName()})
|
||||||
|
closureColumn(header : "Keywords", type : String, read : {row -> row.keywords.join(" ")})
|
||||||
|
closureColumn(header : "Date", type : String, read : {row -> String.valueOf(new Date(row.timestamp))})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panel (constraints : BorderLayout.SOUTH) {
|
||||||
|
button(text : "Refresh", refreshAction)
|
||||||
|
button(text : "Clear Hits", clearHitsAction)
|
||||||
|
button(text : "Trust", enabled : bind {model.trustButtonsEnabled}, trustAction)
|
||||||
|
button(text : "Distrust", enabled : bind {model.trustButtonsEnabled}, distrustAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int getSelectedRule() {
|
||||||
|
int selectedRow = rulesTable.getSelectedRow()
|
||||||
|
if (selectedRow < 0)
|
||||||
|
return -1
|
||||||
|
if (lastRulesSortEvent != null)
|
||||||
|
selectedRow = rulesTable.rowSorter.convertRowIndexToModel(selectedRow)
|
||||||
|
selectedRow
|
||||||
|
}
|
||||||
|
|
||||||
|
int getSelectedHit() {
|
||||||
|
int selectedRow = hitsTable.getSelectedRow()
|
||||||
|
if (selectedRow < 0)
|
||||||
|
return -1
|
||||||
|
if (lastHitsSortEvent != null)
|
||||||
|
selectedRow = hitsTable.rowSorter.convertRowIndexToModel(selectedRow)
|
||||||
|
selectedRow
|
||||||
|
}
|
||||||
|
|
||||||
|
void mvcGroupInit(Map<String,String> args) {
|
||||||
|
def centerRenderer = new DefaultTableCellRenderer()
|
||||||
|
centerRenderer.setHorizontalAlignment(JLabel.CENTER)
|
||||||
|
rulesTable.setDefaultRenderer(Integer.class, centerRenderer)
|
||||||
|
rulesTable.rowSorter.addRowSorterListener({evt -> lastRulesSortEvent = evt})
|
||||||
|
rulesTable.rowSorter.setSortsOnUpdates(true)
|
||||||
|
def selectionModel = rulesTable.getSelectionModel()
|
||||||
|
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
|
||||||
|
selectionModel.addListSelectionListener({
|
||||||
|
int selectedRow = getSelectedRule()
|
||||||
|
if (selectedRow < 0) {
|
||||||
|
model.deleteButtonEnabled = false
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
model.deleteButtonEnabled = true
|
||||||
|
model.hits.clear()
|
||||||
|
Matcher matcher = model.rules[selectedRow]
|
||||||
|
model.hits.addAll(matcher.matches)
|
||||||
|
hitsTable.model.fireTableDataChanged()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
hitsTable.rowSorter.addRowSorterListener({evt -> lastHitsSortEvent = evt})
|
||||||
|
hitsTable.rowSorter.setSortsOnUpdates(true)
|
||||||
|
selectionModel = hitsTable.getSelectionModel()
|
||||||
|
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
|
||||||
|
selectionModel.addListSelectionListener({
|
||||||
|
int selectedRow = getSelectedHit()
|
||||||
|
model.trustButtonsEnabled = selectedRow >= 0
|
||||||
|
})
|
||||||
|
|
||||||
|
dialog.getContentPane().add(mainPanel)
|
||||||
|
dialog.pack()
|
||||||
|
dialog.setLocationRelativeTo(mainFrame)
|
||||||
|
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
|
||||||
|
dialog.addWindowListener(new WindowAdapter() {
|
||||||
|
public void windowClosed(WindowEvent e) {
|
||||||
|
mvcGroup.destroy()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
dialog.show()
|
||||||
|
}
|
||||||
|
}
|
@@ -16,17 +16,21 @@ import javax.swing.JMenuItem
|
|||||||
import javax.swing.JPopupMenu
|
import javax.swing.JPopupMenu
|
||||||
import javax.swing.JSplitPane
|
import javax.swing.JSplitPane
|
||||||
import javax.swing.JTable
|
import javax.swing.JTable
|
||||||
|
import javax.swing.JTree
|
||||||
import javax.swing.ListSelectionModel
|
import javax.swing.ListSelectionModel
|
||||||
import javax.swing.SwingConstants
|
import javax.swing.SwingConstants
|
||||||
|
import javax.swing.TransferHandler
|
||||||
import javax.swing.border.Border
|
import javax.swing.border.Border
|
||||||
import javax.swing.table.DefaultTableCellRenderer
|
import javax.swing.table.DefaultTableCellRenderer
|
||||||
|
import javax.swing.tree.TreeNode
|
||||||
|
import javax.swing.tree.TreePath
|
||||||
|
|
||||||
import com.muwire.core.Constants
|
import com.muwire.core.Constants
|
||||||
import com.muwire.core.MuWireSettings
|
import com.muwire.core.MuWireSettings
|
||||||
|
import com.muwire.core.SharedFile
|
||||||
import com.muwire.core.download.Downloader
|
import com.muwire.core.download.Downloader
|
||||||
import com.muwire.core.files.FileSharedEvent
|
import com.muwire.core.files.FileSharedEvent
|
||||||
import com.muwire.core.trust.RemoteTrustList
|
import com.muwire.core.trust.RemoteTrustList
|
||||||
|
|
||||||
import java.awt.BorderLayout
|
import java.awt.BorderLayout
|
||||||
import java.awt.CardLayout
|
import java.awt.CardLayout
|
||||||
import java.awt.FlowLayout
|
import java.awt.FlowLayout
|
||||||
@@ -34,6 +38,7 @@ import java.awt.GridBagConstraints
|
|||||||
import java.awt.GridBagLayout
|
import java.awt.GridBagLayout
|
||||||
import java.awt.Insets
|
import java.awt.Insets
|
||||||
import java.awt.Toolkit
|
import java.awt.Toolkit
|
||||||
|
import java.awt.datatransfer.DataFlavor
|
||||||
import java.awt.datatransfer.StringSelection
|
import java.awt.datatransfer.StringSelection
|
||||||
import java.awt.event.MouseAdapter
|
import java.awt.event.MouseAdapter
|
||||||
import java.awt.event.MouseEvent
|
import java.awt.event.MouseEvent
|
||||||
@@ -54,16 +59,17 @@ class MainFrameView {
|
|||||||
def downloadsTable
|
def downloadsTable
|
||||||
def lastDownloadSortEvent
|
def lastDownloadSortEvent
|
||||||
def lastSharedSortEvent
|
def lastSharedSortEvent
|
||||||
def lastWatchedSortEvent
|
|
||||||
def trustTablesSortEvents = [:]
|
def trustTablesSortEvents = [:]
|
||||||
|
|
||||||
|
UISettings settings
|
||||||
|
|
||||||
void initUI() {
|
void initUI() {
|
||||||
UISettings settings = application.context.get("ui-settings")
|
settings = application.context.get("ui-settings")
|
||||||
builder.with {
|
builder.with {
|
||||||
application(size : [1024,768], id: 'main-frame',
|
application(size : [1024,768], id: 'main-frame',
|
||||||
locationRelativeTo : null,
|
locationRelativeTo : null,
|
||||||
title: application.configuration['application.title'] + " " +
|
title: application.configuration['application.title'] + " " +
|
||||||
metadata["application.version"] + " revision " + metadata["build.revision"],
|
metadata["application.version"] + " revision " + metadata["build.revision"],
|
||||||
iconImage: imageIcon('/MuWire-48x48.png').image,
|
iconImage: imageIcon('/MuWire-48x48.png').image,
|
||||||
iconImages: [imageIcon('/MuWire-48x48.png').image,
|
iconImages: [imageIcon('/MuWire-48x48.png').image,
|
||||||
imageIcon('/MuWire-32x32.png').image,
|
imageIcon('/MuWire-32x32.png').image,
|
||||||
@@ -73,6 +79,11 @@ class MainFrameView {
|
|||||||
menuBar {
|
menuBar {
|
||||||
menu (text : "Options") {
|
menu (text : "Options") {
|
||||||
menuItem("Configuration", actionPerformed : {mvcGroup.createMVCGroup("Options")})
|
menuItem("Configuration", actionPerformed : {mvcGroup.createMVCGroup("Options")})
|
||||||
|
menuItem("Content Control", actionPerformed : {
|
||||||
|
def env = [:]
|
||||||
|
env["core"] = model.core
|
||||||
|
mvcGroup.createMVCGroup("content-panel", env)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
menu (text : "Status") {
|
menu (text : "Status") {
|
||||||
menuItem("MuWire", actionPerformed : {mvcGroup.createMVCGroup("mu-wire-status")})
|
menuItem("MuWire", actionPerformed : {mvcGroup.createMVCGroup("mu-wire-status")})
|
||||||
@@ -85,16 +96,17 @@ class MainFrameView {
|
|||||||
borderLayout()
|
borderLayout()
|
||||||
panel (constraints: BorderLayout.WEST) {
|
panel (constraints: BorderLayout.WEST) {
|
||||||
gridLayout(rows:1, cols: 2)
|
gridLayout(rows:1, cols: 2)
|
||||||
button(text: "Searches", actionPerformed : showSearchWindow)
|
button(text: "Searches", enabled : bind{model.searchesPaneButtonEnabled},actionPerformed : showSearchWindow)
|
||||||
button(text: "Uploads", actionPerformed : showUploadsWindow)
|
button(text: "Downloads", enabled : bind{model.downloadsPaneButtonEnabled}, actionPerformed : showDownloadsWindow)
|
||||||
|
button(text: "Uploads", enabled : bind{model.uploadsPaneButtonEnabled}, actionPerformed : showUploadsWindow)
|
||||||
if (settings.showMonitor)
|
if (settings.showMonitor)
|
||||||
button(text: "Monitor", actionPerformed : showMonitorWindow)
|
button(text: "Monitor", enabled: bind{model.monitorPaneButtonEnabled},actionPerformed : showMonitorWindow)
|
||||||
button(text: "Trust", actionPerformed : showTrustWindow)
|
button(text: "Trust", enabled:bind{model.trustPaneButtonEnabled},actionPerformed : showTrustWindow)
|
||||||
}
|
}
|
||||||
panel(id: "top-panel", constraints: BorderLayout.CENTER) {
|
panel(id: "top-panel", constraints: BorderLayout.CENTER) {
|
||||||
cardLayout()
|
cardLayout()
|
||||||
label(constraints : "top-connect-panel",
|
label(constraints : "top-connect-panel",
|
||||||
text : " MuWire is connecting, please wait. You will be able to search soon.") // TODO: real padding
|
text : " MuWire is connecting, please wait. You will be able to search soon.") // TODO: real padding
|
||||||
panel(constraints : "top-search-panel") {
|
panel(constraints : "top-search-panel") {
|
||||||
borderLayout()
|
borderLayout()
|
||||||
panel(constraints: BorderLayout.CENTER) {
|
panel(constraints: BorderLayout.CENTER) {
|
||||||
@@ -113,37 +125,19 @@ class MainFrameView {
|
|||||||
cardLayout()
|
cardLayout()
|
||||||
panel (constraints : "search window") {
|
panel (constraints : "search window") {
|
||||||
borderLayout()
|
borderLayout()
|
||||||
splitPane( orientation : JSplitPane.VERTICAL_SPLIT, dividerLocation : 500,
|
tabbedPane(id : "result-tabs", constraints: BorderLayout.CENTER)
|
||||||
continuousLayout : true, constraints : BorderLayout.CENTER) {
|
}
|
||||||
panel (constraints : JSplitPane.TOP) {
|
panel (constraints: "downloads window") {
|
||||||
borderLayout()
|
gridLayout(rows : 1, cols : 1)
|
||||||
tabbedPane(id : "result-tabs", constraints: BorderLayout.CENTER)
|
splitPane(orientation: JSplitPane.VERTICAL_SPLIT, continuousLayout : true, dividerLocation: 500 ) {
|
||||||
panel(constraints : BorderLayout.SOUTH) {
|
panel {
|
||||||
button(text : "Download", enabled : bind {model.downloadActionEnabled}, downloadAction)
|
|
||||||
button(text : "Trust", enabled: bind {model.trustButtonsEnabled }, trustAction)
|
|
||||||
button(text : "Distrust", enabled : bind {model.trustButtonsEnabled}, distrustAction)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
panel (constraints : JSplitPane.BOTTOM) {
|
|
||||||
borderLayout()
|
borderLayout()
|
||||||
scrollPane (constraints : BorderLayout.CENTER) {
|
scrollPane (constraints : BorderLayout.CENTER) {
|
||||||
downloadsTable = table(id : "downloads-table", autoCreateRowSorter : true) {
|
downloadsTable = table(id : "downloads-table", autoCreateRowSorter : true) {
|
||||||
tableModel(list: model.downloads) {
|
tableModel(list: model.downloads) {
|
||||||
closureColumn(header: "Name", preferredWidth: 300, type: String, read : {row -> row.downloader.file.getName()})
|
closureColumn(header: "Name", preferredWidth: 300, type: String, read : {row -> row.downloader.file.getName()})
|
||||||
closureColumn(header: "Status", preferredWidth: 50, type: String, read : {row -> row.downloader.getCurrentState().toString()})
|
closureColumn(header: "Status", preferredWidth: 50, type: String, read : {row -> row.downloader.getCurrentState().toString()})
|
||||||
closureColumn(header: "Progress", preferredWidth: 70, type: String, read: { row ->
|
closureColumn(header: "Progress", preferredWidth: 70, type: Downloader, read: { row -> row.downloader })
|
||||||
int pieces = row.downloader.nPieces
|
|
||||||
int done = row.downloader.donePieces()
|
|
||||||
int percent = -1
|
|
||||||
if ( row.downloader.nPieces != 0 ) {
|
|
||||||
percent = (done * 100) / pieces
|
|
||||||
}
|
|
||||||
long size = row.downloader.pieceSize
|
|
||||||
size *= pieces
|
|
||||||
String totalSize = DataHelper.formatSize2Decimal(size, false) + "B"
|
|
||||||
String.format("%02d", percent) + "% of ${totalSize} ($done/$pieces pcs)".toString()
|
|
||||||
})
|
|
||||||
closureColumn(header: "Sources", preferredWidth : 10, type: Integer, read : {row -> row.downloader.activeWorkers()})
|
|
||||||
closureColumn(header: "Speed", preferredWidth: 50, type:String, read :{row ->
|
closureColumn(header: "Speed", preferredWidth: 50, type:String, read :{row ->
|
||||||
DataHelper.formatSize2Decimal(row.downloader.speed(), false) + "B/sec"
|
DataHelper.formatSize2Decimal(row.downloader.speed(), false) + "B/sec"
|
||||||
})
|
})
|
||||||
@@ -152,8 +146,39 @@ class MainFrameView {
|
|||||||
}
|
}
|
||||||
panel (constraints : BorderLayout.SOUTH) {
|
panel (constraints : BorderLayout.SOUTH) {
|
||||||
button(text: "Pause", enabled : bind {model.pauseButtonEnabled}, pauseAction)
|
button(text: "Pause", enabled : bind {model.pauseButtonEnabled}, pauseAction)
|
||||||
button(text: "Cancel", enabled : bind {model.cancelButtonEnabled }, cancelAction )
|
|
||||||
button(text: bind { model.resumeButtonText }, enabled : bind {model.retryButtonEnabled}, resumeAction)
|
button(text: bind { model.resumeButtonText }, enabled : bind {model.retryButtonEnabled}, resumeAction)
|
||||||
|
button(text: "Cancel", enabled : bind {model.cancelButtonEnabled }, cancelAction)
|
||||||
|
button(text: "Clear Done", enabled : bind {model.clearButtonEnabled}, clearAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panel {
|
||||||
|
borderLayout()
|
||||||
|
panel(constraints : BorderLayout.NORTH) {
|
||||||
|
label(text : "Download Details")
|
||||||
|
}
|
||||||
|
scrollPane(constraints : BorderLayout.CENTER) {
|
||||||
|
panel (id : "download-details-panel") {
|
||||||
|
cardLayout()
|
||||||
|
panel (constraints : "select-download") {
|
||||||
|
label(text : "Select a download to view details")
|
||||||
|
}
|
||||||
|
panel(constraints : "download-selected") {
|
||||||
|
gridBagLayout()
|
||||||
|
label(text : "Download Location:", constraints : gbc(gridx:0, gridy:0))
|
||||||
|
label(text : bind {model.downloader?.file?.getAbsolutePath()},
|
||||||
|
constraints: gbc(gridx:1, gridy:0, gridwidth: 2, insets : [0,0,0,20]))
|
||||||
|
label(text : "Piece Size", constraints : gbc(gridx: 0, gridy:1))
|
||||||
|
label(text : bind {model.downloader?.pieceSize}, constraints : gbc(gridx:1, gridy:1))
|
||||||
|
label(text : "Known Sources:", constraints : gbc(gridx:3, gridy: 0))
|
||||||
|
label(text : bind {model.downloader?.activeWorkers?.size()}, constraints : gbc(gridx:4, gridy:0, insets : [0,0,0,20]))
|
||||||
|
label(text : "Active Sources:", constraints : gbc(gridx:3, gridy:1))
|
||||||
|
label(text : bind {model.downloader?.activeWorkers()}, constraints : gbc(gridx:4, gridy:1, insets : [0,0,0,20]))
|
||||||
|
label(text : "Total Pieces:", constraints : gbc(gridx:5, gridy: 0))
|
||||||
|
label(text : bind {model.downloader?.nPieces}, constraints : gbc(gridx:6, gridy:0, insets : [0,0,0,20]))
|
||||||
|
label(text : "Done Pieces:", constraints: gbc(gridx:5, gridy: 1))
|
||||||
|
label(text : bind {model.downloader?.donePieces()}, constraints : gbc(gridx:6, gridy:1, insets : [0,0,0,20]))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,46 +189,55 @@ class MainFrameView {
|
|||||||
borderLayout()
|
borderLayout()
|
||||||
panel (constraints : BorderLayout.NORTH) {
|
panel (constraints : BorderLayout.NORTH) {
|
||||||
label(text : bind {
|
label(text : bind {
|
||||||
if (model.hashingFile == null) {
|
if (model.hashingFile == null) {
|
||||||
""
|
"You can drag-and-drop files and directories here"
|
||||||
} else {
|
} else {
|
||||||
"hashing: " + model.hashingFile.getAbsolutePath() + " (" + DataHelper.formatSize2Decimal(model.hashingFile.length(), false).toString() + "B)"
|
"hashing: " + model.hashingFile.getAbsolutePath() + " (" + DataHelper.formatSize2Decimal(model.hashingFile.length(), false).toString() + "B)"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
panel (border : etchedBorder(), constraints : BorderLayout.CENTER) {
|
panel (border : etchedBorder(), constraints : BorderLayout.CENTER) {
|
||||||
gridLayout(cols : 2, rows : 1)
|
borderLayout()
|
||||||
panel {
|
panel (id : "shared-files-panel", constraints : BorderLayout.CENTER){
|
||||||
borderLayout()
|
cardLayout()
|
||||||
scrollPane (constraints : BorderLayout.CENTER) {
|
panel (constraints : "shared files table") {
|
||||||
table(id : "watched-directories-table", autoCreateRowSorter: true) {
|
borderLayout()
|
||||||
tableModel(list : model.watched) {
|
scrollPane(constraints : BorderLayout.CENTER) {
|
||||||
closureColumn(header: "Watched Directories", type : String, read : { it })
|
table(id : "shared-files-table", autoCreateRowSorter: true) {
|
||||||
|
tableModel(list : model.shared) {
|
||||||
|
closureColumn(header : "Name", preferredWidth : 500, type : String, read : {row -> row.getCachedPath()})
|
||||||
|
closureColumn(header : "Size", preferredWidth : 100, type : Long, read : {row -> row.getCachedLength() })
|
||||||
|
closureColumn(header : "Comments", preferredWidth : 100, type : Boolean, read : {it.getComment() != null})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
panel (constraints : "shared files tree") {
|
||||||
panel {
|
borderLayout()
|
||||||
borderLayout()
|
scrollPane(constraints : BorderLayout.CENTER) {
|
||||||
scrollPane(constraints : BorderLayout.CENTER) {
|
def jtree = new JTree(model.sharedTree)
|
||||||
table(id : "shared-files-table", autoCreateRowSorter: true) {
|
jtree.setCellRenderer(new SharedTreeRenderer())
|
||||||
tableModel(list : model.shared) {
|
tree(id : "shared-files-tree", rootVisible : false, expandsSelectedPaths: true, jtree)
|
||||||
closureColumn(header : "Name", preferredWidth : 500, type : String, read : {row -> row.getCachedPath()})
|
|
||||||
closureColumn(header : "Size", preferredWidth : 100, type : Long, read : {row -> row.getCachedLength() })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
panel (constraints : BorderLayout.SOUTH) {
|
panel (constraints : BorderLayout.SOUTH) {
|
||||||
gridLayout(rows:1, cols:2)
|
gridLayout(rows:1, cols:3)
|
||||||
panel {
|
panel {
|
||||||
button(text : "Add directories to watch", actionPerformed : watchDirectories)
|
buttonGroup(id : "sharedViewType")
|
||||||
button(text : "Share files", actionPerformed : shareFiles)
|
radioButton(text : "Tree", selected : true, buttonGroup : sharedViewType, actionPerformed : showSharedFilesTree)
|
||||||
|
radioButton(text : "Table", selected : false, buttonGroup : sharedViewType, actionPerformed : showSharedFilesTable)
|
||||||
}
|
}
|
||||||
panel {
|
panel {
|
||||||
label("Shared:")
|
button(text : "Share files", actionPerformed : shareFiles)
|
||||||
label(text : bind {model.loadedFiles.toString()})
|
button(text : "Add Comment", enabled : bind {model.addCommentButtonEnabled}, addCommentAction)
|
||||||
|
}
|
||||||
|
panel {
|
||||||
|
panel {
|
||||||
|
label("Shared:")
|
||||||
|
label(text : bind {model.loadedFiles}, id : "shared-files-count")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -287,8 +321,8 @@ class MainFrameView {
|
|||||||
})
|
})
|
||||||
closureColumn(header : "Timestamp", type : String, read : {
|
closureColumn(header : "Timestamp", type : String, read : {
|
||||||
String.format("%02d", it.timestamp.get(Calendar.HOUR_OF_DAY)) + ":" +
|
String.format("%02d", it.timestamp.get(Calendar.HOUR_OF_DAY)) + ":" +
|
||||||
String.format("%02d", it.timestamp.get(Calendar.MINUTE)) + ":" +
|
String.format("%02d", it.timestamp.get(Calendar.MINUTE)) + ":" +
|
||||||
String.format("%02d", it.timestamp.get(Calendar.SECOND))
|
String.format("%02d", it.timestamp.get(Calendar.SECOND))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -374,74 +408,91 @@ class MainFrameView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void mvcGroupInit(Map<String, String> args) {
|
void mvcGroupInit(Map<String, String> args) {
|
||||||
|
|
||||||
|
def mainFrame = builder.getVariable("main-frame")
|
||||||
|
mainFrame.setTransferHandler(new TransferHandler() {
|
||||||
|
public boolean canImport(TransferHandler.TransferSupport support) {
|
||||||
|
return support.isDataFlavorSupported(DataFlavor.javaFileListFlavor)
|
||||||
|
}
|
||||||
|
public boolean importData(TransferHandler.TransferSupport support) {
|
||||||
|
def files = support.getTransferable().getTransferData(DataFlavor.javaFileListFlavor)
|
||||||
|
files.each {
|
||||||
|
model.core.eventBus.publish(new FileSharedEvent(file : it))
|
||||||
|
}
|
||||||
|
showUploadsWindow.call()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
def downloadsTable = builder.getVariable("downloads-table")
|
def downloadsTable = builder.getVariable("downloads-table")
|
||||||
def selectionModel = downloadsTable.getSelectionModel()
|
def selectionModel = downloadsTable.getSelectionModel()
|
||||||
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
|
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
|
||||||
selectionModel.addListSelectionListener({
|
selectionModel.addListSelectionListener({
|
||||||
|
def downloadDetailsPanel = builder.getVariable("download-details-panel")
|
||||||
int selectedRow = selectedDownloaderRow()
|
int selectedRow = selectedDownloaderRow()
|
||||||
if (selectedRow < 0) {
|
if (selectedRow < 0) {
|
||||||
model.cancelButtonEnabled = false
|
model.cancelButtonEnabled = false
|
||||||
model.retryButtonEnabled = false
|
model.retryButtonEnabled = false
|
||||||
model.pauseButtonEnabled = false
|
model.pauseButtonEnabled = false
|
||||||
|
model.downloader = null
|
||||||
|
downloadDetailsPanel.getLayout().show(downloadDetailsPanel,"select-download")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
def downloader = model.downloads[selectedRow]?.downloader
|
def downloader = model.downloads[selectedRow]?.downloader
|
||||||
if (downloader == null)
|
if (downloader == null)
|
||||||
return
|
return
|
||||||
|
model.downloader = downloader
|
||||||
|
downloadDetailsPanel.getLayout().show(downloadDetailsPanel,"download-selected")
|
||||||
switch(downloader.getCurrentState()) {
|
switch(downloader.getCurrentState()) {
|
||||||
case Downloader.DownloadState.CONNECTING :
|
case Downloader.DownloadState.CONNECTING :
|
||||||
case Downloader.DownloadState.DOWNLOADING :
|
case Downloader.DownloadState.DOWNLOADING :
|
||||||
case Downloader.DownloadState.HASHLIST:
|
case Downloader.DownloadState.HASHLIST:
|
||||||
model.cancelButtonEnabled = true
|
model.cancelButtonEnabled = true
|
||||||
model.pauseButtonEnabled = true
|
model.pauseButtonEnabled = true
|
||||||
model.retryButtonEnabled = false
|
model.retryButtonEnabled = false
|
||||||
break
|
break
|
||||||
case Downloader.DownloadState.FAILED:
|
case Downloader.DownloadState.FAILED:
|
||||||
model.cancelButtonEnabled = true
|
model.cancelButtonEnabled = true
|
||||||
model.retryButtonEnabled = true
|
model.retryButtonEnabled = true
|
||||||
model.resumeButtonText = "Retry"
|
model.resumeButtonText = "Retry"
|
||||||
model.pauseButtonEnabled = false
|
model.pauseButtonEnabled = false
|
||||||
break
|
break
|
||||||
case Downloader.DownloadState.PAUSED:
|
case Downloader.DownloadState.PAUSED:
|
||||||
model.cancelButtonEnabled = true
|
model.cancelButtonEnabled = true
|
||||||
model.retryButtonEnabled = true
|
model.retryButtonEnabled = true
|
||||||
model.resumeButtonText = "Resume"
|
model.resumeButtonText = "Resume"
|
||||||
model.pauseButtonEnabled = false
|
model.pauseButtonEnabled = false
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
model.cancelButtonEnabled = false
|
model.cancelButtonEnabled = false
|
||||||
model.retryButtonEnabled = false
|
model.retryButtonEnabled = false
|
||||||
model.pauseButtonEnabled = false
|
model.pauseButtonEnabled = false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
def centerRenderer = new DefaultTableCellRenderer()
|
def centerRenderer = new DefaultTableCellRenderer()
|
||||||
centerRenderer.setHorizontalAlignment(JLabel.CENTER)
|
centerRenderer.setHorizontalAlignment(JLabel.CENTER)
|
||||||
downloadsTable.setDefaultRenderer(Integer.class, centerRenderer)
|
downloadsTable.setDefaultRenderer(Integer.class, centerRenderer)
|
||||||
|
downloadsTable.setDefaultRenderer(Downloader.class, new DownloadProgressRenderer())
|
||||||
|
|
||||||
downloadsTable.rowSorter.addRowSorterListener({evt -> lastDownloadSortEvent = evt})
|
downloadsTable.rowSorter.addRowSorterListener({evt -> lastDownloadSortEvent = evt})
|
||||||
downloadsTable.rowSorter.setSortsOnUpdates(true)
|
downloadsTable.rowSorter.setSortsOnUpdates(true)
|
||||||
|
downloadsTable.rowSorter.setComparator(2, new DownloaderComparator())
|
||||||
|
|
||||||
downloadsTable.addMouseListener(new MouseAdapter() {
|
downloadsTable.addMouseListener(new MouseAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void mouseReleased(MouseEvent e) {
|
public void mouseReleased(MouseEvent e) {
|
||||||
if (e.isPopupTrigger())
|
if (e.isPopupTrigger())
|
||||||
showDownloadsMenu(e)
|
showDownloadsMenu(e)
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void mousePressed(MouseEvent e) {
|
public void mousePressed(MouseEvent e) {
|
||||||
if (e.isPopupTrigger())
|
if (e.isPopupTrigger())
|
||||||
showDownloadsMenu(e)
|
showDownloadsMenu(e)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// shared files table
|
|
||||||
def sharedFilesTable = builder.getVariable("shared-files-table")
|
|
||||||
sharedFilesTable.columnModel.getColumn(1).setCellRenderer(new SizeRenderer())
|
|
||||||
|
|
||||||
sharedFilesTable.rowSorter.addRowSorterListener({evt -> lastSharedSortEvent = evt})
|
|
||||||
sharedFilesTable.rowSorter.setSortsOnUpdates(true)
|
|
||||||
|
|
||||||
|
// shared files menu
|
||||||
JPopupMenu sharedFilesMenu = new JPopupMenu()
|
JPopupMenu sharedFilesMenu = new JPopupMenu()
|
||||||
JMenuItem copyHashToClipboard = new JMenuItem("Copy hash to clipboard")
|
JMenuItem copyHashToClipboard = new JMenuItem("Copy hash to clipboard")
|
||||||
copyHashToClipboard.addActionListener({mvcGroup.view.copyHashToClipboard()})
|
copyHashToClipboard.addActionListener({mvcGroup.view.copyHashToClipboard()})
|
||||||
@@ -449,58 +500,75 @@ class MainFrameView {
|
|||||||
JMenuItem unshareSelectedFiles = new JMenuItem("Unshare selected files")
|
JMenuItem unshareSelectedFiles = new JMenuItem("Unshare selected files")
|
||||||
unshareSelectedFiles.addActionListener({mvcGroup.controller.unshareSelectedFile()})
|
unshareSelectedFiles.addActionListener({mvcGroup.controller.unshareSelectedFile()})
|
||||||
sharedFilesMenu.add(unshareSelectedFiles)
|
sharedFilesMenu.add(unshareSelectedFiles)
|
||||||
sharedFilesTable.addMouseListener(new MouseAdapter() {
|
JMenuItem commentSelectedFiles = new JMenuItem("Comment selected files")
|
||||||
@Override
|
commentSelectedFiles.addActionListener({mvcGroup.controller.addComment()})
|
||||||
public void mouseReleased(MouseEvent e) {
|
sharedFilesMenu.add(commentSelectedFiles)
|
||||||
if (e.isPopupTrigger())
|
|
||||||
showPopupMenu(sharedFilesMenu, e)
|
def sharedFilesMouseListener = new MouseAdapter() {
|
||||||
}
|
@Override
|
||||||
@Override
|
public void mouseReleased(MouseEvent e) {
|
||||||
public void mousePressed(MouseEvent e) {
|
if (e.isPopupTrigger())
|
||||||
if (e.isPopupTrigger())
|
showPopupMenu(sharedFilesMenu, e)
|
||||||
showPopupMenu(sharedFilesMenu, e)
|
}
|
||||||
}
|
@Override
|
||||||
|
public void mousePressed(MouseEvent e) {
|
||||||
|
if (e.isPopupTrigger())
|
||||||
|
showPopupMenu(sharedFilesMenu, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// shared files table and tree
|
||||||
|
def sharedFilesTable = builder.getVariable("shared-files-table")
|
||||||
|
sharedFilesTable.columnModel.getColumn(1).setCellRenderer(new SizeRenderer())
|
||||||
|
|
||||||
|
sharedFilesTable.rowSorter.addRowSorterListener({evt -> lastSharedSortEvent = evt})
|
||||||
|
sharedFilesTable.rowSorter.setSortsOnUpdates(true)
|
||||||
|
|
||||||
|
sharedFilesTable.addMouseListener(sharedFilesMouseListener)
|
||||||
|
|
||||||
|
selectionModel = sharedFilesTable.getSelectionModel()
|
||||||
|
selectionModel.addListSelectionListener({
|
||||||
|
def selectedFiles = selectedSharedFiles()
|
||||||
|
if (selectedFiles == null || selectedFiles.isEmpty())
|
||||||
|
return
|
||||||
|
model.addCommentButtonEnabled = true
|
||||||
|
})
|
||||||
|
def sharedFilesTree = builder.getVariable("shared-files-tree")
|
||||||
|
sharedFilesTree.addMouseListener(sharedFilesMouseListener)
|
||||||
|
|
||||||
|
sharedFilesTree.addTreeSelectionListener({
|
||||||
|
def selectedNode = sharedFilesTree.getLastSelectedPathComponent()
|
||||||
|
model.addCommentButtonEnabled = selectedNode != null
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// searches table
|
// searches table
|
||||||
def searchesTable = builder.getVariable("searches-table")
|
def searchesTable = builder.getVariable("searches-table")
|
||||||
JPopupMenu searchTableMenu = new JPopupMenu()
|
JPopupMenu searchTableMenu = new JPopupMenu()
|
||||||
|
|
||||||
JMenuItem copySearchToClipboard = new JMenuItem("Copy search to clipboard")
|
JMenuItem copySearchToClipboard = new JMenuItem("Copy search to clipboard")
|
||||||
copySearchToClipboard.addActionListener({mvcGroup.view.copySearchToClipboard(searchesTable)})
|
copySearchToClipboard.addActionListener({mvcGroup.view.copySearchToClipboard(searchesTable)})
|
||||||
searchTableMenu.add(copySearchToClipboard)
|
JMenuItem trustSearcher = new JMenuItem("Trust searcher")
|
||||||
searchesTable.addMouseListener(new MouseAdapter() {
|
trustSearcher.addActionListener({mvcGroup.controller.trustPersonaFromSearch()})
|
||||||
@Override
|
JMenuItem distrustSearcher = new JMenuItem("Distrust searcher")
|
||||||
public void mouseReleased(MouseEvent e) {
|
distrustSearcher.addActionListener({mvcGroup.controller.distrustPersonaFromSearch()})
|
||||||
if (e.isPopupTrigger())
|
|
||||||
showPopupMenu(searchTableMenu, e)
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void mousePressed(MouseEvent e) {
|
|
||||||
if (e.isPopupTrigger())
|
|
||||||
showPopupMenu(searchTableMenu, e)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// watched directories table
|
searchTableMenu.add(copySearchToClipboard)
|
||||||
def watchedTable = builder.getVariable("watched-directories-table")
|
searchTableMenu.add(trustSearcher)
|
||||||
watchedTable.rowSorter.addRowSorterListener({evt -> lastWatchedSortEvent = evt})
|
searchTableMenu.add(distrustSearcher)
|
||||||
watchedTable.rowSorter.setSortsOnUpdates(true)
|
|
||||||
JPopupMenu watchedMenu = new JPopupMenu()
|
searchesTable.addMouseListener(new MouseAdapter() {
|
||||||
JMenuItem stopWatching = new JMenuItem("Stop sharing")
|
@Override
|
||||||
stopWatching.addActionListener({mvcGroup.controller.stopWatchingDirectory()})
|
public void mouseReleased(MouseEvent e) {
|
||||||
watchedMenu.add(stopWatching)
|
if (e.isPopupTrigger())
|
||||||
watchedTable.addMouseListener(new MouseAdapter() {
|
showPopupMenu(searchTableMenu, e)
|
||||||
@Override
|
}
|
||||||
public void mouseReleased(MouseEvent e) {
|
@Override
|
||||||
if (e.isPopupTrigger())
|
public void mousePressed(MouseEvent e) {
|
||||||
showPopupMenu(watchedMenu, e)
|
if (e.isPopupTrigger())
|
||||||
}
|
showPopupMenu(searchTableMenu, e)
|
||||||
@Override
|
}
|
||||||
public void mousePressed(MouseEvent e) {
|
})
|
||||||
if (e.isPopupTrigger())
|
|
||||||
showPopupMenu(watchedMenu, e)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// subscription table
|
// subscription table
|
||||||
def subscriptionTable = builder.getVariable("subscription-table")
|
def subscriptionTable = builder.getVariable("subscription-table")
|
||||||
@@ -523,20 +591,20 @@ class MainFrameView {
|
|||||||
switch(trustList.status) {
|
switch(trustList.status) {
|
||||||
case RemoteTrustList.Status.NEW:
|
case RemoteTrustList.Status.NEW:
|
||||||
case RemoteTrustList.Status.UPDATING:
|
case RemoteTrustList.Status.UPDATING:
|
||||||
model.reviewButtonEnabled = false
|
model.reviewButtonEnabled = false
|
||||||
model.updateButtonEnabled = false
|
model.updateButtonEnabled = false
|
||||||
model.unsubscribeButtonEnabled = false
|
model.unsubscribeButtonEnabled = false
|
||||||
break
|
break
|
||||||
case RemoteTrustList.Status.UPDATED:
|
case RemoteTrustList.Status.UPDATED:
|
||||||
model.reviewButtonEnabled = true
|
model.reviewButtonEnabled = true
|
||||||
model.updateButtonEnabled = true
|
model.updateButtonEnabled = true
|
||||||
model.unsubscribeButtonEnabled = true
|
model.unsubscribeButtonEnabled = true
|
||||||
break
|
break
|
||||||
case RemoteTrustList.Status.UPDATE_FAILED:
|
case RemoteTrustList.Status.UPDATE_FAILED:
|
||||||
model.reviewButtonEnabled = false
|
model.reviewButtonEnabled = false
|
||||||
model.updateButtonEnabled = true
|
model.updateButtonEnabled = true
|
||||||
model.unsubscribeButtonEnabled = true
|
model.unsubscribeButtonEnabled = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -575,28 +643,65 @@ class MainFrameView {
|
|||||||
model.markNeutralFromDistrustedButtonEnabled = true
|
model.markNeutralFromDistrustedButtonEnabled = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// show tree by default
|
||||||
|
showSharedFilesTree.call()
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void showPopupMenu(JPopupMenu menu, MouseEvent event) {
|
private static void showPopupMenu(JPopupMenu menu, MouseEvent event) {
|
||||||
menu.show(event.getComponent(), event.getX(), event.getY())
|
menu.show(event.getComponent(), event.getX(), event.getY())
|
||||||
}
|
}
|
||||||
|
|
||||||
def selectedSharedFile() {
|
def selectedSharedFiles() {
|
||||||
def sharedFilesTable = builder.getVariable("shared-files-table")
|
if (!model.treeVisible) {
|
||||||
int selected = sharedFilesTable.getSelectedRow()
|
def sharedFilesTable = builder.getVariable("shared-files-table")
|
||||||
if (selected < 0)
|
int[] selected = sharedFilesTable.getSelectedRows()
|
||||||
return null
|
if (selected.length == 0)
|
||||||
if (lastSharedSortEvent != null)
|
return null
|
||||||
selected = sharedFilesTable.rowSorter.convertRowIndexToModel(selected)
|
List<SharedFile> rv = new ArrayList<>()
|
||||||
model.shared[selected]
|
if (lastSharedSortEvent != null) {
|
||||||
|
for (int i = 0; i < selected.length; i ++) {
|
||||||
|
selected[i] = sharedFilesTable.rowSorter.convertRowIndexToModel(selected[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selected.each {
|
||||||
|
rv.add(model.shared[it])
|
||||||
|
}
|
||||||
|
return rv
|
||||||
|
} else {
|
||||||
|
def sharedFilesTree = builder.getVariable("shared-files-tree")
|
||||||
|
List<SharedFile> rv = new ArrayList<>()
|
||||||
|
for (TreePath path : sharedFilesTree.getSelectionPaths()) {
|
||||||
|
getLeafs(path.getLastPathComponent(), rv)
|
||||||
|
}
|
||||||
|
return rv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void getLeafs(TreeNode node, List<SharedFile> dest) {
|
||||||
|
if (node.isLeaf()) {
|
||||||
|
dest.add(node.getUserObject())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
def children = node.children()
|
||||||
|
while(children.hasMoreElements()) {
|
||||||
|
getLeafs(children.nextElement(), dest)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def copyHashToClipboard() {
|
def copyHashToClipboard() {
|
||||||
def selected = selectedSharedFile()
|
def selectedFiles = selectedSharedFiles()
|
||||||
if (selected == null)
|
if (selectedFiles == null)
|
||||||
return
|
return
|
||||||
String root = Base64.encode(selected.infoHash.getRoot())
|
String roots = ""
|
||||||
StringSelection selection = new StringSelection(root)
|
for (Iterator<SharedFile> iterator = selectedFiles.iterator(); iterator.hasNext(); ) {
|
||||||
|
SharedFile selected = iterator.next()
|
||||||
|
String root = Base64.encode(selected.infoHash.getRoot())
|
||||||
|
roots += root
|
||||||
|
if (iterator.hasNext())
|
||||||
|
roots += "\n"
|
||||||
|
}
|
||||||
|
StringSelection selection = new StringSelection(roots)
|
||||||
def clipboard = Toolkit.getDefaultToolkit().getSystemClipboard()
|
def clipboard = Toolkit.getDefaultToolkit().getSystemClipboard()
|
||||||
clipboard.setContents(selection, null)
|
clipboard.setContents(selection, null)
|
||||||
}
|
}
|
||||||
@@ -686,60 +791,80 @@ class MainFrameView {
|
|||||||
def showSearchWindow = {
|
def showSearchWindow = {
|
||||||
def cardsPanel = builder.getVariable("cards-panel")
|
def cardsPanel = builder.getVariable("cards-panel")
|
||||||
cardsPanel.getLayout().show(cardsPanel, "search window")
|
cardsPanel.getLayout().show(cardsPanel, "search window")
|
||||||
|
model.searchesPaneButtonEnabled = false
|
||||||
|
model.downloadsPaneButtonEnabled = true
|
||||||
|
model.uploadsPaneButtonEnabled = true
|
||||||
|
model.monitorPaneButtonEnabled = true
|
||||||
|
model.trustPaneButtonEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
def showDownloadsWindow = {
|
||||||
|
def cardsPanel = builder.getVariable("cards-panel")
|
||||||
|
cardsPanel.getLayout().show(cardsPanel, "downloads window")
|
||||||
|
model.searchesPaneButtonEnabled = true
|
||||||
|
model.downloadsPaneButtonEnabled = false
|
||||||
|
model.uploadsPaneButtonEnabled = true
|
||||||
|
model.monitorPaneButtonEnabled = true
|
||||||
|
model.trustPaneButtonEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
def showUploadsWindow = {
|
def showUploadsWindow = {
|
||||||
def cardsPanel = builder.getVariable("cards-panel")
|
def cardsPanel = builder.getVariable("cards-panel")
|
||||||
cardsPanel.getLayout().show(cardsPanel, "uploads window")
|
cardsPanel.getLayout().show(cardsPanel, "uploads window")
|
||||||
|
model.searchesPaneButtonEnabled = true
|
||||||
|
model.downloadsPaneButtonEnabled = true
|
||||||
|
model.uploadsPaneButtonEnabled = false
|
||||||
|
model.monitorPaneButtonEnabled = true
|
||||||
|
model.trustPaneButtonEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
def showMonitorWindow = {
|
def showMonitorWindow = {
|
||||||
def cardsPanel = builder.getVariable("cards-panel")
|
def cardsPanel = builder.getVariable("cards-panel")
|
||||||
cardsPanel.getLayout().show(cardsPanel,"monitor window")
|
cardsPanel.getLayout().show(cardsPanel,"monitor window")
|
||||||
|
model.searchesPaneButtonEnabled = true
|
||||||
|
model.downloadsPaneButtonEnabled = true
|
||||||
|
model.uploadsPaneButtonEnabled = true
|
||||||
|
model.monitorPaneButtonEnabled = false
|
||||||
|
model.trustPaneButtonEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
def showTrustWindow = {
|
def showTrustWindow = {
|
||||||
def cardsPanel = builder.getVariable("cards-panel")
|
def cardsPanel = builder.getVariable("cards-panel")
|
||||||
cardsPanel.getLayout().show(cardsPanel,"trust window")
|
cardsPanel.getLayout().show(cardsPanel,"trust window")
|
||||||
|
model.searchesPaneButtonEnabled = true
|
||||||
|
model.downloadsPaneButtonEnabled = true
|
||||||
|
model.uploadsPaneButtonEnabled = true
|
||||||
|
model.monitorPaneButtonEnabled = true
|
||||||
|
model.trustPaneButtonEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
def showSharedFilesTable = {
|
||||||
|
model.treeVisible = false
|
||||||
|
def cardsPanel = builder.getVariable("shared-files-panel")
|
||||||
|
cardsPanel.getLayout().show(cardsPanel, "shared files table")
|
||||||
|
}
|
||||||
|
|
||||||
|
def showSharedFilesTree = {
|
||||||
|
model.treeVisible = true
|
||||||
|
def cardsPanel = builder.getVariable("shared-files-panel")
|
||||||
|
cardsPanel.getLayout().show(cardsPanel, "shared files tree")
|
||||||
}
|
}
|
||||||
|
|
||||||
def shareFiles = {
|
def shareFiles = {
|
||||||
def chooser = new JFileChooser()
|
def chooser = new JFileChooser()
|
||||||
chooser.setFileHidingEnabled(false)
|
chooser.setFileHidingEnabled(!model.core.muOptions.shareHiddenFiles)
|
||||||
chooser.setDialogTitle("Select file to share")
|
chooser.setDialogTitle("Select file to share")
|
||||||
chooser.setFileSelectionMode(JFileChooser.FILES_ONLY)
|
chooser.setFileSelectionMode(JFileChooser.FILES_ONLY)
|
||||||
|
chooser.setMultiSelectionEnabled(true)
|
||||||
int rv = chooser.showOpenDialog(null)
|
int rv = chooser.showOpenDialog(null)
|
||||||
if (rv == JFileChooser.APPROVE_OPTION) {
|
if (rv == JFileChooser.APPROVE_OPTION) {
|
||||||
model.core.eventBus.publish(new FileSharedEvent(file : chooser.getSelectedFile()))
|
chooser.getSelectedFiles().each {
|
||||||
|
File canonical = it.getCanonicalFile()
|
||||||
|
model.core.eventBus.publish(new FileSharedEvent(file : canonical))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def watchDirectories = {
|
|
||||||
def chooser = new JFileChooser()
|
|
||||||
chooser.setFileHidingEnabled(false)
|
|
||||||
chooser.setDialogTitle("Select directory to watch")
|
|
||||||
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY)
|
|
||||||
int rv = chooser.showOpenDialog(null)
|
|
||||||
if (rv == JFileChooser.APPROVE_OPTION) {
|
|
||||||
File f = chooser.getSelectedFile()
|
|
||||||
model.watched << f.getAbsolutePath()
|
|
||||||
application.context.get("muwire-settings").watchedDirectories << f.getAbsolutePath()
|
|
||||||
mvcGroup.controller.saveMuWireSettings()
|
|
||||||
builder.getVariable("watched-directories-table").model.fireTableDataChanged()
|
|
||||||
model.core.eventBus.publish(new FileSharedEvent(file : f))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String getSelectedWatchedDirectory() {
|
|
||||||
def watchedTable = builder.getVariable("watched-directories-table")
|
|
||||||
int selectedRow = watchedTable.getSelectedRow()
|
|
||||||
if (selectedRow < 0)
|
|
||||||
return null
|
|
||||||
if (lastWatchedSortEvent != null)
|
|
||||||
selectedRow = watchedTable.rowSorter.convertRowIndexToModel(selectedRow)
|
|
||||||
model.watched[selectedRow]
|
|
||||||
}
|
|
||||||
|
|
||||||
int getSelectedTrustTablesRow(String tableName) {
|
int getSelectedTrustTablesRow(String tableName) {
|
||||||
def table = builder.getVariable(tableName)
|
def table = builder.getVariable(tableName)
|
||||||
int selectedRow = table.getSelectedRow()
|
int selectedRow = table.getSelectedRow()
|
||||||
@@ -749,4 +874,12 @@ class MainFrameView {
|
|||||||
selectedRow = table.rowSorter.convertRowIndexToModel(selectedRow)
|
selectedRow = table.rowSorter.convertRowIndexToModel(selectedRow)
|
||||||
selectedRow
|
selectedRow
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void refreshSharedFiles() {
|
||||||
|
def tree = builder.getVariable("shared-files-tree")
|
||||||
|
TreePath[] selectedPaths = tree.getSelectionPaths()
|
||||||
|
model.sharedTree.nodeStructureChanged(model.treeRoot)
|
||||||
|
tree.setSelectionPaths(selectedPaths)
|
||||||
|
builder.getVariable("shared-files-table").model.fireTableDataChanged()
|
||||||
|
}
|
||||||
}
|
}
|
@@ -12,6 +12,7 @@ import javax.swing.SwingConstants
|
|||||||
import com.muwire.core.Core
|
import com.muwire.core.Core
|
||||||
|
|
||||||
import java.awt.BorderLayout
|
import java.awt.BorderLayout
|
||||||
|
import java.awt.GridBagConstraints
|
||||||
import java.awt.event.WindowAdapter
|
import java.awt.event.WindowAdapter
|
||||||
import java.awt.event.WindowEvent
|
import java.awt.event.WindowEvent
|
||||||
|
|
||||||
@@ -35,6 +36,9 @@ class OptionsView {
|
|||||||
def updateField
|
def updateField
|
||||||
def autoDownloadUpdateCheckbox
|
def autoDownloadUpdateCheckbox
|
||||||
def shareDownloadedCheckbox
|
def shareDownloadedCheckbox
|
||||||
|
def shareHiddenCheckbox
|
||||||
|
def searchCommentsCheckbox
|
||||||
|
def browseFilesCheckbox
|
||||||
|
|
||||||
def inboundLengthField
|
def inboundLengthField
|
||||||
def inboundQuantityField
|
def inboundQuantityField
|
||||||
@@ -46,6 +50,7 @@ class OptionsView {
|
|||||||
def lnfField
|
def lnfField
|
||||||
def monitorCheckbox
|
def monitorCheckbox
|
||||||
def fontField
|
def fontField
|
||||||
|
def fontSizeField
|
||||||
def clearCancelledDownloadsCheckbox
|
def clearCancelledDownloadsCheckbox
|
||||||
def clearFinishedDownloadsCheckbox
|
def clearFinishedDownloadsCheckbox
|
||||||
def excludeLocalResultCheckbox
|
def excludeLocalResultCheckbox
|
||||||
@@ -55,6 +60,7 @@ class OptionsView {
|
|||||||
def outBwField
|
def outBwField
|
||||||
|
|
||||||
def allowUntrustedCheckbox
|
def allowUntrustedCheckbox
|
||||||
|
def searchExtraHopCheckbox
|
||||||
def allowTrustListsCheckbox
|
def allowTrustListsCheckbox
|
||||||
def trustListIntervalField
|
def trustListIntervalField
|
||||||
|
|
||||||
@@ -68,23 +74,32 @@ class OptionsView {
|
|||||||
d.setResizable(false)
|
d.setResizable(false)
|
||||||
p = builder.panel {
|
p = builder.panel {
|
||||||
gridBagLayout()
|
gridBagLayout()
|
||||||
label(text : "Retry failed downloads every", constraints : gbc(gridx: 0, gridy: 0))
|
label(text : "Search in comments", constraints:gbc(gridx: 0, gridy:0))
|
||||||
retryField = textField(text : bind { model.downloadRetryInterval }, columns : 2, constraints : gbc(gridx: 1, gridy: 0))
|
searchCommentsCheckbox = checkBox(selected : bind {model.searchComments}, constraints : gbc(gridx:1, gridy:0))
|
||||||
label(text : "minutes", constraints : gbc(gridx : 2, gridy: 0))
|
|
||||||
|
|
||||||
label(text : "Check for updates every", constraints : gbc(gridx : 0, gridy: 1))
|
label(text : "Retry failed downloads every", constraints : gbc(gridx: 0, gridy: 1))
|
||||||
updateField = textField(text : bind {model.updateCheckInterval }, columns : 2, constraints : gbc(gridx : 1, gridy: 1))
|
retryField = textField(text : bind { model.downloadRetryInterval }, columns : 2, constraints : gbc(gridx: 1, gridy: 1))
|
||||||
label(text : "hours", constraints : gbc(gridx: 2, gridy : 1))
|
label(text : "seconds", constraints : gbc(gridx : 2, gridy: 1))
|
||||||
|
|
||||||
label(text : "Download updates automatically", constraints: gbc(gridx :0, gridy : 2))
|
label(text : "Check for updates every", constraints : gbc(gridx : 0, gridy: 2))
|
||||||
autoDownloadUpdateCheckbox = checkBox(selected : bind {model.autoDownloadUpdate}, constraints : gbc(gridx:1, gridy : 2))
|
updateField = textField(text : bind {model.updateCheckInterval }, columns : 2, constraints : gbc(gridx : 1, gridy: 2))
|
||||||
|
label(text : "hours", constraints : gbc(gridx: 2, gridy : 2))
|
||||||
|
|
||||||
label(text : "Share downloaded files", constraints : gbc(gridx : 0, gridy:3))
|
label(text : "Download updates automatically", constraints: gbc(gridx :0, gridy : 3))
|
||||||
shareDownloadedCheckbox = checkBox(selected : bind {model.shareDownloadedFiles}, constraints : gbc(gridx :1, gridy:3))
|
autoDownloadUpdateCheckbox = checkBox(selected : bind {model.autoDownloadUpdate}, constraints : gbc(gridx:1, gridy : 3))
|
||||||
|
|
||||||
label(text : "Save downloaded files to:", constraints: gbc(gridx:0, gridy:4))
|
label(text : "Share downloaded files", constraints : gbc(gridx : 0, gridy:4))
|
||||||
button(text : "Choose", constraints : gbc(gridx : 1, gridy:4), downloadLocationAction)
|
shareDownloadedCheckbox = checkBox(selected : bind {model.shareDownloadedFiles}, constraints : gbc(gridx :1, gridy:4))
|
||||||
label(text : bind {model.downloadLocation}, constraints: gbc(gridx:0, gridy:5, gridwidth:2))
|
|
||||||
|
label(text : "Share hidden files", constraints : gbc(gridx : 0, gridy:5))
|
||||||
|
shareHiddenCheckbox = checkBox(selected : bind {model.shareHiddenFiles}, constraints : gbc(gridx :1, gridy:5))
|
||||||
|
|
||||||
|
label(text : "Allow browsing", constraints : gbc(gridx : 0, gridy : 6))
|
||||||
|
browseFilesCheckbox = checkBox(selected : bind {model.browseFiles}, constraints : gbc(gridx : 1, gridy : 6))
|
||||||
|
|
||||||
|
label(text : "Save downloaded files to:", constraints: gbc(gridx:0, gridy:7))
|
||||||
|
button(text : "Choose", constraints : gbc(gridx : 1, gridy:7), downloadLocationAction)
|
||||||
|
label(text : bind {model.downloadLocation}, constraints: gbc(gridx:0, gridy:8, gridwidth:2))
|
||||||
|
|
||||||
}
|
}
|
||||||
i = builder.panel {
|
i = builder.panel {
|
||||||
@@ -112,19 +127,28 @@ class OptionsView {
|
|||||||
gridBagLayout()
|
gridBagLayout()
|
||||||
label(text : "Changing these settings requires a restart", constraints : gbc(gridx : 0, gridy : 0, gridwidth: 2))
|
label(text : "Changing these settings requires a restart", constraints : gbc(gridx : 0, gridy : 0, gridwidth: 2))
|
||||||
label(text : "Look And Feel", constraints : gbc(gridx: 0, gridy:1))
|
label(text : "Look And Feel", constraints : gbc(gridx: 0, gridy:1))
|
||||||
lnfField = textField(text : bind {model.lnf}, columns : 4, constraints : gbc(gridx : 1, gridy : 1))
|
lnfField = textField(text : bind {model.lnf}, columns : 4, constraints : gbc(gridx : 1, gridy : 1, anchor : GridBagConstraints.LINE_START))
|
||||||
label(text : "Font", constraints : gbc(gridx: 0, gridy : 2))
|
label(text : "Font", constraints : gbc(gridx: 0, gridy : 2))
|
||||||
fontField = textField(text : bind {model.font}, columns : 4, constraints : gbc(gridx : 1, gridy:2))
|
fontField = textField(text : bind {model.font}, columns : 4, constraints : gbc(gridx : 1, gridy:2, anchor : GridBagConstraints.LINE_START))
|
||||||
// label(text : "Show Monitor", constraints : gbc(gridx :0, gridy: 3))
|
|
||||||
// monitorCheckbox = checkBox(selected : bind {model.showMonitor}, constraints : gbc(gridx : 1, gridy: 3))
|
label(text : "Font Size", constraints : gbc(gridx: 0, gridy : 3))
|
||||||
label(text : "Clear Cancelled Downloads", constraints: gbc(gridx: 0, gridy:4))
|
buttonGroup(id: "fontSizeGroup")
|
||||||
clearCancelledDownloadsCheckbox = checkBox(selected : bind {model.clearCancelledDownloads}, constraints : gbc(gridx : 1, gridy:4))
|
radioButton(text: "Automatic", selected : bind {model.automaticFontSize}, buttonGroup : fontSizeGroup,
|
||||||
label(text : "Clear Finished Downloads", constraints: gbc(gridx: 0, gridy:5))
|
constraints : gbc(gridx : 1, gridy: 3, anchor : GridBagConstraints.LINE_START), automaticFontAction)
|
||||||
clearFinishedDownloadsCheckbox = checkBox(selected : bind {model.clearFinishedDownloads}, constraints : gbc(gridx : 1, gridy:5))
|
radioButton(text: "Custom", selected : bind {!model.automaticFontSize}, buttonGroup : fontSizeGroup,
|
||||||
label(text : "Exclude Local Files From Results", constraints: gbc(gridx:0, gridy:6))
|
constraints : gbc(gridx : 1, gridy: 4, anchor : GridBagConstraints.LINE_START), customFontAction)
|
||||||
excludeLocalResultCheckbox = checkBox(selected : bind {model.excludeLocalResult}, constraints : gbc(gridx: 1, gridy : 6))
|
fontSizeField = textField(text : bind {model.customFontSize}, enabled : bind {!model.automaticFontSize}, constraints : gbc(gridx : 2, gridy : 4))
|
||||||
// label(text : "Show Hash Searches In Monitor", constraints: gbc(gridx:0, gridy:7))
|
|
||||||
// showSearchHashesCheckbox = checkBox(selected : bind {model.showSearchHashes}, constraints : gbc(gridx: 1, gridy: 7))
|
label(text : "Automatically Clear Cancelled Downloads", constraints: gbc(gridx: 0, gridy:5))
|
||||||
|
clearCancelledDownloadsCheckbox = checkBox(selected : bind {model.clearCancelledDownloads},
|
||||||
|
constraints : gbc(gridx : 1, gridy:5, anchor : GridBagConstraints.LINE_START))
|
||||||
|
label(text : "Automatically Clear Finished Downloads", constraints: gbc(gridx: 0, gridy:6))
|
||||||
|
clearFinishedDownloadsCheckbox = checkBox(selected : bind {model.clearFinishedDownloads},
|
||||||
|
constraints : gbc(gridx : 1, gridy:6, anchor : GridBagConstraints.LINE_START))
|
||||||
|
label(text : "Exclude Local Files From Results", constraints: gbc(gridx:0, gridy:7))
|
||||||
|
excludeLocalResultCheckbox = checkBox(selected : bind {model.excludeLocalResult},
|
||||||
|
constraints : gbc(gridx: 1, gridy : 7, anchor : GridBagConstraints.LINE_START))
|
||||||
|
|
||||||
}
|
}
|
||||||
bandwidth = builder.panel {
|
bandwidth = builder.panel {
|
||||||
gridBagLayout()
|
gridBagLayout()
|
||||||
@@ -138,11 +162,13 @@ class OptionsView {
|
|||||||
gridBagLayout()
|
gridBagLayout()
|
||||||
label(text : "Allow only trusted connections", constraints : gbc(gridx: 0, gridy : 0))
|
label(text : "Allow only trusted connections", constraints : gbc(gridx: 0, gridy : 0))
|
||||||
allowUntrustedCheckbox = checkBox(selected : bind {model.onlyTrusted}, constraints : gbc(gridx: 1, gridy : 0))
|
allowUntrustedCheckbox = checkBox(selected : bind {model.onlyTrusted}, constraints : gbc(gridx: 1, gridy : 0))
|
||||||
label(text : "Allow others to view my trust list", constraints : gbc(gridx: 0, gridy : 1))
|
label(text : "Search extra hop", constraints : gbc(gridx:0, gridy:1))
|
||||||
allowTrustListsCheckbox = checkBox(selected : bind {model.trustLists}, constraints : gbc(gridx: 1, gridy : 1))
|
searchExtraHopCheckbox = checkBox(selected : bind {model.searchExtraHop}, constraints : gbc(gridx: 1, gridy : 1))
|
||||||
label(text : "Update trust lists every ", constraints : gbc(gridx:0, gridy:2))
|
label(text : "Allow others to view my trust list", constraints : gbc(gridx: 0, gridy : 2))
|
||||||
trustListIntervalField = textField(text : bind {model.trustListInterval}, constraints:gbc(gridx:1, gridy:2))
|
allowTrustListsCheckbox = checkBox(selected : bind {model.trustLists}, constraints : gbc(gridx: 1, gridy : 2))
|
||||||
label(text : "hours", constraints : gbc(gridx: 2, gridy:2))
|
label(text : "Update trust lists every ", constraints : gbc(gridx:0, gridy:3))
|
||||||
|
trustListIntervalField = textField(text : bind {model.trustListInterval}, constraints:gbc(gridx:1, gridy:3))
|
||||||
|
label(text : "hours", constraints : gbc(gridx: 2, gridy:3))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
183
gui/griffon-app/views/com/muwire/gui/OptionsView.groovy.orig
Normal file
183
gui/griffon-app/views/com/muwire/gui/OptionsView.groovy.orig
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
package com.muwire.gui
|
||||||
|
|
||||||
|
import griffon.core.artifact.GriffonView
|
||||||
|
import griffon.inject.MVCMember
|
||||||
|
import griffon.metadata.ArtifactProviderFor
|
||||||
|
|
||||||
|
import javax.swing.JDialog
|
||||||
|
import javax.swing.JPanel
|
||||||
|
import javax.swing.JTabbedPane
|
||||||
|
import javax.swing.SwingConstants
|
||||||
|
|
||||||
|
import com.muwire.core.Core
|
||||||
|
|
||||||
|
import java.awt.BorderLayout
|
||||||
|
import java.awt.event.WindowAdapter
|
||||||
|
import java.awt.event.WindowEvent
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull
|
||||||
|
|
||||||
|
@ArtifactProviderFor(GriffonView)
|
||||||
|
class OptionsView {
|
||||||
|
@MVCMember @Nonnull
|
||||||
|
FactoryBuilderSupport builder
|
||||||
|
@MVCMember @Nonnull
|
||||||
|
OptionsModel model
|
||||||
|
|
||||||
|
def d
|
||||||
|
def p
|
||||||
|
def i
|
||||||
|
def u
|
||||||
|
def bandwidth
|
||||||
|
def trust
|
||||||
|
|
||||||
|
def retryField
|
||||||
|
def updateField
|
||||||
|
def autoDownloadUpdateCheckbox
|
||||||
|
def shareDownloadedCheckbox
|
||||||
|
|
||||||
|
def inboundLengthField
|
||||||
|
def inboundQuantityField
|
||||||
|
def outboundLengthField
|
||||||
|
def outboundQuantityField
|
||||||
|
def i2pUDPPortField
|
||||||
|
def i2pNTCPPortField
|
||||||
|
|
||||||
|
def lnfField
|
||||||
|
def monitorCheckbox
|
||||||
|
def fontField
|
||||||
|
def clearCancelledDownloadsCheckbox
|
||||||
|
def clearFinishedDownloadsCheckbox
|
||||||
|
def excludeLocalResultCheckbox
|
||||||
|
def showSearchHashesCheckbox
|
||||||
|
|
||||||
|
def inBwField
|
||||||
|
def outBwField
|
||||||
|
|
||||||
|
def allowUntrustedCheckbox
|
||||||
|
def allowTrustListsCheckbox
|
||||||
|
def trustListIntervalField
|
||||||
|
|
||||||
|
def buttonsPanel
|
||||||
|
|
||||||
|
def mainFrame
|
||||||
|
|
||||||
|
void initUI() {
|
||||||
|
mainFrame = application.windowManager.findWindow("main-frame")
|
||||||
|
d = new JDialog(mainFrame, "Options", true)
|
||||||
|
d.setResizable(false)
|
||||||
|
p = builder.panel {
|
||||||
|
gridBagLayout()
|
||||||
|
label(text : "Retry failed downloads every", constraints : gbc(gridx: 0, gridy: 0))
|
||||||
|
retryField = textField(text : bind { model.downloadRetryInterval }, columns : 2, constraints : gbc(gridx: 1, gridy: 0))
|
||||||
|
label(text : "minutes", constraints : gbc(gridx : 2, gridy: 0))
|
||||||
|
|
||||||
|
label(text : "Check for updates every", constraints : gbc(gridx : 0, gridy: 1))
|
||||||
|
updateField = textField(text : bind {model.updateCheckInterval }, columns : 2, constraints : gbc(gridx : 1, gridy: 1))
|
||||||
|
label(text : "hours", constraints : gbc(gridx: 2, gridy : 1))
|
||||||
|
|
||||||
|
label(text : "Download updates automatically", constraints: gbc(gridx :0, gridy : 2))
|
||||||
|
autoDownloadUpdateCheckbox = checkBox(selected : bind {model.autoDownloadUpdate}, constraints : gbc(gridx:1, gridy : 2))
|
||||||
|
|
||||||
|
label(text : "Share downloaded files", constraints : gbc(gridx : 0, gridy:3))
|
||||||
|
shareDownloadedCheckbox = checkBox(selected : bind {model.shareDownloadedFiles}, constraints : gbc(gridx :1, gridy:3))
|
||||||
|
|
||||||
|
label(text : "Save downloaded files to:", constraints: gbc(gridx:0, gridy:4))
|
||||||
|
button(text : "Choose", constraints : gbc(gridx : 1, gridy:4), downloadLocationAction)
|
||||||
|
label(text : bind {model.downloadLocation}, constraints: gbc(gridx:0, gridy:5, gridwidth:2))
|
||||||
|
|
||||||
|
}
|
||||||
|
i = builder.panel {
|
||||||
|
gridBagLayout()
|
||||||
|
label(text : "Changing these settings requires a restart", constraints : gbc(gridx : 0, gridy : 0, gridwidth: 2))
|
||||||
|
label(text : "Inbound Length", constraints : gbc(gridx:0, gridy:1))
|
||||||
|
inboundLengthField = textField(text : bind {model.inboundLength}, columns : 2, constraints : gbc(gridx:1, gridy:1))
|
||||||
|
label(text : "Inbound Quantity", constraints : gbc(gridx:0, gridy:2))
|
||||||
|
inboundQuantityField = textField(text : bind {model.inboundQuantity}, columns : 2, constraints : gbc(gridx:1, gridy:2))
|
||||||
|
label(text : "Outbound Length", constraints : gbc(gridx:0, gridy:3))
|
||||||
|
outboundLengthField = textField(text : bind {model.outboundLength}, columns : 2, constraints : gbc(gridx:1, gridy:3))
|
||||||
|
label(text : "Outbound Quantity", constraints : gbc(gridx:0, gridy:4))
|
||||||
|
outboundQuantityField = textField(text : bind {model.outboundQuantity}, columns : 2, constraints : gbc(gridx:1, gridy:4))
|
||||||
|
|
||||||
|
Core core = application.context.get("core")
|
||||||
|
if (core.router != null) {
|
||||||
|
label(text : "TCP Port", constraints : gbc(gridx :0, gridy: 5))
|
||||||
|
i2pNTCPPortField = textField(text : bind {model.i2pNTCPPort}, columns : 4, constraints : gbc(gridx:1, gridy:5))
|
||||||
|
label(text : "UDP Port", constraints : gbc(gridx :0, gridy: 6))
|
||||||
|
i2pUDPPortField = textField(text : bind {model.i2pUDPPort}, columns : 4, constraints : gbc(gridx:1, gridy:6))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
u = builder.panel {
|
||||||
|
gridBagLayout()
|
||||||
|
label(text : "Changing these settings requires a restart", constraints : gbc(gridx : 0, gridy : 0, gridwidth: 2))
|
||||||
|
label(text : "Look And Feel", constraints : gbc(gridx: 0, gridy:1))
|
||||||
|
lnfField = textField(text : bind {model.lnf}, columns : 4, constraints : gbc(gridx : 1, gridy : 1))
|
||||||
|
label(text : "Font", constraints : gbc(gridx: 0, gridy : 2))
|
||||||
|
fontField = textField(text : bind {model.font}, columns : 4, constraints : gbc(gridx : 1, gridy:2))
|
||||||
|
// label(text : "Show Monitor", constraints : gbc(gridx :0, gridy: 3))
|
||||||
|
// monitorCheckbox = checkBox(selected : bind {model.showMonitor}, constraints : gbc(gridx : 1, gridy: 3))
|
||||||
|
label(text : "Clear Cancelled Downloads", constraints: gbc(gridx: 0, gridy:4))
|
||||||
|
clearCancelledDownloadsCheckbox = checkBox(selected : bind {model.clearCancelledDownloads}, constraints : gbc(gridx : 1, gridy:4))
|
||||||
|
label(text : "Clear Finished Downloads", constraints: gbc(gridx: 0, gridy:5))
|
||||||
|
clearFinishedDownloadsCheckbox = checkBox(selected : bind {model.clearFinishedDownloads}, constraints : gbc(gridx : 1, gridy:5))
|
||||||
|
label(text : "Exclude Local Files From Results", constraints: gbc(gridx:0, gridy:6))
|
||||||
|
excludeLocalResultCheckbox = checkBox(selected : bind {model.excludeLocalResult}, constraints : gbc(gridx: 1, gridy : 6))
|
||||||
|
// label(text : "Show Hash Searches In Monitor", constraints: gbc(gridx:0, gridy:7))
|
||||||
|
// showSearchHashesCheckbox = checkBox(selected : bind {model.showSearchHashes}, constraints : gbc(gridx: 1, gridy: 7))
|
||||||
|
}
|
||||||
|
bandwidth = builder.panel {
|
||||||
|
gridBagLayout()
|
||||||
|
label(text : "Changing these settings requires a restart", constraints : gbc(gridx : 0, gridy : 0, gridwidth: 2))
|
||||||
|
label(text : "Inbound bandwidth (KB)", constraints : gbc(gridx: 0, gridy : 1))
|
||||||
|
inBwField = textField(text : bind {model.inBw}, columns : 3, constraints : gbc(gridx : 1, gridy : 1))
|
||||||
|
label(text : "Outbound bandwidth (KB)", constraints : gbc(gridx: 0, gridy : 2))
|
||||||
|
outBwField = textField(text : bind {model.outBw}, columns : 3, constraints : gbc(gridx : 1, gridy : 2))
|
||||||
|
}
|
||||||
|
trust = builder.panel {
|
||||||
|
gridBagLayout()
|
||||||
|
label(text : "Allow only trusted connections", constraints : gbc(gridx: 0, gridy : 0))
|
||||||
|
allowUntrustedCheckbox = checkBox(selected : bind {model.onlyTrusted}, constraints : gbc(gridx: 1, gridy : 0))
|
||||||
|
label(text : "Allow others to view my trust list", constraints : gbc(gridx: 0, gridy : 1))
|
||||||
|
allowTrustListsCheckbox = checkBox(selected : bind {model.trustLists}, constraints : gbc(gridx: 1, gridy : 1))
|
||||||
|
label(text : "Update trust lists every ", constraints : gbc(gridx:0, gridy:2))
|
||||||
|
trustListIntervalField = textField(text : bind {model.trustListInterval}, constraints:gbc(gridx:1, gridy:2))
|
||||||
|
label(text : "hours", constraints : gbc(gridx: 2, gridy:2))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
buttonsPanel = builder.panel {
|
||||||
|
gridBagLayout()
|
||||||
|
button(text : "Save", constraints : gbc(gridx : 1, gridy: 2), saveAction)
|
||||||
|
button(text : "Cancel", constraints : gbc(gridx : 2, gridy: 2), cancelAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void mvcGroupInit(Map<String,String> args) {
|
||||||
|
def tabbedPane = new JTabbedPane()
|
||||||
|
tabbedPane.addTab("MuWire", p)
|
||||||
|
tabbedPane.addTab("I2P", i)
|
||||||
|
tabbedPane.addTab("GUI", u)
|
||||||
|
Core core = application.context.get("core")
|
||||||
|
if (core.router != null) {
|
||||||
|
tabbedPane.addTab("Bandwidth", bandwidth)
|
||||||
|
}
|
||||||
|
tabbedPane.addTab("Trust", trust)
|
||||||
|
|
||||||
|
JPanel panel = new JPanel()
|
||||||
|
panel.setLayout(new BorderLayout())
|
||||||
|
panel.add(tabbedPane, BorderLayout.CENTER)
|
||||||
|
panel.add(buttonsPanel, BorderLayout.SOUTH)
|
||||||
|
|
||||||
|
d.getContentPane().add(panel)
|
||||||
|
d.pack()
|
||||||
|
d.setLocationRelativeTo(mainFrame)
|
||||||
|
d.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
|
||||||
|
d.addWindowListener(new WindowAdapter() {
|
||||||
|
public void windowClosed(WindowEvent e) {
|
||||||
|
mvcGroup.destroy()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
d.show()
|
||||||
|
}
|
||||||
|
}
|
@@ -11,15 +11,20 @@ import javax.swing.JComponent
|
|||||||
import javax.swing.JLabel
|
import javax.swing.JLabel
|
||||||
import javax.swing.JMenuItem
|
import javax.swing.JMenuItem
|
||||||
import javax.swing.JPopupMenu
|
import javax.swing.JPopupMenu
|
||||||
|
import javax.swing.JSplitPane
|
||||||
import javax.swing.JTable
|
import javax.swing.JTable
|
||||||
import javax.swing.ListSelectionModel
|
import javax.swing.ListSelectionModel
|
||||||
import javax.swing.SwingConstants
|
import javax.swing.SwingConstants
|
||||||
import javax.swing.table.DefaultTableCellRenderer
|
import javax.swing.table.DefaultTableCellRenderer
|
||||||
|
|
||||||
|
import com.muwire.core.Persona
|
||||||
|
import com.muwire.core.search.UIResultEvent
|
||||||
import com.muwire.core.util.DataUtil
|
import com.muwire.core.util.DataUtil
|
||||||
|
|
||||||
import java.awt.BorderLayout
|
import java.awt.BorderLayout
|
||||||
import java.awt.Color
|
import java.awt.Color
|
||||||
|
import java.awt.FlowLayout
|
||||||
|
import java.awt.GridBagConstraints
|
||||||
import java.awt.Toolkit
|
import java.awt.Toolkit
|
||||||
import java.awt.datatransfer.StringSelection
|
import java.awt.datatransfer.StringSelection
|
||||||
import java.awt.event.MouseAdapter
|
import java.awt.event.MouseAdapter
|
||||||
@@ -37,23 +42,72 @@ class SearchTabView {
|
|||||||
def pane
|
def pane
|
||||||
def parent
|
def parent
|
||||||
def searchTerms
|
def searchTerms
|
||||||
|
def sendersTable
|
||||||
|
def lastSendersSortEvent
|
||||||
def resultsTable
|
def resultsTable
|
||||||
def lastSortEvent
|
def lastSortEvent
|
||||||
|
def sequentialDownloadCheckbox
|
||||||
|
|
||||||
void initUI() {
|
void initUI() {
|
||||||
builder.with {
|
builder.with {
|
||||||
def resultsTable
|
def resultsTable
|
||||||
def pane = scrollPane {
|
def sendersTable
|
||||||
resultsTable = table(id : "results-table", autoCreateRowSorter : true) {
|
def sequentialDownloadCheckbox
|
||||||
tableModel(list: model.results) {
|
def pane = panel {
|
||||||
closureColumn(header: "Name", preferredWidth: 350, type: String, read : {row -> row.name.replace('<','_')})
|
gridLayout(rows :1, cols : 1)
|
||||||
closureColumn(header: "Size", preferredWidth: 20, type: Long, read : {row -> row.size})
|
splitPane(orientation: JSplitPane.VERTICAL_SPLIT, continuousLayout : true, dividerLocation: 300 ) {
|
||||||
closureColumn(header: "Direct Sources", preferredWidth: 50, type : Integer, read : { row -> model.hashBucket[row.infohash].size()})
|
panel {
|
||||||
closureColumn(header: "Possible Sources", preferredWidth : 50, type : Integer, read : {row -> model.sourcesBucket[row.infohash].size()})
|
borderLayout()
|
||||||
closureColumn(header: "Sender", preferredWidth: 170, type: String, read : {row -> row.sender.getHumanReadableName()})
|
scrollPane (constraints : BorderLayout.CENTER) {
|
||||||
closureColumn(header: "Trust", preferredWidth: 50, type: String, read : {row ->
|
sendersTable = table(id : "senders-table", autoCreateRowSorter : true) {
|
||||||
model.core.trustService.getLevel(row.sender.destination).toString()
|
tableModel(list : model.senders) {
|
||||||
})
|
closureColumn(header : "Sender", preferredWidth : 500, type: String, read : {row -> row.getHumanReadableName()})
|
||||||
|
closureColumn(header : "Results", preferredWidth : 20, type: Integer, read : {row -> model.sendersBucket[row].size()})
|
||||||
|
closureColumn(header : "Browse", preferredWidth : 20, type: Boolean, read : {row -> model.sendersBucket[row].first().browse})
|
||||||
|
closureColumn(header : "Trust", preferredWidth : 50, type: String, read : { row ->
|
||||||
|
model.core.trustService.getLevel(row.destination).toString()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panel(constraints : BorderLayout.SOUTH) {
|
||||||
|
gridLayout(rows: 1, cols : 2)
|
||||||
|
panel {
|
||||||
|
button(text : "Browse Host", enabled : bind {model.browseActionEnabled}, browseAction)
|
||||||
|
}
|
||||||
|
panel {
|
||||||
|
button(text : "Trust", enabled: bind {model.trustButtonsEnabled }, trustAction)
|
||||||
|
button(text : "Neutral", enabled: bind {model.trustButtonsEnabled}, neutralAction)
|
||||||
|
button(text : "Distrust", enabled : bind {model.trustButtonsEnabled}, distrustAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panel {
|
||||||
|
borderLayout()
|
||||||
|
scrollPane (constraints : BorderLayout.CENTER) {
|
||||||
|
resultsTable = table(id : "results-table", autoCreateRowSorter : true) {
|
||||||
|
tableModel(list: model.results) {
|
||||||
|
closureColumn(header: "Name", preferredWidth: 350, type: String, read : {row -> row.name.replace('<','_')})
|
||||||
|
closureColumn(header: "Size", preferredWidth: 20, type: Long, read : {row -> row.size})
|
||||||
|
closureColumn(header: "Direct Sources", preferredWidth: 50, type : Integer, read : { row -> model.hashBucket[row.infohash].size()})
|
||||||
|
closureColumn(header: "Possible Sources", preferredWidth : 50, type : Integer, read : {row -> model.sourcesBucket[row.infohash].size()})
|
||||||
|
closureColumn(header: "Comments", preferredWidth: 20, type: Boolean, read : {row -> row.comment != null})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panel(constraints : BorderLayout.SOUTH) {
|
||||||
|
gridLayout(rows: 1, cols: 3)
|
||||||
|
panel()
|
||||||
|
panel {
|
||||||
|
button(text : "Download", enabled : bind {model.downloadActionEnabled}, downloadAction)
|
||||||
|
}
|
||||||
|
panel {
|
||||||
|
gridBagLayout()
|
||||||
|
panel (constraints : gbc(gridx : 0, gridy : 0, weightx : 100))
|
||||||
|
sequentialDownloadCheckbox = checkBox(constraints : gbc(gridx : 1, gridy: 0, weightx : 0),selected : false, enabled : bind {model.downloadActionEnabled})
|
||||||
|
label(constraints: gbc(gridx: 2, gridy: 0, weightx : 0),text : "Download sequentially")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,17 +117,27 @@ class SearchTabView {
|
|||||||
this.pane.putClientProperty("results-table",resultsTable)
|
this.pane.putClientProperty("results-table",resultsTable)
|
||||||
|
|
||||||
this.resultsTable = resultsTable
|
this.resultsTable = resultsTable
|
||||||
|
this.sendersTable = sendersTable
|
||||||
|
this.sequentialDownloadCheckbox = sequentialDownloadCheckbox
|
||||||
|
|
||||||
def selectionModel = resultsTable.getSelectionModel()
|
def selectionModel = resultsTable.getSelectionModel()
|
||||||
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
|
selectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
|
||||||
selectionModel.addListSelectionListener( {
|
selectionModel.addListSelectionListener( {
|
||||||
int row = resultsTable.getSelectedRow()
|
int[] rows = resultsTable.getSelectedRows()
|
||||||
if (row < 0)
|
if (rows.length == 0) {
|
||||||
|
model.downloadActionEnabled = false
|
||||||
return
|
return
|
||||||
if (lastSortEvent != null)
|
}
|
||||||
row = resultsTable.rowSorter.convertRowIndexToModel(row)
|
if (lastSortEvent != null) {
|
||||||
mvcGroup.parentGroup.model.trustButtonsEnabled = true
|
for (int i = 0; i < rows.length; i ++) {
|
||||||
mvcGroup.parentGroup.model.downloadActionEnabled = mvcGroup.parentGroup.model.canDownload(model.results[row].infohash)
|
rows[i] = resultsTable.rowSorter.convertRowIndexToModel(rows[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
boolean downloadActionEnabled = true
|
||||||
|
rows.each {
|
||||||
|
downloadActionEnabled &= mvcGroup.parentGroup.model.canDownload(model.results[it].infohash)
|
||||||
|
}
|
||||||
|
model.downloadActionEnabled = downloadActionEnabled
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,12 +162,11 @@ class SearchTabView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
parent.setTabComponentAt(index, tabPanel)
|
parent.setTabComponentAt(index, tabPanel)
|
||||||
|
mvcGroup.parentGroup.view.showSearchWindow.call()
|
||||||
|
|
||||||
def centerRenderer = new DefaultTableCellRenderer()
|
def centerRenderer = new DefaultTableCellRenderer()
|
||||||
centerRenderer.setHorizontalAlignment(JLabel.CENTER)
|
centerRenderer.setHorizontalAlignment(JLabel.CENTER)
|
||||||
resultsTable.columnModel.getColumn(1).setCellRenderer(centerRenderer)
|
|
||||||
resultsTable.setDefaultRenderer(Integer.class,centerRenderer)
|
resultsTable.setDefaultRenderer(Integer.class,centerRenderer)
|
||||||
resultsTable.columnModel.getColumn(4).setCellRenderer(centerRenderer)
|
|
||||||
|
|
||||||
resultsTable.columnModel.getColumn(1).setCellRenderer(new SizeRenderer())
|
resultsTable.columnModel.getColumn(1).setCellRenderer(new SizeRenderer())
|
||||||
|
|
||||||
@@ -118,7 +181,7 @@ class SearchTabView {
|
|||||||
if (e.button == MouseEvent.BUTTON3)
|
if (e.button == MouseEvent.BUTTON3)
|
||||||
showPopupMenu(e)
|
showPopupMenu(e)
|
||||||
else if (e.button == MouseEvent.BUTTON1 && e.clickCount == 2)
|
else if (e.button == MouseEvent.BUTTON1 && e.clickCount == 2)
|
||||||
mvcGroup.parentGroup.controller.download()
|
mvcGroup.controller.download()
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void mouseReleased(MouseEvent e) {
|
public void mouseReleased(MouseEvent e) {
|
||||||
@@ -126,38 +189,123 @@ class SearchTabView {
|
|||||||
showPopupMenu(e)
|
showPopupMenu(e)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// senders table
|
||||||
|
sendersTable.setDefaultRenderer(Integer.class, centerRenderer)
|
||||||
|
sendersTable.rowSorter.addRowSorterListener({evt -> lastSendersSortEvent = evt})
|
||||||
|
sendersTable.rowSorter.setSortsOnUpdates(true)
|
||||||
|
def selectionModel = sendersTable.getSelectionModel()
|
||||||
|
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
|
||||||
|
selectionModel.addListSelectionListener({
|
||||||
|
int row = selectedSenderRow()
|
||||||
|
if (row < 0) {
|
||||||
|
model.trustButtonsEnabled = false
|
||||||
|
model.browseActionEnabled = false
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
Persona sender = model.senders[row]
|
||||||
|
model.browseActionEnabled = model.sendersBucket[sender].first().browse
|
||||||
|
model.trustButtonsEnabled = true
|
||||||
|
model.results.clear()
|
||||||
|
model.results.addAll(model.sendersBucket[sender])
|
||||||
|
resultsTable.model.fireTableDataChanged()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def closeTab = {
|
def closeTab = {
|
||||||
int index = parent.indexOfTab(searchTerms)
|
int index = parent.indexOfTab(searchTerms)
|
||||||
parent.removeTabAt(index)
|
parent.removeTabAt(index)
|
||||||
mvcGroup.parentGroup.model.trustButtonsEnabled = false
|
model.trustButtonsEnabled = false
|
||||||
mvcGroup.parentGroup.model.downloadActionEnabled = false
|
model.downloadActionEnabled = false
|
||||||
mvcGroup.destroy()
|
mvcGroup.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
def showPopupMenu(MouseEvent e) {
|
def showPopupMenu(MouseEvent e) {
|
||||||
JPopupMenu menu = new JPopupMenu()
|
JPopupMenu menu = new JPopupMenu()
|
||||||
if (mvcGroup.parentGroup.model.downloadActionEnabled) {
|
boolean showMenu = false
|
||||||
|
if (model.downloadActionEnabled) {
|
||||||
JMenuItem download = new JMenuItem("Download")
|
JMenuItem download = new JMenuItem("Download")
|
||||||
download.addActionListener({mvcGroup.parentGroup.controller.download()})
|
download.addActionListener({mvcGroup.controller.download()})
|
||||||
menu.add(download)
|
menu.add(download)
|
||||||
|
showMenu = true
|
||||||
}
|
}
|
||||||
JMenuItem copyHashToClipboard = new JMenuItem("Copy hash to clipboard")
|
if (resultsTable.getSelectedRows().length == 1) {
|
||||||
copyHashToClipboard.addActionListener({mvcGroup.view.copyHashToClipboard()})
|
JMenuItem copyHashToClipboard = new JMenuItem("Copy hash to clipboard")
|
||||||
menu.add(copyHashToClipboard)
|
copyHashToClipboard.addActionListener({mvcGroup.view.copyHashToClipboard()})
|
||||||
menu.show(e.getComponent(), e.getX(), e.getY())
|
menu.add(copyHashToClipboard)
|
||||||
|
JMenuItem copyNameToClipboard = new JMenuItem("Copy name to clipboard")
|
||||||
|
copyNameToClipboard.addActionListener({mvcGroup.view.copyNameToClipboard()})
|
||||||
|
menu.add(copyNameToClipboard)
|
||||||
|
showMenu = true
|
||||||
|
|
||||||
|
// show comment if any
|
||||||
|
int selectedRow = resultsTable.getSelectedRow()
|
||||||
|
if (lastSortEvent != null)
|
||||||
|
selectedRow = resultsTable.rowSorter.convertRowIndexToModel(selectedRow)
|
||||||
|
if (model.results[selectedRow].comment != null) {
|
||||||
|
JMenuItem showComment = new JMenuItem("Show Comment")
|
||||||
|
showComment.addActionListener({mvcGroup.view.showComment()})
|
||||||
|
menu.add(showComment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (showMenu)
|
||||||
|
menu.show(e.getComponent(), e.getX(), e.getY())
|
||||||
|
}
|
||||||
|
|
||||||
|
private UIResultEvent getSelectedResult() {
|
||||||
|
int[] selectedRows = resultsTable.getSelectedRows()
|
||||||
|
if (selectedRows.length != 1)
|
||||||
|
return null
|
||||||
|
int selected = selectedRows[0]
|
||||||
|
if (lastSortEvent != null)
|
||||||
|
selected = resultsTable.rowSorter.convertRowIndexToModel(selected)
|
||||||
|
model.results[selected]
|
||||||
}
|
}
|
||||||
|
|
||||||
def copyHashToClipboard() {
|
def copyHashToClipboard() {
|
||||||
int selected = resultsTable.getSelectedRow()
|
def result = getSelectedResult()
|
||||||
if (selected < 0)
|
if (result == null)
|
||||||
return
|
return
|
||||||
if (lastSortEvent != null)
|
String hash = Base64.encode(result.infohash.getRoot())
|
||||||
selected = resultsTable.rowSorter.convertRowIndexToModel(selected)
|
|
||||||
String hash = Base64.encode(model.results[selected].infohash.getRoot())
|
|
||||||
StringSelection selection = new StringSelection(hash)
|
StringSelection selection = new StringSelection(hash)
|
||||||
def clipboard = Toolkit.getDefaultToolkit().getSystemClipboard()
|
def clipboard = Toolkit.getDefaultToolkit().getSystemClipboard()
|
||||||
clipboard.setContents(selection, null)
|
clipboard.setContents(selection, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def copyNameToClipboard() {
|
||||||
|
def result = getSelectedResult()
|
||||||
|
if (result == null)
|
||||||
|
return
|
||||||
|
StringSelection selection = new StringSelection(result.getName())
|
||||||
|
def clipboard = Toolkit.getDefaultToolkit().getSystemClipboard()
|
||||||
|
clipboard.setContents(selection, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
def showComment() {
|
||||||
|
int selectedRow = resultsTable.getSelectedRow()
|
||||||
|
if (selectedRow < 0)
|
||||||
|
return
|
||||||
|
if (lastSortEvent != null)
|
||||||
|
selectedRow = resultsTable.rowSorter.convertRowIndexToModel(selectedRow)
|
||||||
|
UIResultEvent event = model.results[selectedRow]
|
||||||
|
if (event.comment == null)
|
||||||
|
return
|
||||||
|
|
||||||
|
String groupId = Base64.encode(event.infohash.getRoot())
|
||||||
|
Map<String,Object> params = new HashMap<>()
|
||||||
|
params['result'] = event
|
||||||
|
|
||||||
|
mvcGroup.createMVCGroup("show-comment", groupId, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
int selectedSenderRow() {
|
||||||
|
int row = sendersTable.getSelectedRow()
|
||||||
|
if (row < 0)
|
||||||
|
return -1
|
||||||
|
if (lastSendersSortEvent != null)
|
||||||
|
row = sendersTable.rowSorter.convertRowIndexToModel(row)
|
||||||
|
row
|
||||||
|
}
|
||||||
}
|
}
|
61
gui/griffon-app/views/com/muwire/gui/ShowCommentView.groovy
Normal file
61
gui/griffon-app/views/com/muwire/gui/ShowCommentView.groovy
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package com.muwire.gui
|
||||||
|
|
||||||
|
import griffon.core.artifact.GriffonView
|
||||||
|
import griffon.inject.MVCMember
|
||||||
|
import griffon.metadata.ArtifactProviderFor
|
||||||
|
import net.i2p.data.Base64
|
||||||
|
|
||||||
|
import javax.swing.JDialog
|
||||||
|
import javax.swing.SwingConstants
|
||||||
|
|
||||||
|
import com.muwire.core.util.DataUtil
|
||||||
|
|
||||||
|
import java.awt.BorderLayout
|
||||||
|
import java.awt.event.WindowAdapter
|
||||||
|
import java.awt.event.WindowEvent
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull
|
||||||
|
|
||||||
|
@ArtifactProviderFor(GriffonView)
|
||||||
|
class ShowCommentView {
|
||||||
|
@MVCMember @Nonnull
|
||||||
|
FactoryBuilderSupport builder
|
||||||
|
@MVCMember @Nonnull
|
||||||
|
ShowCommentModel model
|
||||||
|
|
||||||
|
def mainFrame
|
||||||
|
def dialog
|
||||||
|
def p
|
||||||
|
|
||||||
|
void initUI() {
|
||||||
|
mainFrame = application.windowManager.findWindow("main-frame")
|
||||||
|
dialog = new JDialog(mainFrame, model.result.name, true)
|
||||||
|
dialog.setResizable(true)
|
||||||
|
|
||||||
|
|
||||||
|
p = builder.panel {
|
||||||
|
borderLayout()
|
||||||
|
panel (constraints : BorderLayout.CENTER) {
|
||||||
|
scrollPane {
|
||||||
|
textArea(text : model.result.comment, rows : 20, columns : 100, editable : false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panel (constraints : BorderLayout.SOUTH) {
|
||||||
|
button(text : "Dismiss", dismissAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void mvcGroupInit(Map<String,String> args) {
|
||||||
|
dialog.getContentPane().add(p)
|
||||||
|
dialog.pack()
|
||||||
|
dialog.setLocationRelativeTo(mainFrame)
|
||||||
|
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
|
||||||
|
dialog.addWindowListener( new WindowAdapter() {
|
||||||
|
public void windowClosed(WindowEvent e) {
|
||||||
|
mvcGroup.destroy()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
dialog.show()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,25 @@
|
|||||||
|
package com.muwire.gui
|
||||||
|
|
||||||
|
import griffon.core.test.GriffonFestRule
|
||||||
|
import org.fest.swing.fixture.FrameFixture
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
import static org.junit.Assert.fail
|
||||||
|
|
||||||
|
class ContentPanelIntegrationTest {
|
||||||
|
static {
|
||||||
|
System.setProperty('griffon.swing.edt.violations.check', 'true')
|
||||||
|
System.setProperty('griffon.swing.edt.hang.monitor', 'true')
|
||||||
|
}
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final GriffonFestRule fest = new GriffonFestRule()
|
||||||
|
|
||||||
|
private FrameFixture window
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void smokeTest() {
|
||||||
|
fail('Not implemented yet!')
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,39 @@
|
|||||||
|
package com.muwire.gui
|
||||||
|
|
||||||
|
import javax.swing.JComponent
|
||||||
|
import javax.swing.JLabel
|
||||||
|
import javax.swing.JTable
|
||||||
|
import javax.swing.table.DefaultTableCellRenderer
|
||||||
|
|
||||||
|
import com.muwire.core.download.Downloader
|
||||||
|
|
||||||
|
import net.i2p.data.DataHelper
|
||||||
|
|
||||||
|
class DownloadProgressRenderer extends DefaultTableCellRenderer {
|
||||||
|
DownloadProgressRenderer() {
|
||||||
|
setHorizontalAlignment(JLabel.CENTER)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
JComponent getTableCellRendererComponent(JTable table, Object value,
|
||||||
|
boolean isSelected, boolean hasFocus, int row, int column) {
|
||||||
|
Downloader d = (Downloader) value
|
||||||
|
int pieces = d.nPieces
|
||||||
|
int done = d.donePieces()
|
||||||
|
int percent = -1
|
||||||
|
if (pieces != 0)
|
||||||
|
percent = (done * 100 / pieces)
|
||||||
|
String totalSize = DataHelper.formatSize2Decimal(d.length, false) + "B"
|
||||||
|
setText(String.format("%2d", percent) + "% of ${totalSize}".toString())
|
||||||
|
|
||||||
|
if (isSelected) {
|
||||||
|
setForeground(table.getSelectionForeground())
|
||||||
|
setBackground(table.getSelectionBackground())
|
||||||
|
} else {
|
||||||
|
setForeground(table.getForeground())
|
||||||
|
setBackground(table.getBackground())
|
||||||
|
}
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,13 @@
|
|||||||
|
package com.muwire.gui
|
||||||
|
|
||||||
|
import com.muwire.core.download.Downloader
|
||||||
|
|
||||||
|
class DownloaderComparator implements Comparator<Downloader>{
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(Downloader o1, Downloader o2) {
|
||||||
|
double d1 = o1.donePieces() * 1.0 / o1.nPieces
|
||||||
|
double d2 = o2.donePieces() * 1.0 / o2.nPieces
|
||||||
|
return Double.compare(d1, d2);
|
||||||
|
}
|
||||||
|
}
|
18
gui/src/main/groovy/com/muwire/gui/InterimTreeNode.groovy
Normal file
18
gui/src/main/groovy/com/muwire/gui/InterimTreeNode.groovy
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package com.muwire.gui
|
||||||
|
|
||||||
|
class InterimTreeNode {
|
||||||
|
private final File file
|
||||||
|
InterimTreeNode(File file) {
|
||||||
|
this.file = file
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (!(o instanceof InterimTreeNode))
|
||||||
|
return false
|
||||||
|
file == o.file
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
file.getName()
|
||||||
|
}
|
||||||
|
}
|
44
gui/src/main/groovy/com/muwire/gui/SharedTreeRenderer.groovy
Normal file
44
gui/src/main/groovy/com/muwire/gui/SharedTreeRenderer.groovy
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package com.muwire.gui
|
||||||
|
|
||||||
|
import java.awt.Component
|
||||||
|
|
||||||
|
import javax.swing.ImageIcon
|
||||||
|
import javax.swing.JTree
|
||||||
|
import javax.swing.tree.DefaultTreeCellRenderer
|
||||||
|
|
||||||
|
import com.muwire.core.SharedFile
|
||||||
|
|
||||||
|
import net.i2p.data.DataHelper
|
||||||
|
|
||||||
|
class SharedTreeRenderer extends DefaultTreeCellRenderer {
|
||||||
|
|
||||||
|
private final ImageIcon commentIcon
|
||||||
|
|
||||||
|
SharedTreeRenderer() {
|
||||||
|
commentIcon = new ImageIcon((URL) SharedTreeRenderer.class.getResource("/comment.png"))
|
||||||
|
}
|
||||||
|
|
||||||
|
public Component getTreeCellRendererComponent(JTree tree, Object value,
|
||||||
|
boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
|
||||||
|
|
||||||
|
def userObject = value.getUserObject()
|
||||||
|
def defaultRenderer = super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus)
|
||||||
|
if (userObject instanceof InterimTreeNode || userObject == null)
|
||||||
|
return defaultRenderer
|
||||||
|
|
||||||
|
|
||||||
|
SharedFile sf = (SharedFile) userObject
|
||||||
|
String name = sf.getFile().getName()
|
||||||
|
long length = sf.getCachedLength()
|
||||||
|
String formatted = DataHelper.formatSize2Decimal(length, false)+"B"
|
||||||
|
|
||||||
|
|
||||||
|
setText("$name ($formatted)")
|
||||||
|
setEnabled(true)
|
||||||
|
if (sf.comment != null) {
|
||||||
|
setIcon(commentIcon)
|
||||||
|
}
|
||||||
|
|
||||||
|
this
|
||||||
|
}
|
||||||
|
}
|
@@ -5,6 +5,8 @@ class UISettings {
|
|||||||
String lnf
|
String lnf
|
||||||
boolean showMonitor
|
boolean showMonitor
|
||||||
String font
|
String font
|
||||||
|
boolean autoFontSize
|
||||||
|
int fontSize
|
||||||
boolean clearCancelledDownloads
|
boolean clearCancelledDownloads
|
||||||
boolean clearFinishedDownloads
|
boolean clearFinishedDownloads
|
||||||
boolean excludeLocalResult
|
boolean excludeLocalResult
|
||||||
@@ -18,6 +20,8 @@ class UISettings {
|
|||||||
clearFinishedDownloads = Boolean.parseBoolean(props.getProperty("clearFinishedDownloads","false"))
|
clearFinishedDownloads = Boolean.parseBoolean(props.getProperty("clearFinishedDownloads","false"))
|
||||||
excludeLocalResult = Boolean.parseBoolean(props.getProperty("excludeLocalResult","true"))
|
excludeLocalResult = Boolean.parseBoolean(props.getProperty("excludeLocalResult","true"))
|
||||||
showSearchHashes = Boolean.parseBoolean(props.getProperty("showSearchHashes","true"))
|
showSearchHashes = Boolean.parseBoolean(props.getProperty("showSearchHashes","true"))
|
||||||
|
autoFontSize = Boolean.parseBoolean(props.getProperty("autoFontSize","false"))
|
||||||
|
fontSize = Integer.parseInt(props.getProperty("fontSize","12"))
|
||||||
}
|
}
|
||||||
|
|
||||||
void write(OutputStream out) throws IOException {
|
void write(OutputStream out) throws IOException {
|
||||||
@@ -28,6 +32,8 @@ class UISettings {
|
|||||||
props.setProperty("clearFinishedDownloads", String.valueOf(clearFinishedDownloads))
|
props.setProperty("clearFinishedDownloads", String.valueOf(clearFinishedDownloads))
|
||||||
props.setProperty("excludeLocalResult", String.valueOf(excludeLocalResult))
|
props.setProperty("excludeLocalResult", String.valueOf(excludeLocalResult))
|
||||||
props.setProperty("showSearchHashes", String.valueOf(showSearchHashes))
|
props.setProperty("showSearchHashes", String.valueOf(showSearchHashes))
|
||||||
|
props.setProperty("autoFontSize", String.valueOf(autoFontSize))
|
||||||
|
props.setProperty("fontSize", String.valueOf(fontSize))
|
||||||
if (font != null)
|
if (font != null)
|
||||||
props.setProperty("font", font)
|
props.setProperty("font", font)
|
||||||
|
|
||||||
|
@@ -1,21 +0,0 @@
|
|||||||
package com.muwire.gui
|
|
||||||
|
|
||||||
import griffon.core.test.GriffonUnitRule
|
|
||||||
import griffon.core.test.TestFor
|
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
import static org.junit.Assert.fail
|
|
||||||
|
|
||||||
@TestFor(EventListController)
|
|
||||||
class EventListControllerTest {
|
|
||||||
private EventListController controller
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public final GriffonUnitRule griffon = new GriffonUnitRule()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void smokeTest() {
|
|
||||||
fail('Not yet implemented!')
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,21 +0,0 @@
|
|||||||
package com.muwire.gui
|
|
||||||
|
|
||||||
import griffon.core.test.GriffonUnitRule
|
|
||||||
import griffon.core.test.TestFor
|
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
import static org.junit.Assert.fail
|
|
||||||
|
|
||||||
@TestFor(I2PStatusController)
|
|
||||||
class I2PStatusControllerTest {
|
|
||||||
private I2PStatusController controller
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public final GriffonUnitRule griffon = new GriffonUnitRule()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void smokeTest() {
|
|
||||||
fail('Not yet implemented!')
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,21 +0,0 @@
|
|||||||
package com.muwire.gui
|
|
||||||
|
|
||||||
import griffon.core.test.GriffonUnitRule
|
|
||||||
import griffon.core.test.TestFor
|
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
import static org.junit.Assert.fail
|
|
||||||
|
|
||||||
@TestFor(MainFrameController)
|
|
||||||
class MainFrameControllerTest {
|
|
||||||
private MainFrameController controller
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public final GriffonUnitRule griffon = new GriffonUnitRule()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void smokeTest() {
|
|
||||||
fail('Not yet implemented!')
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,21 +0,0 @@
|
|||||||
package com.muwire.gui
|
|
||||||
|
|
||||||
import griffon.core.test.GriffonUnitRule
|
|
||||||
import griffon.core.test.TestFor
|
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
import static org.junit.Assert.fail
|
|
||||||
|
|
||||||
@TestFor(MuWireStatusController)
|
|
||||||
class MuWireStatusControllerTest {
|
|
||||||
private MuWireStatusController controller
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public final GriffonUnitRule griffon = new GriffonUnitRule()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void smokeTest() {
|
|
||||||
fail('Not yet implemented!')
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,21 +0,0 @@
|
|||||||
package com.muwire.gui
|
|
||||||
|
|
||||||
import griffon.core.test.GriffonUnitRule
|
|
||||||
import griffon.core.test.TestFor
|
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
import static org.junit.Assert.fail
|
|
||||||
|
|
||||||
@TestFor(OptionsController)
|
|
||||||
class OptionsControllerTest {
|
|
||||||
private OptionsController controller
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public final GriffonUnitRule griffon = new GriffonUnitRule()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void smokeTest() {
|
|
||||||
fail('Not yet implemented!')
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,21 +0,0 @@
|
|||||||
package com.muwire.gui
|
|
||||||
|
|
||||||
import griffon.core.test.GriffonUnitRule
|
|
||||||
import griffon.core.test.TestFor
|
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
import static org.junit.Assert.fail
|
|
||||||
|
|
||||||
@TestFor(SearchTabController)
|
|
||||||
class SearchTabControllerTest {
|
|
||||||
private SearchTabController controller
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public final GriffonUnitRule griffon = new GriffonUnitRule()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void smokeTest() {
|
|
||||||
fail('Not yet implemented!')
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,21 +0,0 @@
|
|||||||
package com.muwire.gui
|
|
||||||
|
|
||||||
import griffon.core.test.GriffonUnitRule
|
|
||||||
import griffon.core.test.TestFor
|
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
import static org.junit.Assert.fail
|
|
||||||
|
|
||||||
@TestFor(TrustListController)
|
|
||||||
class TrustListControllerTest {
|
|
||||||
private TrustListController controller
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public final GriffonUnitRule griffon = new GriffonUnitRule()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void smokeTest() {
|
|
||||||
fail('Not yet implemented!')
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,3 +1,8 @@
|
|||||||
apply plugin : 'application'
|
apply plugin : 'application'
|
||||||
mainClassName = 'com.muwire.hostcache.HostCache'
|
mainClassName = 'com.muwire.hostcache.HostCache'
|
||||||
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties']
|
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties']
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
testCompile 'org.junit.jupiter:junit-jupiter-api:5.4.2'
|
||||||
|
testCompile 'junit:junit:4.12'
|
||||||
|
}
|
||||||
|
123
plug/build.gradle
Normal file
123
plug/build.gradle
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
buildscript {
|
||||||
|
repositories { mavenCentral() }
|
||||||
|
dependencies {
|
||||||
|
classpath fileTree("../i2pjars") { include '*.jar' }
|
||||||
|
classpath 'gnu.getopt:java-getopt:1.0.13'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin : 'java'
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile project(":webui")
|
||||||
|
}
|
||||||
|
|
||||||
|
def buildDir = new File("$buildDir")
|
||||||
|
def zipDir = new File(buildDir, "zip")
|
||||||
|
def libDir = new File(zipDir, "lib")
|
||||||
|
def consoleDir = new File(zipDir, "console")
|
||||||
|
def webAppDir = new File(consoleDir, "webapps")
|
||||||
|
def keystore = new File(System.getProperty("user.home")+"/.i2p-plugin-keys/plugin-su3-keystore.ks")
|
||||||
|
|
||||||
|
|
||||||
|
String libDirPath() {
|
||||||
|
def libDir = new File("$buildDir/zip/lib")
|
||||||
|
libDir.listFiles().stream().map({
|
||||||
|
"\$PLUGIN/lib/" + it.getName()
|
||||||
|
}).collect(java.util.stream.Collectors.joining(','))
|
||||||
|
}
|
||||||
|
|
||||||
|
task pluginConfig {
|
||||||
|
doLast {
|
||||||
|
def binding = [ "version" : project.version + "-b" + project.buildNumber,
|
||||||
|
"author" : project.author,
|
||||||
|
"signer" : project.signer,
|
||||||
|
"websiteURL" : project.websiteURL,
|
||||||
|
"updateURLsu3" : project.updateURLsu3,
|
||||||
|
"i2pVersion" : project.i2pVersion ]
|
||||||
|
|
||||||
|
def templateFile = new File("$projectDir/templates/plugin.config.template")
|
||||||
|
def engine = new groovy.text.SimpleTemplateEngine()
|
||||||
|
def output = engine.createTemplate(templateFile).make(binding)
|
||||||
|
|
||||||
|
zipDir.mkdirs()
|
||||||
|
|
||||||
|
def outputFile = new File(zipDir,"plugin.config")
|
||||||
|
outputFile.text = output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task pluginDir {
|
||||||
|
dependsOn ':webui:assemble'
|
||||||
|
doLast { task ->
|
||||||
|
libDir.mkdirs()
|
||||||
|
def webapp = project(":webui")
|
||||||
|
def i2pjars = task.project.fileTree("../i2pjars").getFiles()
|
||||||
|
webapp.configurations.runtime.each {
|
||||||
|
if (!i2pjars.contains(it)) {
|
||||||
|
def dest = new File(libDir, it.getName())
|
||||||
|
java.nio.file.Files.copy(it.toPath(), dest.toPath())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
webAppDir.mkdirs()
|
||||||
|
def warFile = webapp.configurations.warArtifact.getAllArtifacts().file[0]
|
||||||
|
def dest = new File(webAppDir, "MuWire.war")
|
||||||
|
java.nio.file.Files.copy(warFile.toPath(), dest.toPath())
|
||||||
|
"zip -d ${dest.toString()} *.jar".execute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task webappConfig {
|
||||||
|
doLast {
|
||||||
|
def cPath = "webapps.MuWire.classpath=" + libDirPath()
|
||||||
|
def webappConfig = new File(consoleDir, "webapps.config")
|
||||||
|
webappConfig.text = cPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task pack {
|
||||||
|
doLast {
|
||||||
|
if (project.pack200 == "true") {
|
||||||
|
libDir.listFiles().stream().filter( { it.getName().endsWith(".jar") } ).
|
||||||
|
filter({it.length() > 512*1024}).forEach {
|
||||||
|
println "packing $it"
|
||||||
|
def name = it.toString()
|
||||||
|
println "pack200 --no-gzip ${name}.pack $name".execute().text
|
||||||
|
it.delete()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println "pack200 disabled"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task pluginZip(type: Zip) {
|
||||||
|
archiveFileName = "MuWire.zip"
|
||||||
|
destinationDirectory = buildDir
|
||||||
|
from zipDir
|
||||||
|
}
|
||||||
|
|
||||||
|
task sign {
|
||||||
|
doLast {
|
||||||
|
def password = project.keystorePassword
|
||||||
|
if (password == "") {
|
||||||
|
println "enter your keystore password:"
|
||||||
|
def reader = new BufferedReader(new InputStreamReader(System.in))
|
||||||
|
password = reader.readLine()
|
||||||
|
}
|
||||||
|
def su3args = []
|
||||||
|
def version = project.version + "-b" + project.buildNumber
|
||||||
|
su3args << 'sign' << '-t' << '6' << '-c' << 'PLUGIN' << '-f' << '0' << '-p' << password << \
|
||||||
|
"$buildDir/MuWire.zip".toString() << "$buildDir/MuWire.su3".toString() << \
|
||||||
|
keystore.getAbsoluteFile().toString() << version << project.signer
|
||||||
|
println "now enter private key password for signer $project.signer"
|
||||||
|
net.i2p.crypto.SU3File.main(su3args.toArray(new String[0]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
webappConfig.dependsOn pluginDir
|
||||||
|
pack.dependsOn webappConfig
|
||||||
|
pluginZip.dependsOn(webappConfig,pluginConfig,pack)
|
||||||
|
sign.dependsOn pluginZip
|
||||||
|
assemble.dependsOn sign
|
14
plug/templates/plugin.config.template
Normal file
14
plug/templates/plugin.config.template
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
name=MuWire
|
||||||
|
description=Easy Anonymous File-Sharing
|
||||||
|
license=GPLv3
|
||||||
|
version=${version}
|
||||||
|
author=${author}
|
||||||
|
signer=${signer}
|
||||||
|
websiteURL=${websiteURL}
|
||||||
|
updateURL.su3=${updateURLsu3}
|
||||||
|
min-i2p-version=${i2pVersion}
|
||||||
|
min-java-version=1.8
|
||||||
|
consoleLinkName=MuWire
|
||||||
|
consoleLinkTooltip=Anonymous File Sharing
|
||||||
|
consoleLinkURL=/MuWire
|
||||||
|
<% println "date="+System.currentTimeMillis() %>
|
@@ -4,3 +4,5 @@ include 'update-server'
|
|||||||
include 'core'
|
include 'core'
|
||||||
include 'gui'
|
include 'gui'
|
||||||
include 'cli'
|
include 'cli'
|
||||||
|
// include 'webui'
|
||||||
|
// include 'plug'
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user