Compare commits
70 Commits
trust-list
...
muwire-0.4
Author | SHA1 | Date | |
---|---|---|---|
![]() |
92dd7064c6 | ||
![]() |
b2e4dda677 | ||
![]() |
e77a2c8961 | ||
![]() |
ee2fd2ef68 | ||
![]() |
3f95d2bf1d | ||
![]() |
1390983732 | ||
![]() |
ce660cefe9 | ||
![]() |
72b81eb886 | ||
![]() |
57d593a68a | ||
![]() |
39a81a3376 | ||
![]() |
fd0bf17c24 | ||
![]() |
ac12bff69b | ||
![]() |
feef773bac | ||
![]() |
239d8f12a7 | ||
![]() |
8bbc61a7cb | ||
![]() |
7f31c4477f | ||
![]() |
6bad67c1bf | ||
![]() |
c76e6dc99f | ||
![]() |
acf9db0db3 | ||
![]() |
69b4f0b547 | ||
![]() |
80e165b505 | ||
![]() |
bcce55b873 | ||
![]() |
d5c92560db | ||
![]() |
f827c1c9bf | ||
![]() |
88c5f1a02d | ||
![]() |
d8e44f5f39 | ||
![]() |
72ff47ffe5 | ||
![]() |
066ee2c96d | ||
![]() |
0a8016dea7 | ||
![]() |
db36367b11 | ||
![]() |
b6c9ccb7f6 | ||
![]() |
a9dc636bce | ||
![]() |
3cc0574d11 | ||
![]() |
20fab9b16d | ||
![]() |
4015818323 | ||
![]() |
f569d45c8c | ||
![]() |
3773647869 | ||
![]() |
29cdbf018c | ||
![]() |
94bb7022eb | ||
![]() |
39808302df | ||
![]() |
2d22f9c39e | ||
![]() |
ee8f80bab6 | ||
![]() |
3e6242e583 | ||
![]() |
41181616ee | ||
![]() |
eb2530ca32 | ||
![]() |
b5233780ef | ||
![]() |
78753d7538 | ||
![]() |
4740e8b4f5 | ||
![]() |
ad5b00fc90 | ||
![]() |
d6c6880848 | ||
![]() |
4f948c1b9e | ||
![]() |
2b68c24f9c | ||
![]() |
bcdf0422db | ||
![]() |
f6434b478d | ||
![]() |
e979fdd26f | ||
![]() |
e6bfcaaab9 | ||
![]() |
9780108e8a | ||
![]() |
697c7d2d6d | ||
![]() |
887d10c8bf | ||
![]() |
ef6b8fe458 | ||
![]() |
20ab55d763 | ||
![]() |
eda58c9e0d | ||
![]() |
1ccf6fbdfa | ||
![]() |
5711979272 | ||
![]() |
9a5e2b1fa3 | ||
![]() |
a89b423dfc | ||
![]() |
79e8438941 | ||
![]() |
19c2c46491 | ||
![]() |
78f1d54b69 | ||
![]() |
9461649ed4 |
35
README.md
35
README.md
@@ -4,7 +4,7 @@ MuWire is an easy to use file-sharing program which offers anonymity using [I2P
|
||||
|
||||
It is inspired by the LimeWire Gnutella client and developped by a former LimeWire developer.
|
||||
|
||||
The current stable release - 0.4.0 is avaiable for download at https://muwire.com. You can find technical documentation in the "doc" folder.
|
||||
The current stable release - 0.4.6 is avaiable for download at https://muwire.com. You can find technical documentation in the "doc" folder.
|
||||
|
||||
### Building
|
||||
|
||||
@@ -23,34 +23,13 @@ Some of the UI tests will fail because they haven't been written yet :-/
|
||||
|
||||
### Running
|
||||
|
||||
You need to have an I2P router up and running on the same machine. After you build the application, look inside `gui/build/distributions`. Untar/unzip one of the `shadow` files and then run the jar contained inside by typing `java -jar MuWire-x.y.z.jar` in a terminal or command prompt. If you use a custom I2CP host and port, create a file `$HOME/.MuWire/i2p.properties` and put `i2cp.tcp.host=<host>` and `i2cp.tcp.port=<port>` in there.
|
||||
After you build the application, look inside `gui/build/distributions`. Untar/unzip one of the `shadow` files and then run the jar contained inside by typing `java -jar MuWire-x.y.z.jar` in a terminal or command prompt.
|
||||
|
||||
The first time you run MuWire it will ask you to select a nickname. This nickname will be displayed with search results, so that others can verify the file was shared by you. It is best to leave MuWire running all the time, just like I2P.
|
||||
If you have an I2P router running on the same machine that is all you need to do. If you use a custom I2CP host and port, create a file `i2p.properties` and put `i2cp.tcp.host=<host>` and `i2cp.tcp.port=<port>` in there. On Windows that file should go into `%HOME%\AppData\Roaming\MuWire`, on Mac into `$HOME/Library/Application Support/MuWire` and on Linux `$HOME/.MuWire`
|
||||
|
||||
If you do not have an I2P router, pass the following switch to the Java process: `-DembeddedRouter=true`. This will launch MuWire's embedded router. Be aware that this causes startup to take a lot longer.
|
||||
|
||||
### Known bugs and limitations
|
||||
### GPG Fingerprint
|
||||
471B 9FD4 5517 A5ED 101F C57D A728 3207 2D52 5E41
|
||||
|
||||
* Many UI features you would expect are not there yet
|
||||
|
||||
### Quick FAQ
|
||||
|
||||
* why is MuWire slow ?
|
||||
|
||||
- too few sources you're downloading from
|
||||
- you can increase the number of tunnels by using more tunnels via Options->I2P Inbound/Outbound Quantity
|
||||
the default is 4 and you could raise up to as high as 16 ( Caution !!!!)
|
||||
|
||||
* my search is not returning (enough) results !
|
||||
|
||||
- search is keyword or hash based
|
||||
- keywords and hash(es) are NOT regexed or wildcarded so they have to be complete
|
||||
so searching for 'musi' will not return results with 'music' - you have to search for 'music'
|
||||
- ALL keywords have to match
|
||||
- only use space for keyword separation
|
||||
- if you already have the file in question it is not displayed ( can be changed via Options )
|
||||
|
||||
* what's this right click -> 'Copy hash to clipboard' for ?
|
||||
|
||||
- if you have a specific file you wish to share or download you can use the hash as a unique identifier
|
||||
to make sure you have exactly the right file.
|
||||
- you can share this hash with others to ensure they are getting the right file
|
||||
You can find the full key at https://keybase.io/zlatinb
|
||||
|
4
TODO.md
4
TODO.md
@@ -12,10 +12,6 @@ This reduces query traffic by not sending last hop queries to peers that definit
|
||||
|
||||
This helps with scalability
|
||||
|
||||
##### Trust List Sharing
|
||||
|
||||
For helping users make better decisions whom to trust
|
||||
|
||||
##### Content Control Panel
|
||||
|
||||
To allow every user to not route queries for content they do not like. This is mostly GUI work, the backend part is simple
|
||||
|
@@ -2,7 +2,7 @@ subprojects {
|
||||
apply plugin: 'groovy'
|
||||
|
||||
dependencies {
|
||||
compile 'net.i2p:i2p:0.9.40'
|
||||
compile 'net.i2p:i2p:0.9.41'
|
||||
compile 'org.codehaus.groovy:groovy-all:2.4.15'
|
||||
}
|
||||
|
||||
|
@@ -35,7 +35,7 @@ class Cli {
|
||||
|
||||
Core core
|
||||
try {
|
||||
core = new Core(props, home, "0.4.4")
|
||||
core = new Core(props, home, "0.4.9")
|
||||
} catch (Exception bad) {
|
||||
bad.printStackTrace(System.out)
|
||||
println "Failed to initialize core, exiting"
|
||||
|
@@ -53,7 +53,7 @@ class CliDownloader {
|
||||
|
||||
Core core
|
||||
try {
|
||||
core = new Core(props, home, "0.4.4")
|
||||
core = new Core(props, home, "0.4.9")
|
||||
} catch (Exception bad) {
|
||||
bad.printStackTrace(System.out)
|
||||
println "Failed to initialize core, exiting"
|
||||
|
@@ -2,9 +2,9 @@ apply plugin : 'application'
|
||||
mainClassName = 'com.muwire.core.Core'
|
||||
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties']
|
||||
dependencies {
|
||||
compile 'net.i2p:router:0.9.40'
|
||||
compile 'net.i2p.client:mstreaming:0.9.40'
|
||||
compile 'net.i2p.client:streaming:0.9.40'
|
||||
compile 'net.i2p:router:0.9.41'
|
||||
compile 'net.i2p.client:mstreaming:0.9.41'
|
||||
compile 'net.i2p.client:streaming:0.9.41'
|
||||
|
||||
testCompile 'org.junit.jupiter:junit-jupiter-api:5.4.2'
|
||||
testCompile 'junit:junit:4.12'
|
||||
|
@@ -20,6 +20,7 @@ import com.muwire.core.download.UIDownloadPausedEvent
|
||||
import com.muwire.core.download.UIDownloadResumedEvent
|
||||
import com.muwire.core.files.FileDownloadedEvent
|
||||
import com.muwire.core.files.FileHashedEvent
|
||||
import com.muwire.core.files.FileHashingEvent
|
||||
import com.muwire.core.files.FileHasher
|
||||
import com.muwire.core.files.FileLoadedEvent
|
||||
import com.muwire.core.files.FileManager
|
||||
@@ -47,6 +48,8 @@ import com.muwire.core.trust.TrustSubscriptionEvent
|
||||
import com.muwire.core.update.UpdateClient
|
||||
import com.muwire.core.upload.UploadManager
|
||||
import com.muwire.core.util.MuWireLogManager
|
||||
import com.muwire.core.content.ContentControlEvent
|
||||
import com.muwire.core.content.ContentManager
|
||||
|
||||
import groovy.util.logging.Log
|
||||
import net.i2p.I2PAppContext
|
||||
@@ -89,6 +92,7 @@ public class Core {
|
||||
private final DirectoryWatcher directoryWatcher
|
||||
final FileManager fileManager
|
||||
final UploadManager uploadManager
|
||||
final ContentManager contentManager
|
||||
|
||||
private final Router router
|
||||
|
||||
@@ -140,7 +144,7 @@ public class Core {
|
||||
routerProps.setProperty("i2np.udp.port", i2pOptions["i2np.udp.port"])
|
||||
routerProps.setProperty("i2np.udp.internalPort", i2pOptions["i2np.udp.port"])
|
||||
router = new Router(routerProps)
|
||||
I2PAppContext.getGlobalContext().metaClass = new RouterContextMetaClass()
|
||||
router.getContext().setLogManager(new MuWireLogManager())
|
||||
router.runRouter()
|
||||
while(!router.isRunning())
|
||||
Thread.sleep(100)
|
||||
@@ -288,6 +292,11 @@ public class Core {
|
||||
trustSubscriber = new TrustSubscriber(eventBus, i2pConnector, props)
|
||||
eventBus.register(UILoadedEvent.class, trustSubscriber)
|
||||
eventBus.register(TrustSubscriptionEvent.class, trustSubscriber)
|
||||
|
||||
log.info("initializing content manager")
|
||||
contentManager = new ContentManager()
|
||||
eventBus.register(ContentControlEvent.class, contentManager)
|
||||
eventBus.register(QueryEvent.class, contentManager)
|
||||
}
|
||||
|
||||
public void startServices() {
|
||||
@@ -318,6 +327,8 @@ public class Core {
|
||||
connectionEstablisher.stop()
|
||||
log.info("shutting down directory watcher")
|
||||
directoryWatcher.stop()
|
||||
log.info("shutting down cache client")
|
||||
cacheClient.stop()
|
||||
log.info("shutting down connection manager")
|
||||
connectionManager.shutdown()
|
||||
if (router != null) {
|
||||
@@ -326,19 +337,6 @@ public class Core {
|
||||
}
|
||||
}
|
||||
|
||||
static class RouterContextMetaClass extends DelegatingMetaClass {
|
||||
private final Object logManager = new MuWireLogManager()
|
||||
RouterContextMetaClass() {
|
||||
super(RouterContext.class)
|
||||
}
|
||||
|
||||
Object invokeMethod(Object object, String name, Object[] args) {
|
||||
if (name == "logManager")
|
||||
return logManager
|
||||
super.invokeMethod(object, name, args)
|
||||
}
|
||||
}
|
||||
|
||||
static main(args) {
|
||||
def home = System.getProperty("user.home") + File.separator + ".MuWire"
|
||||
home = new File(home)
|
||||
@@ -363,7 +361,7 @@ public class Core {
|
||||
}
|
||||
}
|
||||
|
||||
Core core = new Core(props, home, "0.4.4")
|
||||
Core core = new Core(props, home, "0.4.9")
|
||||
core.startServices()
|
||||
|
||||
// ... at the end, sleep or execute script
|
||||
|
@@ -48,4 +48,9 @@ class EventBus {
|
||||
}
|
||||
currentHandlers.add handler
|
||||
}
|
||||
|
||||
synchronized void unregister(Class<? extends Event> eventType, def handler) {
|
||||
log.info("Unregistering $handler for type $eventType")
|
||||
handlers[eventType]?.remove(handler)
|
||||
}
|
||||
}
|
||||
|
@@ -28,6 +28,8 @@ class MuWireSettings {
|
||||
int meshExpiration
|
||||
boolean embeddedRouter
|
||||
int inBw, outBw
|
||||
Set<String> watchedKeywords
|
||||
Set<String> watchedRegexes
|
||||
|
||||
MuWireSettings() {
|
||||
this(new Properties())
|
||||
@@ -54,11 +56,9 @@ class MuWireSettings {
|
||||
inBw = Integer.valueOf(props.getProperty("inBw","256"))
|
||||
outBw = Integer.valueOf(props.getProperty("outBw","128"))
|
||||
|
||||
watchedDirectories = new HashSet<>()
|
||||
if (props.containsKey("watchedDirectories")) {
|
||||
String[] encoded = props.getProperty("watchedDirectories").split(",")
|
||||
encoded.each { watchedDirectories << DataUtil.readi18nString(Base64.decode(it)) }
|
||||
}
|
||||
watchedDirectories = readEncodedSet(props, "watchedDirectories")
|
||||
watchedKeywords = readEncodedSet(props, "watchedKeywords")
|
||||
watchedRegexes = readEncodedSet(props, "watchedRegexes")
|
||||
|
||||
trustSubscriptions = new HashSet<>()
|
||||
if (props.containsKey("trustSubscriptions")) {
|
||||
@@ -66,6 +66,8 @@ class MuWireSettings {
|
||||
trustSubscriptions.add(new Persona(new ByteArrayInputStream(Base64.decode(it))))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void write(OutputStream out) throws IOException {
|
||||
@@ -80,7 +82,7 @@ class MuWireSettings {
|
||||
props.setProperty("downloadRetryInterval", String.valueOf(downloadRetryInterval))
|
||||
props.setProperty("updateCheckInterval", String.valueOf(updateCheckInterval))
|
||||
props.setProperty("autoDownloadUpdate", String.valueOf(autoDownloadUpdate))
|
||||
props.setProperty("updateType",updateType)
|
||||
props.setProperty("updateType",String.valueOf(updateType))
|
||||
props.setProperty("shareDownloadedFiles", String.valueOf(shareDownloadedFiles))
|
||||
props.setProperty("downloadSequentialRatio", String.valueOf(downloadSequentialRatio))
|
||||
props.setProperty("hostClearInterval", String.valueOf(hostClearInterval))
|
||||
@@ -89,12 +91,9 @@ class MuWireSettings {
|
||||
props.setProperty("inBw", String.valueOf(inBw))
|
||||
props.setProperty("outBw", String.valueOf(outBw))
|
||||
|
||||
if (!watchedDirectories.isEmpty()) {
|
||||
String encoded = watchedDirectories.stream().
|
||||
map({Base64.encode(DataUtil.encodei18nString(it))}).
|
||||
collect(Collectors.joining(","))
|
||||
props.setProperty("watchedDirectories", encoded)
|
||||
}
|
||||
writeEncodedSet(watchedDirectories, "watchedDirectories", props)
|
||||
writeEncodedSet(watchedKeywords, "watchedKeywords", props)
|
||||
writeEncodedSet(watchedRegexes, "watchedRegexes", props)
|
||||
|
||||
if (!trustSubscriptions.isEmpty()) {
|
||||
String encoded = trustSubscriptions.stream().
|
||||
@@ -106,6 +105,24 @@ class MuWireSettings {
|
||||
props.store(out, "")
|
||||
}
|
||||
|
||||
private static Set<String> readEncodedSet(Properties props, String property) {
|
||||
Set<String> rv = new HashSet<>()
|
||||
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() {
|
||||
isLeaf
|
||||
}
|
||||
|
@@ -0,0 +1,9 @@
|
||||
package com.muwire.core.content
|
||||
|
||||
import com.muwire.core.Event
|
||||
|
||||
class ContentControlEvent extends Event {
|
||||
String term
|
||||
boolean regex
|
||||
boolean add
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
package com.muwire.core.content
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
import com.muwire.core.search.QueryEvent
|
||||
|
||||
import net.i2p.util.ConcurrentHashSet
|
||||
|
||||
class ContentManager {
|
||||
|
||||
Set<Matcher> matchers = new ConcurrentHashSet()
|
||||
|
||||
void onContentControlEvent(ContentControlEvent e) {
|
||||
Matcher m
|
||||
if (e.regex)
|
||||
m = new RegexMatcher(e.term)
|
||||
else
|
||||
m = new KeywordMatcher(e.term)
|
||||
if (e.add)
|
||||
matchers.add(m)
|
||||
else
|
||||
matchers.remove(m)
|
||||
}
|
||||
|
||||
void onQueryEvent(QueryEvent e) {
|
||||
if (e.searchEvent.searchTerms == null)
|
||||
return
|
||||
matchers.each { it.process(e) }
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
package com.muwire.core.content
|
||||
|
||||
class KeywordMatcher extends Matcher {
|
||||
private final String keyword
|
||||
KeywordMatcher(String keyword) {
|
||||
this.keyword = keyword
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean match(List<String> searchTerms) {
|
||||
boolean found = false
|
||||
searchTerms.each {
|
||||
if (keyword == it)
|
||||
found = true
|
||||
}
|
||||
found
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTerm() {
|
||||
keyword
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
keyword.hashCode()
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof KeywordMatcher))
|
||||
return false
|
||||
KeywordMatcher other = (KeywordMatcher) o
|
||||
keyword.equals(other.keyword)
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
package com.muwire.core.content
|
||||
|
||||
import com.muwire.core.Persona
|
||||
|
||||
class Match {
|
||||
Persona persona
|
||||
String [] keywords
|
||||
long timestamp
|
||||
}
|
20
core/src/main/groovy/com/muwire/core/content/Matcher.groovy
Normal file
20
core/src/main/groovy/com/muwire/core/content/Matcher.groovy
Normal file
@@ -0,0 +1,20 @@
|
||||
package com.muwire.core.content
|
||||
|
||||
import com.muwire.core.search.QueryEvent
|
||||
|
||||
abstract class Matcher {
|
||||
final List<Match> matches = Collections.synchronizedList(new ArrayList<>())
|
||||
final Set<UUID> uuids = new HashSet<>()
|
||||
|
||||
protected abstract boolean match(List<String> searchTerms);
|
||||
|
||||
public abstract String getTerm();
|
||||
|
||||
public void process(QueryEvent qe) {
|
||||
def terms = qe.searchEvent.searchTerms
|
||||
if (match(terms) && uuids.add(qe.searchEvent.uuid)) {
|
||||
long now = System.currentTimeMillis()
|
||||
matches << new Match(persona : qe.originator, keywords : terms, timestamp : now)
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
package com.muwire.core.content
|
||||
|
||||
import java.util.regex.Pattern
|
||||
import java.util.stream.Collectors
|
||||
|
||||
class RegexMatcher extends Matcher {
|
||||
private final Pattern pattern
|
||||
RegexMatcher(String pattern) {
|
||||
this.pattern = Pattern.compile(pattern)
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean match(List<String> keywords) {
|
||||
String combined = keywords.join(" ")
|
||||
return pattern.matcher(combined).find()
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTerm() {
|
||||
pattern.pattern()
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
pattern.pattern().hashCode()
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof RegexMatcher))
|
||||
return false
|
||||
RegexMatcher other = (RegexMatcher) o
|
||||
pattern.pattern() == other.pattern.pattern()
|
||||
}
|
||||
}
|
@@ -14,6 +14,7 @@ import static com.muwire.core.util.DataUtil.readTillRN
|
||||
import groovy.util.logging.Log
|
||||
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.MappedByteBuffer
|
||||
import java.nio.channels.FileChannel
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.nio.file.Files
|
||||
@@ -25,8 +26,6 @@ import java.util.logging.Level
|
||||
@Log
|
||||
class DownloadSession {
|
||||
|
||||
private static int SAMPLES = 10
|
||||
|
||||
private final EventBus eventBus
|
||||
private final String meB64
|
||||
private final Pieces pieces
|
||||
@@ -38,10 +37,10 @@ class DownloadSession {
|
||||
private final Set<Integer> available
|
||||
private final MessageDigest digest
|
||||
|
||||
private final LinkedList<Long> timestamps = new LinkedList<>()
|
||||
private final LinkedList<Integer> reads = new LinkedList<>()
|
||||
private long lastSpeedRead = System.currentTimeMillis()
|
||||
private long dataSinceLastRead
|
||||
|
||||
private ByteBuffer mapped
|
||||
private MappedByteBuffer mapped
|
||||
|
||||
DownloadSession(EventBus eventBus, String meB64, Pieces pieces, InfoHash infoHash, Endpoint endpoint, File file,
|
||||
int pieceSize, long fileLength, Set<Integer> available) {
|
||||
@@ -71,21 +70,23 @@ class DownloadSession {
|
||||
OutputStream os = endpoint.getOutputStream()
|
||||
InputStream is = endpoint.getInputStream()
|
||||
|
||||
int piece
|
||||
int[] pieceAndPosition
|
||||
if (available.isEmpty())
|
||||
piece = pieces.claim()
|
||||
pieceAndPosition = pieces.claim()
|
||||
else
|
||||
piece = pieces.claim(new HashSet<>(available))
|
||||
if (piece == -1)
|
||||
pieceAndPosition = pieces.claim(new HashSet<>(available))
|
||||
if (pieceAndPosition == null)
|
||||
return false
|
||||
int piece = pieceAndPosition[0]
|
||||
int position = pieceAndPosition[1]
|
||||
boolean steal = pieceAndPosition[2] == 1
|
||||
boolean unclaim = true
|
||||
|
||||
log.info("will download piece $piece")
|
||||
|
||||
long start = piece * pieceSize
|
||||
long end = Math.min(fileLength, start + pieceSize) - 1
|
||||
long length = end - start + 1
|
||||
log.info("will download piece $piece from position $position steal $steal")
|
||||
|
||||
long pieceStart = piece * ((long)pieceSize)
|
||||
long end = Math.min(fileLength, pieceStart + pieceSize) - 1
|
||||
long start = pieceStart + position
|
||||
String root = Base64.encode(infoHash.getRoot())
|
||||
|
||||
try {
|
||||
@@ -174,8 +175,9 @@ class DownloadSession {
|
||||
FileChannel channel
|
||||
try {
|
||||
channel = Files.newByteChannel(file.toPath(), EnumSet.of(StandardOpenOption.READ, StandardOpenOption.WRITE,
|
||||
StandardOpenOption.SPARSE, StandardOpenOption.CREATE)) // TODO: double-check, maybe CREATE_NEW
|
||||
mapped = channel.map(FileChannel.MapMode.READ_WRITE, start, end - start + 1)
|
||||
StandardOpenOption.SPARSE, StandardOpenOption.CREATE))
|
||||
mapped = channel.map(FileChannel.MapMode.READ_WRITE, pieceStart, end - pieceStart + 1)
|
||||
mapped.position(position)
|
||||
|
||||
byte[] tmp = new byte[0x1 << 13]
|
||||
while(mapped.hasRemaining()) {
|
||||
@@ -186,31 +188,28 @@ class DownloadSession {
|
||||
throw new IOException()
|
||||
synchronized(this) {
|
||||
mapped.put(tmp, 0, read)
|
||||
|
||||
if (timestamps.size() == SAMPLES) {
|
||||
timestamps.removeFirst()
|
||||
reads.removeFirst()
|
||||
}
|
||||
timestamps.addLast(System.currentTimeMillis())
|
||||
reads.addLast(read)
|
||||
dataSinceLastRead += read
|
||||
pieces.markPartial(piece, mapped.position())
|
||||
}
|
||||
}
|
||||
|
||||
mapped.clear()
|
||||
digest.update(mapped)
|
||||
DataUtil.tryUnmap(mapped)
|
||||
byte [] hash = digest.digest()
|
||||
byte [] expected = new byte[32]
|
||||
System.arraycopy(infoHash.getHashList(), piece * 32, expected, 0, 32)
|
||||
if (hash != expected)
|
||||
throw new BadHashException()
|
||||
if (hash != expected) {
|
||||
pieces.markPartial(piece, 0)
|
||||
throw new BadHashException("bad hash on piece $piece")
|
||||
}
|
||||
} finally {
|
||||
try { channel?.close() } catch (IOException ignore) {}
|
||||
DataUtil.tryUnmap(mapped)
|
||||
}
|
||||
pieces.markDownloaded(piece)
|
||||
unclaim = false
|
||||
} finally {
|
||||
if (unclaim)
|
||||
if (unclaim && !steal)
|
||||
pieces.unclaim(piece)
|
||||
}
|
||||
return true
|
||||
@@ -223,24 +222,11 @@ class DownloadSession {
|
||||
}
|
||||
|
||||
synchronized int speed() {
|
||||
if (timestamps.size() < SAMPLES)
|
||||
return 0
|
||||
int totalRead = 0
|
||||
int idx = 0
|
||||
final long now = System.currentTimeMillis()
|
||||
|
||||
while(idx < SAMPLES && timestamps.get(idx) < now - 1000)
|
||||
idx++
|
||||
if (idx == SAMPLES)
|
||||
return 0
|
||||
if (idx == SAMPLES - 1)
|
||||
return reads[idx]
|
||||
|
||||
long interval = timestamps.last - timestamps[idx]
|
||||
if (interval == 0)
|
||||
interval = 1
|
||||
for (int i = idx; i < SAMPLES; i++)
|
||||
totalRead += reads[idx]
|
||||
(int)(totalRead * 1000.0 / interval)
|
||||
long interval = Math.max(1000, now - lastSpeedRead)
|
||||
lastSpeedRead = now;
|
||||
int rv = (int) (dataSinceLastRead * 1000.0 / interval)
|
||||
dataSinceLastRead = 0
|
||||
rv
|
||||
}
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@ import com.muwire.core.connection.Endpoint
|
||||
import java.nio.file.AtomicMoveNotSupportedException
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.StandardCopyOption
|
||||
import java.time.Instant
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
@@ -58,6 +59,11 @@ public class Downloader {
|
||||
private final AtomicBoolean eventFired = new AtomicBoolean()
|
||||
private boolean piecesFileClosed
|
||||
|
||||
private ArrayList speedArr = new ArrayList<Integer>()
|
||||
private int speedPos = 0
|
||||
private int speedAvg = 0
|
||||
private long timestamp = Instant.now().toEpochMilli()
|
||||
|
||||
public Downloader(EventBus eventBus, DownloadManager downloadManager,
|
||||
Persona me, File file, long length, InfoHash infoHash,
|
||||
int pieceSizePow2, I2PConnector connector, Set<Destination> destinations,
|
||||
@@ -76,6 +82,10 @@ public class Downloader {
|
||||
this.pieceSize = 1 << pieceSizePow2
|
||||
this.pieces = pieces
|
||||
this.nPieces = pieces.nPieces
|
||||
|
||||
// default size suitable for an average of 5 seconds / 5 elements / 5 interval units
|
||||
// it's easily adjustable by resizing the size of speedArr
|
||||
this.speedArr = [ 0, 0, 0, 0, 0 ]
|
||||
}
|
||||
|
||||
public synchronized InfoHash getInfoHash() {
|
||||
@@ -101,8 +111,14 @@ public class Downloader {
|
||||
if (!piecesFile.exists())
|
||||
return
|
||||
piecesFile.eachLine {
|
||||
int piece = Integer.parseInt(it)
|
||||
String [] split = it.split(",")
|
||||
int piece = Integer.parseInt(split[0])
|
||||
if (split.length == 1)
|
||||
pieces.markDownloaded(piece)
|
||||
else {
|
||||
int position = Integer.parseInt(split[1])
|
||||
pieces.markPartial(piece, position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,9 +127,7 @@ public class Downloader {
|
||||
if (piecesFileClosed)
|
||||
return
|
||||
piecesFile.withPrintWriter { writer ->
|
||||
pieces.getDownloaded().each { piece ->
|
||||
writer.println(piece)
|
||||
}
|
||||
pieces.write(writer)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -124,14 +138,35 @@ public class Downloader {
|
||||
|
||||
|
||||
public int speed() {
|
||||
int total = 0
|
||||
int currSpeed = 0
|
||||
if (getCurrentState() == DownloadState.DOWNLOADING) {
|
||||
activeWorkers.values().each {
|
||||
if (it.currentState == WorkerState.DOWNLOADING)
|
||||
total += it.speed()
|
||||
currSpeed += it.speed()
|
||||
}
|
||||
}
|
||||
total
|
||||
|
||||
// normalize to speedArr.size
|
||||
currSpeed /= speedArr.size()
|
||||
|
||||
// compute new speedAvg and update speedArr
|
||||
if ( speedArr[speedPos] > speedAvg ) {
|
||||
speedAvg = 0
|
||||
} else {
|
||||
speedAvg -= speedArr[speedPos]
|
||||
}
|
||||
speedAvg += currSpeed
|
||||
speedArr[speedPos] = currSpeed
|
||||
// this might be necessary due to rounding errors
|
||||
if (speedAvg < 0)
|
||||
speedAvg = 0
|
||||
|
||||
// rolling index over the speedArr
|
||||
speedPos++
|
||||
if (speedPos >= speedArr.size())
|
||||
speedPos=0
|
||||
|
||||
speedAvg
|
||||
}
|
||||
|
||||
public DownloadState getCurrentState() {
|
||||
@@ -272,12 +307,17 @@ public class Downloader {
|
||||
} catch (Exception bad) {
|
||||
log.log(Level.WARNING,"Exception while downloading",DataUtil.findRoot(bad))
|
||||
} finally {
|
||||
writePieces()
|
||||
currentState = WorkerState.FINISHED
|
||||
if (pieces.isComplete() && eventFired.compareAndSet(false, true)) {
|
||||
synchronized(piecesFile) {
|
||||
piecesFileClosed = true
|
||||
piecesFile.delete()
|
||||
}
|
||||
activeWorkers.values().each {
|
||||
if (it.destination != destination)
|
||||
it.cancel()
|
||||
}
|
||||
try {
|
||||
Files.move(incompleteFile.toPath(), file.toPath(), StandardCopyOption.ATOMIC_MOVE)
|
||||
} catch (AtomicMoveNotSupportedException e) {
|
||||
|
@@ -5,6 +5,7 @@ class Pieces {
|
||||
private final int nPieces
|
||||
private final float ratio
|
||||
private final Random random = new Random()
|
||||
private final Map<Integer,Integer> partials = new HashMap<>()
|
||||
|
||||
Pieces(int nPieces) {
|
||||
this(nPieces, 1.0f)
|
||||
@@ -17,16 +18,22 @@ class Pieces {
|
||||
claimed = new BitSet(nPieces)
|
||||
}
|
||||
|
||||
synchronized int claim() {
|
||||
synchronized int[] claim() {
|
||||
int claimedCardinality = claimed.cardinality()
|
||||
if (claimedCardinality == nPieces)
|
||||
return -1
|
||||
if (claimedCardinality == nPieces) {
|
||||
// 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 ( (1.0f * claimedCardinality) / nPieces > ratio) {
|
||||
int rv = claimed.nextClearBit(0)
|
||||
claimed.set(rv)
|
||||
return rv
|
||||
return [rv, partials.getOrDefault(rv, 0), 0]
|
||||
}
|
||||
|
||||
while(true) {
|
||||
@@ -34,20 +41,28 @@ class Pieces {
|
||||
if (claimed.get(start))
|
||||
continue
|
||||
claimed.set(start)
|
||||
return start
|
||||
return [start, partials.getOrDefault(start,0), 0]
|
||||
}
|
||||
}
|
||||
|
||||
synchronized int claim(Set<Integer> available) {
|
||||
for (int i = claimed.nextSetBit(0); i >= 0; i = claimed.nextSetBit(i+1))
|
||||
synchronized int[] claim(Set<Integer> available) {
|
||||
for (int i = done.nextSetBit(0); i >= 0; i = done.nextSetBit(i+1))
|
||||
available.remove(i)
|
||||
if (available.isEmpty())
|
||||
return -1
|
||||
List<Integer> toList = available.toList()
|
||||
return null
|
||||
Set<Integer> availableCopy = new HashSet<>(available)
|
||||
for (int i = claimed.nextSetBit(0); i >= 0; i = claimed.nextSetBit(i+1))
|
||||
availableCopy.remove(i)
|
||||
if (availableCopy.isEmpty()) {
|
||||
// steal
|
||||
int rv = available.first()
|
||||
return [rv, partials.getOrDefault(rv, 0), 1]
|
||||
}
|
||||
List<Integer> toList = availableCopy.toList()
|
||||
Collections.shuffle(toList)
|
||||
int rv = toList[0]
|
||||
claimed.set(rv)
|
||||
rv
|
||||
[rv, partials.getOrDefault(rv, 0), 0]
|
||||
}
|
||||
|
||||
synchronized def getDownloaded() {
|
||||
@@ -61,6 +76,11 @@ class Pieces {
|
||||
synchronized void markDownloaded(int piece) {
|
||||
done.set(piece)
|
||||
claimed.set(piece)
|
||||
partials.remove(piece)
|
||||
}
|
||||
|
||||
synchronized void markPartial(int piece, int position) {
|
||||
partials.put(piece, position)
|
||||
}
|
||||
|
||||
synchronized void unclaim(int piece) {
|
||||
@@ -82,5 +102,15 @@ class Pieces {
|
||||
synchronized void clearAll() {
|
||||
done.clear()
|
||||
claimed.clear()
|
||||
partials.clear()
|
||||
}
|
||||
|
||||
synchronized void write(PrintWriter writer) {
|
||||
for (int i = done.nextSetBit(0); i >= 0; i = done.nextSetBit(i+1)) {
|
||||
writer.println(i)
|
||||
}
|
||||
partials.each { piece, position ->
|
||||
writer.println("$piece,$position")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,15 @@
|
||||
package com.muwire.core.files
|
||||
|
||||
import com.muwire.core.Event
|
||||
import com.muwire.core.SharedFile
|
||||
|
||||
class FileHashingEvent extends Event {
|
||||
|
||||
File hashingFile
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
super.toString() + " hashingFile " + hashingFile.getAbsolutePath()
|
||||
}
|
||||
|
||||
}
|
@@ -39,6 +39,7 @@ class HasherService {
|
||||
} else if (f.length() > FileHasher.MAX_SIZE) {
|
||||
eventBus.publish new FileHashedEvent(error: "$f is too large to be shared ${f.length()}")
|
||||
} else {
|
||||
eventBus.publish new FileHashingEvent(hashingFile: f)
|
||||
def hash = hasher.hashFile f
|
||||
eventBus.publish new FileHashedEvent(sharedFile: new SharedFile(f, hash, FileHasher.getPieceSize(f.length())))
|
||||
}
|
||||
|
@@ -6,7 +6,8 @@ class CacheServers {
|
||||
|
||||
private static final int TO_GIVE = 3
|
||||
private static Set<Destination> CACHES = [
|
||||
new Destination("Wddh2E6FyyXBF7SvUYHKdN-vjf3~N6uqQWNeBDTM0P33YjiQCOsyedrjmDZmWFrXUJfJLWnCb5bnKezfk4uDaMyj~uvDG~yvLVcFgcPWSUd7BfGgym-zqcG1q1DcM8vfun-US7YamBlmtC6MZ2j-~Igqzmgshita8aLPCfNAA6S6e2UMjjtG7QIXlxpMec75dkHdJlVWbzrk9z8Qgru3YIk0UztYgEwDNBbm9wInsbHhr3HtAfa02QcgRVqRN2PnQXuqUJs7R7~09FZPEviiIcUpkY3FeyLlX1sgQFBeGeA96blaPvZNGd6KnNdgfLgMebx5SSxC-N4KZMSMBz5cgonQF3~m2HHFRSI85zqZNG5X9bJN85t80ltiv1W1es8ZnQW4es11r7MrvJNXz5bmSH641yJIvS6qI8OJJNpFVBIQSXLD-96TayrLQPaYw~uNZ-eXaE6G5dYhiuN8xHsFI1QkdaUaVZnvDGfsRbpS5GtpUbBDbyLkdPurG0i7dN1wAAAA")
|
||||
new Destination("Wddh2E6FyyXBF7SvUYHKdN-vjf3~N6uqQWNeBDTM0P33YjiQCOsyedrjmDZmWFrXUJfJLWnCb5bnKezfk4uDaMyj~uvDG~yvLVcFgcPWSUd7BfGgym-zqcG1q1DcM8vfun-US7YamBlmtC6MZ2j-~Igqzmgshita8aLPCfNAA6S6e2UMjjtG7QIXlxpMec75dkHdJlVWbzrk9z8Qgru3YIk0UztYgEwDNBbm9wInsbHhr3HtAfa02QcgRVqRN2PnQXuqUJs7R7~09FZPEviiIcUpkY3FeyLlX1sgQFBeGeA96blaPvZNGd6KnNdgfLgMebx5SSxC-N4KZMSMBz5cgonQF3~m2HHFRSI85zqZNG5X9bJN85t80ltiv1W1es8ZnQW4es11r7MrvJNXz5bmSH641yJIvS6qI8OJJNpFVBIQSXLD-96TayrLQPaYw~uNZ-eXaE6G5dYhiuN8xHsFI1QkdaUaVZnvDGfsRbpS5GtpUbBDbyLkdPurG0i7dN1wAAAA"),
|
||||
new Destination("JC63wJNOqSJmymkj4~UJWywBTvDGikKMoYP0HX2Wz9c5l3otXSkwnxWAFL4cKr~Ygh3BNNi2t93vuLIiI1W8AsE42kR~PwRx~Y-WvIHXR6KUejRmOp-n8WidtjKg9k4aDy428uSOedqXDxys5mpoeQXwDsv1CoPTTwnmb1GWFy~oTGIsCguCl~aJWGnqiKarPO3GJQ~ev-NbvAQzUfC3HeP1e6pdI5CGGjExahTCID5UjpJw8GaDXWlGmYWWH303Xu4x-vAHQy1dJLsOBCn8dZravsn5BKJk~j0POUon45CCx-~NYtaPe0Itt9cMdD2ciC76Rep1D0X0sm1SjlSs8sZ52KmF3oaLZ6OzgI9QLMIyBUrfi41sK5I0qTuUVBAkvW1xr~L-20dYJ9TrbOaOb2-vDIfKaxVi6xQOuhgQDiSBhd3qv2m0xGu-BM9DQYfNA0FdMjnZmqjmji9RMavzQSsVFIbQGLbrLepiEFlb7TseCK5UtRp8TxnG7L4gbYevBQAEAAcAAA==")
|
||||
]
|
||||
|
||||
static List<Destination> getCacheServers() {
|
||||
|
@@ -7,7 +7,7 @@ import com.muwire.core.Persona
|
||||
import net.i2p.util.ConcurrentHashSet
|
||||
|
||||
class RemoteTrustList {
|
||||
public enum Status { NEW, UPDATING, UPDATED }
|
||||
public enum Status { NEW, UPDATING, UPDATED, UPDATE_FAILED }
|
||||
|
||||
private final Persona persona
|
||||
private final Set<Persona> good, bad
|
||||
|
@@ -94,13 +94,15 @@ class TrustSubscriber {
|
||||
public void run() {
|
||||
trustList.status = RemoteTrustList.Status.UPDATING
|
||||
eventBus.publish(new TrustSubscriptionUpdatedEvent(trustList : trustList))
|
||||
check(trustList, System.currentTimeMillis())
|
||||
if (check(trustList, System.currentTimeMillis()))
|
||||
trustList.status = RemoteTrustList.Status.UPDATED
|
||||
else
|
||||
trustList.status = RemoteTrustList.Status.UPDATE_FAILED
|
||||
eventBus.publish(new TrustSubscriptionUpdatedEvent(trustList : trustList))
|
||||
}
|
||||
}
|
||||
|
||||
private void check(RemoteTrustList trustList, long now) {
|
||||
private boolean check(RemoteTrustList trustList, long now) {
|
||||
log.info("fetching trust list from ${trustList.persona.getHumanReadableName()}")
|
||||
Endpoint endpoint = null
|
||||
try {
|
||||
@@ -118,7 +120,7 @@ class TrustSubscriber {
|
||||
|
||||
if (code != 200) {
|
||||
log.info("couldn't fetch trust list, code $code")
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
// swallow any headers
|
||||
@@ -147,8 +149,10 @@ class TrustSubscriber {
|
||||
trustList.bad.clear()
|
||||
trustList.bad.addAll(bad)
|
||||
|
||||
return true
|
||||
} catch (Exception e) {
|
||||
log.log(Level.WARNING,"exception fetching trust list from ${trustList.persona.getHumanReadableName()}",e)
|
||||
return false
|
||||
} finally {
|
||||
endpoint?.close()
|
||||
}
|
||||
|
@@ -83,7 +83,7 @@ class ContentUploader extends Uploader {
|
||||
String xHave = DataUtil.encodeXHave(mesh.pieces.getDownloaded(), mesh.pieces.nPieces)
|
||||
endpoint.getOutputStream().write("X-Have: $xHave\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||
|
||||
Set<Persona> sources = mesh.getRandom(3, toExclude)
|
||||
Set<Persona> sources = mesh.getRandom(9, toExclude)
|
||||
if (!sources.isEmpty()) {
|
||||
String xAlts = sources.stream().map({ it.toBase64() }).collect(Collectors.joining(","))
|
||||
endpoint.getOutputStream().write("X-Alt: $xAlts\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||
@@ -119,4 +119,8 @@ class ContentUploader extends Uploader {
|
||||
return mesh.pieces.nPieces;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTotalSize() {
|
||||
return file.length();
|
||||
}
|
||||
}
|
||||
|
@@ -61,5 +61,8 @@ class HashListUploader extends Uploader {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public long getTotalSize() {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
@@ -35,5 +35,7 @@ abstract class Uploader {
|
||||
|
||||
abstract int getDonePieces();
|
||||
|
||||
abstract int getTotalPieces()
|
||||
abstract int getTotalPieces();
|
||||
|
||||
abstract long getTotalSize();
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
group = com.muwire
|
||||
version = 0.4.4
|
||||
version = 0.4.9
|
||||
groovyVersion = 2.4.15
|
||||
slf4jVersion = 1.7.25
|
||||
spockVersion = 1.1-groovy-2.4
|
||||
|
34
gradlew
vendored
34
gradlew
vendored
@@ -11,21 +11,21 @@
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
ls=$(ls -ld "$PRG")
|
||||
link=$(expr "$ls" : '.*-> \(.*\)$')
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
PRG=$(dirname "$PRG")"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
SAVED="$(pwd)"
|
||||
cd "$(dirname "$PRG")/" >/dev/null
|
||||
APP_HOME="$(pwd -P)"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
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.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
@@ -49,7 +49,7 @@ cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
case "$(uname)" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
@@ -90,7 +90,7 @@ fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
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 [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
@@ -111,12 +111,12 @@ fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
APP_HOME=$(cygpath --path --mixed "$APP_HOME")
|
||||
CLASSPATH=$(cygpath --path --mixed "$CLASSPATH")
|
||||
JAVACMD=$(cygpath --unix "$JAVACMD")
|
||||
|
||||
# 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=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
@@ -130,13 +130,13 @@ if $cygwin ; then
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
CHECK=$(echo "$arg"|egrep -c "$OURCYGPATTERN" -)
|
||||
CHECK2=$(echo "$arg"|egrep -c "^-") ### Determine if an option
|
||||
|
||||
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
|
||||
eval `echo args$i`="\"$arg\""
|
||||
eval $(echo args$i)="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
|
@@ -41,4 +41,9 @@ mvcGroups {
|
||||
view = 'com.muwire.gui.TrustListView'
|
||||
controller = 'com.muwire.gui.TrustListController'
|
||||
}
|
||||
'content-panel' {
|
||||
model = 'com.muwire.gui.ContentPanelModel'
|
||||
view = 'com.muwire.gui.ContentPanelView'
|
||||
controller = 'com.muwire.gui.ContentPanelController'
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,95 @@
|
||||
package com.muwire.gui
|
||||
|
||||
import griffon.core.artifact.GriffonController
|
||||
import griffon.core.controller.ControllerAction
|
||||
import griffon.inject.MVCMember
|
||||
import griffon.metadata.ArtifactProviderFor
|
||||
import javax.annotation.Nonnull
|
||||
|
||||
import com.muwire.core.Core
|
||||
import com.muwire.core.EventBus
|
||||
import com.muwire.core.content.ContentControlEvent
|
||||
import com.muwire.core.content.Match
|
||||
import com.muwire.core.content.Matcher
|
||||
import com.muwire.core.content.RegexMatcher
|
||||
import com.muwire.core.trust.TrustEvent
|
||||
import com.muwire.core.trust.TrustLevel
|
||||
|
||||
@ArtifactProviderFor(GriffonController)
|
||||
class ContentPanelController {
|
||||
@MVCMember @Nonnull
|
||||
ContentPanelModel model
|
||||
@MVCMember @Nonnull
|
||||
ContentPanelView view
|
||||
|
||||
Core core
|
||||
|
||||
@ControllerAction
|
||||
void addRule() {
|
||||
def term = view.ruleTextField.text
|
||||
|
||||
if (model.regex)
|
||||
core.muOptions.watchedRegexes.add(term)
|
||||
else
|
||||
core.muOptions.watchedKeywords.add(term)
|
||||
saveMuWireSettings()
|
||||
|
||||
core.eventBus.publish(new ContentControlEvent(term : term, regex : model.regex, add:true))
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void deleteRule() {
|
||||
int rule = view.getSelectedRule()
|
||||
if (rule < 0)
|
||||
return
|
||||
Matcher matcher = model.rules[rule]
|
||||
String term = matcher.getTerm()
|
||||
if (matcher instanceof RegexMatcher)
|
||||
core.muOptions.watchedRegexes.remove(term)
|
||||
else
|
||||
core.muOptions.watchedKeywords.remove(term)
|
||||
saveMuWireSettings()
|
||||
|
||||
core.eventBus.publish(new ContentControlEvent(term : term, regex : (matcher instanceof RegexMatcher), add: false))
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void keyword() {
|
||||
model.regex = false
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void regex() {
|
||||
model.regex = true
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void refresh() {
|
||||
model.refresh()
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void trust() {
|
||||
int selectedHit = view.getSelectedHit()
|
||||
if (selectedHit < 0)
|
||||
return
|
||||
Match m = model.hits[selectedHit]
|
||||
core.eventBus.publish(new TrustEvent(persona : m.persona, level : TrustLevel.TRUSTED))
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void distrust() {
|
||||
int selectedHit = view.getSelectedHit()
|
||||
if (selectedHit < 0)
|
||||
return
|
||||
Match m = model.hits[selectedHit]
|
||||
core.eventBus.publish(new TrustEvent(persona : m.persona, level : TrustLevel.DISTRUSTED))
|
||||
}
|
||||
|
||||
void saveMuWireSettings() {
|
||||
File f = new File(core.home, "MuWire.properties")
|
||||
f.withOutputStream {
|
||||
core.muOptions.write(it)
|
||||
}
|
||||
}
|
||||
}
|
@@ -165,6 +165,24 @@ class MainFrameController {
|
||||
core.eventBus.publish( new TrustEvent(persona : result.sender, level : TrustLevel.DISTRUSTED))
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void trustPersonaFromSearch() {
|
||||
int selected = builder.getVariable("searches-table").getSelectedRow()
|
||||
if (selected < 0)
|
||||
return
|
||||
Persona p = model.searches[selected].originator
|
||||
core.eventBus.publish( new TrustEvent(persona : p, level : TrustLevel.TRUSTED) )
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void distrustPersonaFromSearch() {
|
||||
int selected = builder.getVariable("searches-table").getSelectedRow()
|
||||
if (selected < 0)
|
||||
return
|
||||
Persona p = model.searches[selected].originator
|
||||
core.eventBus.publish( new TrustEvent(persona : p, level : TrustLevel.DISTRUSTED) )
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void cancel() {
|
||||
def downloader = model.downloads[selectedDownload()].downloader
|
||||
@@ -191,27 +209,38 @@ class MainFrameController {
|
||||
int row = view.getSelectedTrustTablesRow(tableName)
|
||||
if (row < 0)
|
||||
return
|
||||
builder.getVariable(tableName).model.fireTableDataChanged()
|
||||
core.eventBus.publish(new TrustEvent(persona : list[row], level : level))
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void markTrusted() {
|
||||
markTrust("distrusted-table", TrustLevel.TRUSTED, model.distrusted)
|
||||
model.markTrustedButtonEnabled = false
|
||||
model.markNeutralFromDistrustedButtonEnabled = false
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void markNeutralFromDistrusted() {
|
||||
markTrust("distrusted-table", TrustLevel.NEUTRAL, model.distrusted)
|
||||
model.markTrustedButtonEnabled = false
|
||||
model.markNeutralFromDistrustedButtonEnabled = false
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void markDistrusted() {
|
||||
markTrust("trusted-table", TrustLevel.DISTRUSTED, model.trusted)
|
||||
model.subscribeButtonEnabled = false
|
||||
model.markDistrustedButtonEnabled = false
|
||||
model.markNeutralFromTrustedButtonEnabled = false
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void markNeutralFromTrusted() {
|
||||
markTrust("trusted-table", TrustLevel.NEUTRAL, model.trusted)
|
||||
model.subscribeButtonEnabled = false
|
||||
model.markDistrustedButtonEnabled = false
|
||||
model.markNeutralFromTrustedButtonEnabled = false
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
@@ -223,6 +252,9 @@ class MainFrameController {
|
||||
core.muOptions.trustSubscriptions.add(p)
|
||||
saveMuWireSettings()
|
||||
core.eventBus.publish(new TrustSubscriptionEvent(persona : p, subscribe : true))
|
||||
model.subscribeButtonEnabled = false
|
||||
model.markDistrustedButtonEnabled = false
|
||||
model.markNeutralFromTrustedButtonEnabled = false
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
|
@@ -27,6 +27,7 @@ class TrustListController {
|
||||
return
|
||||
Persona p = model.trusted[selectedRow]
|
||||
eventBus.publish(new TrustEvent(persona : p, level : TrustLevel.TRUSTED))
|
||||
view.fireUpdate("trusted-table")
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
@@ -36,6 +37,7 @@ class TrustListController {
|
||||
return
|
||||
Persona p = model.distrusted[selectedRow]
|
||||
eventBus.publish(new TrustEvent(persona : p, level : TrustLevel.TRUSTED))
|
||||
view.fireUpdate("distrusted-table")
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
@@ -45,6 +47,7 @@ class TrustListController {
|
||||
return
|
||||
Persona p = model.trusted[selectedRow]
|
||||
eventBus.publish(new TrustEvent(persona : p, level : TrustLevel.DISTRUSTED))
|
||||
view.fireUpdate("trusted-table")
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
@@ -54,5 +57,6 @@ class TrustListController {
|
||||
return
|
||||
Persona p = model.distrusted[selectedRow]
|
||||
eventBus.publish(new TrustEvent(persona : p, level : TrustLevel.DISTRUSTED))
|
||||
view.fireUpdate("distrusted-table")
|
||||
}
|
||||
}
|
@@ -49,7 +49,7 @@ class Ready extends AbstractLifecycleHandler {
|
||||
log.info("creating new properties")
|
||||
props = new MuWireSettings()
|
||||
props.embeddedRouter = Boolean.parseBoolean(System.getProperties().getProperty("embeddedRouter"))
|
||||
props.updateType = System.getProperty("updateType")
|
||||
props.updateType = System.getProperty("updateType","jar")
|
||||
def nickname
|
||||
while (true) {
|
||||
nickname = JOptionPane.showInputDialog(null,
|
||||
|
@@ -0,0 +1,55 @@
|
||||
package com.muwire.gui
|
||||
|
||||
import javax.annotation.Nonnull
|
||||
|
||||
import com.muwire.core.Core
|
||||
import com.muwire.core.EventBus
|
||||
import com.muwire.core.content.ContentControlEvent
|
||||
import com.muwire.core.content.ContentManager
|
||||
|
||||
import griffon.core.artifact.GriffonModel
|
||||
import griffon.inject.MVCMember
|
||||
import griffon.transform.Observable
|
||||
import griffon.metadata.ArtifactProviderFor
|
||||
|
||||
@ArtifactProviderFor(GriffonModel)
|
||||
class ContentPanelModel {
|
||||
|
||||
@MVCMember @Nonnull
|
||||
ContentPanelView view
|
||||
|
||||
Core core
|
||||
|
||||
private ContentManager contentManager
|
||||
|
||||
def rules = []
|
||||
def hits = []
|
||||
|
||||
@Observable boolean regex
|
||||
@Observable boolean deleteButtonEnabled
|
||||
@Observable boolean trustButtonsEnabled
|
||||
|
||||
void mvcGroupInit(Map<String,String> args) {
|
||||
contentManager = application.context.get("core").contentManager
|
||||
rules.addAll(contentManager.matchers)
|
||||
core.eventBus.register(ContentControlEvent.class, this)
|
||||
}
|
||||
|
||||
void mvcGroupDestroy() {
|
||||
core.eventBus.unregister(ContentControlEvent.class, this)
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
rules.clear()
|
||||
rules.addAll(contentManager.matchers)
|
||||
hits.clear()
|
||||
view.rulesTable.model.fireTableDataChanged()
|
||||
view.hitsTable.model.fireTableDataChanged()
|
||||
}
|
||||
|
||||
void onContentControlEvent(ContentControlEvent e) {
|
||||
runInsideUIAsync {
|
||||
refresh()
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,8 @@
|
||||
package com.muwire.gui
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.Calendar
|
||||
import java.util.UUID
|
||||
|
||||
import javax.annotation.Nonnull
|
||||
import javax.inject.Inject
|
||||
@@ -15,11 +17,13 @@ import com.muwire.core.RouterDisconnectedEvent
|
||||
import com.muwire.core.connection.ConnectionAttemptStatus
|
||||
import com.muwire.core.connection.ConnectionEvent
|
||||
import com.muwire.core.connection.DisconnectionEvent
|
||||
import com.muwire.core.content.ContentControlEvent
|
||||
import com.muwire.core.download.DownloadStartedEvent
|
||||
import com.muwire.core.download.Downloader
|
||||
import com.muwire.core.files.AllFilesLoadedEvent
|
||||
import com.muwire.core.files.FileDownloadedEvent
|
||||
import com.muwire.core.files.FileHashedEvent
|
||||
import com.muwire.core.files.FileHashingEvent
|
||||
import com.muwire.core.files.FileLoadedEvent
|
||||
import com.muwire.core.files.FileSharedEvent
|
||||
import com.muwire.core.files.FileUnsharedEvent
|
||||
@@ -70,12 +74,19 @@ class MainFrameModel {
|
||||
|
||||
@Observable int connections
|
||||
@Observable String me
|
||||
@Observable int loadedFiles
|
||||
@Observable File hashingFile
|
||||
@Observable boolean downloadActionEnabled
|
||||
@Observable boolean trustButtonsEnabled
|
||||
@Observable boolean cancelButtonEnabled
|
||||
@Observable boolean retryButtonEnabled
|
||||
@Observable boolean pauseButtonEnabled
|
||||
@Observable String resumeButtonText
|
||||
@Observable boolean subscribeButtonEnabled
|
||||
@Observable boolean markNeutralFromTrustedButtonEnabled
|
||||
@Observable boolean markDistrustedButtonEnabled
|
||||
@Observable boolean markNeutralFromDistrustedButtonEnabled
|
||||
@Observable boolean markTrustedButtonEnabled
|
||||
@Observable boolean reviewButtonEnabled
|
||||
@Observable boolean updateButtonEnabled
|
||||
@Observable boolean unsubscribeButtonEnabled
|
||||
@@ -140,6 +151,7 @@ class MainFrameModel {
|
||||
core.eventBus.register(ConnectionEvent.class, this)
|
||||
core.eventBus.register(DisconnectionEvent.class, this)
|
||||
core.eventBus.register(FileHashedEvent.class, this)
|
||||
core.eventBus.register(FileHashingEvent.class, this)
|
||||
core.eventBus.register(FileLoadedEvent.class, this)
|
||||
core.eventBus.register(UploadEvent.class, this)
|
||||
core.eventBus.register(UploadFinishedEvent.class, this)
|
||||
@@ -153,6 +165,13 @@ class MainFrameModel {
|
||||
core.eventBus.register(UpdateDownloadedEvent.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({
|
||||
if (core.shutdown.get())
|
||||
return
|
||||
@@ -256,7 +275,17 @@ class MainFrameModel {
|
||||
}
|
||||
}
|
||||
|
||||
void onFileHashingEvent(FileHashingEvent e) {
|
||||
runInsideUIAsync {
|
||||
loadedFiles = shared.size()
|
||||
hashingFile = e.hashingFile
|
||||
}
|
||||
}
|
||||
|
||||
void onFileHashedEvent(FileHashedEvent e) {
|
||||
runInsideUIAsync {
|
||||
hashingFile = null
|
||||
}
|
||||
if (e.error != null)
|
||||
return // TODO do something
|
||||
if (infoHashes.contains(e.sharedFile.infoHash))
|
||||
@@ -264,6 +293,7 @@ class MainFrameModel {
|
||||
infoHashes.add(e.sharedFile.infoHash)
|
||||
runInsideUIAsync {
|
||||
shared << e.sharedFile
|
||||
loadedFiles = shared.size()
|
||||
JTable table = builder.getVariable("shared-files-table")
|
||||
table.model.fireTableDataChanged()
|
||||
}
|
||||
@@ -275,6 +305,7 @@ class MainFrameModel {
|
||||
infoHashes.add(e.loadedFile.infoHash)
|
||||
runInsideUIAsync {
|
||||
shared << e.loadedFile
|
||||
loadedFiles = shared.size()
|
||||
JTable table = builder.getVariable("shared-files-table")
|
||||
table.model.fireTableDataChanged()
|
||||
}
|
||||
@@ -286,6 +317,7 @@ class MainFrameModel {
|
||||
return
|
||||
runInsideUIAsync {
|
||||
shared.remove(e.unsharedFile)
|
||||
loadedFiles = shared.size()
|
||||
JTable table = builder.getVariable("shared-files-table")
|
||||
table.model.fireTableDataChanged()
|
||||
}
|
||||
@@ -355,10 +387,32 @@ class MainFrameModel {
|
||||
return
|
||||
}
|
||||
runInsideUIAsync {
|
||||
searches.addFirst(new IncomingSearch(search : search, replyTo : e.replyTo, originator : e.originator))
|
||||
JTable table = builder.getVariable("searches-table")
|
||||
|
||||
Boolean searchFound = false
|
||||
Iterator searchIter = searches.iterator()
|
||||
while ( searchIter.hasNext() ) {
|
||||
IncomingSearch searchEle = searchIter.next()
|
||||
if ( searchEle.search == search
|
||||
&& searchEle.originator == e.originator
|
||||
&& searchEle.uuid == e.searchEvent.getUuid() ) {
|
||||
searchIter.remove()
|
||||
table.model.fireTableDataChanged()
|
||||
searchFound = true
|
||||
searchEle.count++
|
||||
searchEle.timestamp = Calendar.getInstance()
|
||||
searches.addFirst(searchEle)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!searchFound) {
|
||||
searches.addFirst(new IncomingSearch(search, e.replyTo, e.originator, e.searchEvent.getUuid()))
|
||||
}
|
||||
|
||||
while(searches.size() > 200)
|
||||
searches.removeLast()
|
||||
JTable table = builder.getVariable("searches-table")
|
||||
|
||||
table.model.fireTableDataChanged()
|
||||
}
|
||||
}
|
||||
@@ -367,6 +421,18 @@ class MainFrameModel {
|
||||
String search
|
||||
Destination replyTo
|
||||
Persona originator
|
||||
long count
|
||||
UUID uuid
|
||||
Calendar timestamp
|
||||
|
||||
IncomingSearch( String search, Destination replyTo, Persona originator, UUID uuid ) {
|
||||
this.search = search
|
||||
this.replyTo = replyTo
|
||||
this.originator = originator
|
||||
this.uuid = uuid
|
||||
this.count = 1
|
||||
this.timestamp = Calendar.getInstance()
|
||||
}
|
||||
}
|
||||
|
||||
void onUpdateAvailableEvent(UpdateAvailableEvent e) {
|
||||
|
154
gui/griffon-app/views/com/muwire/gui/ContentPanelView.groovy
Normal file
154
gui/griffon-app/views/com/muwire/gui/ContentPanelView.groovy
Normal file
@@ -0,0 +1,154 @@
|
||||
package com.muwire.gui
|
||||
|
||||
import griffon.core.artifact.GriffonView
|
||||
import griffon.inject.MVCMember
|
||||
import griffon.metadata.ArtifactProviderFor
|
||||
|
||||
import javax.swing.JDialog
|
||||
import javax.swing.JLabel
|
||||
import javax.swing.ListSelectionModel
|
||||
import javax.swing.SwingConstants
|
||||
import javax.swing.table.DefaultTableCellRenderer
|
||||
|
||||
import com.muwire.core.content.Matcher
|
||||
import com.muwire.core.content.RegexMatcher
|
||||
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.event.WindowAdapter
|
||||
import java.awt.event.WindowEvent
|
||||
|
||||
import javax.annotation.Nonnull
|
||||
|
||||
@ArtifactProviderFor(GriffonView)
|
||||
class ContentPanelView {
|
||||
@MVCMember @Nonnull
|
||||
FactoryBuilderSupport builder
|
||||
@MVCMember @Nonnull
|
||||
ContentPanelModel model
|
||||
|
||||
def dialog
|
||||
def mainFrame
|
||||
def mainPanel
|
||||
|
||||
def rulesTable
|
||||
def ruleTextField
|
||||
def lastRulesSortEvent
|
||||
def hitsTable
|
||||
def lastHitsSortEvent
|
||||
|
||||
void initUI() {
|
||||
mainFrame = application.windowManager.findWindow("main-frame")
|
||||
dialog = new JDialog(mainFrame, "Content Control Panel", true)
|
||||
|
||||
mainPanel = builder.panel {
|
||||
gridLayout(rows:1, cols:2)
|
||||
panel {
|
||||
borderLayout()
|
||||
panel (constraints : BorderLayout.NORTH) {
|
||||
label(text : "Rules")
|
||||
}
|
||||
scrollPane (constraints : BorderLayout.CENTER) {
|
||||
rulesTable = table(id : "rules-table", autoCreateRowSorter : true) {
|
||||
tableModel(list : model.rules) {
|
||||
closureColumn(header: "Term", type:String, read: {row -> row.getTerm()})
|
||||
closureColumn(header: "Regex?", type:Boolean, read: {row -> row instanceof RegexMatcher})
|
||||
closureColumn(header: "Hits", type:Integer, read : {row -> row.matches.size()})
|
||||
}
|
||||
}
|
||||
}
|
||||
panel (constraints : BorderLayout.SOUTH) {
|
||||
borderLayout()
|
||||
ruleTextField = textField(constraints: BorderLayout.CENTER, action: addRuleAction)
|
||||
panel (constraints: BorderLayout.EAST) {
|
||||
buttonGroup(id : "ruleType")
|
||||
radioButton(text: "Keyword", selected : true, buttonGroup: ruleType, keywordAction)
|
||||
radioButton(text: "Regex", selected : false, buttonGroup: ruleType, regexAction)
|
||||
button(text : "Add Rule", addRuleAction)
|
||||
button(text : "Delete Rule", enabled : bind {model.deleteButtonEnabled}, deleteRuleAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
panel (border : etchedBorder()){
|
||||
borderLayout()
|
||||
panel (constraints : BorderLayout.NORTH) {
|
||||
label(text : "Hits")
|
||||
}
|
||||
scrollPane(constraints : BorderLayout.CENTER) {
|
||||
hitsTable = table(id : "hits-table", autoCreateRowSorter : true) {
|
||||
tableModel(list : model.hits) {
|
||||
closureColumn(header : "Searcher", type : String, read : {row -> row.persona.getHumanReadableName()})
|
||||
closureColumn(header : "Keywords", type : String, read : {row -> row.keywords.join(" ")})
|
||||
closureColumn(header : "Date", type : String, read : {row -> String.valueOf(new Date(row.timestamp))})
|
||||
}
|
||||
}
|
||||
}
|
||||
panel (constraints : BorderLayout.SOUTH) {
|
||||
button(text : "Refresh", refreshAction)
|
||||
button(text : "Trust", enabled : bind {model.trustButtonsEnabled}, trustAction)
|
||||
button(text : "Distrust", enabled : bind {model.trustButtonsEnabled}, distrustAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int getSelectedRule() {
|
||||
int selectedRow = rulesTable.getSelectedRow()
|
||||
if (selectedRow < 0)
|
||||
return -1
|
||||
if (lastRulesSortEvent != null)
|
||||
selectedRow = rulesTable.rowSorter.convertRowIndexToModel(selectedRow)
|
||||
selectedRow
|
||||
}
|
||||
|
||||
int getSelectedHit() {
|
||||
int selectedRow = hitsTable.getSelectedRow()
|
||||
if (selectedRow < 0)
|
||||
return -1
|
||||
if (lastHitsSortEvent != null)
|
||||
selectedRow = hitsTable.rowSorter.convertRowIndexToModel(selectedRow)
|
||||
selectedRow
|
||||
}
|
||||
|
||||
void mvcGroupInit(Map<String,String> args) {
|
||||
def centerRenderer = new DefaultTableCellRenderer()
|
||||
centerRenderer.setHorizontalAlignment(JLabel.CENTER)
|
||||
rulesTable.setDefaultRenderer(Integer.class, centerRenderer)
|
||||
rulesTable.rowSorter.addRowSorterListener({evt -> lastRulesSortEvent = evt})
|
||||
rulesTable.rowSorter.setSortsOnUpdates(true)
|
||||
def selectionModel = rulesTable.getSelectionModel()
|
||||
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
|
||||
selectionModel.addListSelectionListener({
|
||||
int selectedRow = getSelectedRule()
|
||||
if (selectedRow < 0) {
|
||||
model.deleteButtonEnabled = false
|
||||
return
|
||||
} else {
|
||||
model.deleteButtonEnabled = true
|
||||
model.hits.clear()
|
||||
Matcher matcher = model.rules[selectedRow]
|
||||
model.hits.addAll(matcher.matches)
|
||||
hitsTable.model.fireTableDataChanged()
|
||||
}
|
||||
})
|
||||
|
||||
hitsTable.rowSorter.addRowSorterListener({evt -> lastHitsSortEvent = evt})
|
||||
hitsTable.rowSorter.setSortsOnUpdates(true)
|
||||
selectionModel = hitsTable.getSelectionModel()
|
||||
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
|
||||
selectionModel.addListSelectionListener({
|
||||
int selectedRow = getSelectedHit()
|
||||
model.trustButtonsEnabled = selectedRow >= 0
|
||||
})
|
||||
|
||||
dialog.getContentPane().add(mainPanel)
|
||||
dialog.pack()
|
||||
dialog.setLocationRelativeTo(mainFrame)
|
||||
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
|
||||
dialog.addWindowListener(new WindowAdapter() {
|
||||
public void windowClosed(WindowEvent e) {
|
||||
mvcGroup.destroy()
|
||||
}
|
||||
})
|
||||
dialog.show()
|
||||
}
|
||||
}
|
@@ -44,7 +44,7 @@ class I2PStatusView {
|
||||
label(text : "Participating Tunnels", constraints : gbc(gridx:0, gridy:4))
|
||||
label(text : bind {model.participatingTunnels}, constraints : gbc(gridx: 1, gridy:4))
|
||||
label(text : "Participating Bandwidth", constraints : gbc(gridx:0, gridy:5))
|
||||
label(text : bind {model.participatingBW}, constraints : gbc(gridx: 1, gridy:6))
|
||||
label(text : bind {model.participatingBW}, constraints : gbc(gridx: 1, gridy:5))
|
||||
label(text : "Active Peers", constraints : gbc(gridx:0, gridy:6))
|
||||
label(text : bind {model.activePeers}, constraints : gbc(gridx: 1, gridy:6))
|
||||
label(text : "Receive Bps (15 seconds)", constraints : gbc(gridx:0, gridy:7))
|
||||
|
@@ -26,7 +26,6 @@ import com.muwire.core.MuWireSettings
|
||||
import com.muwire.core.download.Downloader
|
||||
import com.muwire.core.files.FileSharedEvent
|
||||
import com.muwire.core.trust.RemoteTrustList
|
||||
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.CardLayout
|
||||
import java.awt.FlowLayout
|
||||
@@ -73,6 +72,11 @@ class MainFrameView {
|
||||
menuBar {
|
||||
menu (text : "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") {
|
||||
menuItem("MuWire", actionPerformed : {mvcGroup.createMVCGroup("mu-wire-status")})
|
||||
@@ -129,13 +133,9 @@ class MainFrameView {
|
||||
scrollPane (constraints : BorderLayout.CENTER) {
|
||||
downloadsTable = table(id : "downloads-table", autoCreateRowSorter : true) {
|
||||
tableModel(list: model.downloads) {
|
||||
closureColumn(header: "Name", preferredWidth: 350, 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: "Progress", preferredWidth: 20, type: String, read: { row ->
|
||||
int pieces = row.downloader.nPieces
|
||||
int done = row.downloader.donePieces()
|
||||
"$done/$pieces pieces".toString()
|
||||
})
|
||||
closureColumn(header: "Progress", preferredWidth: 70, type: Downloader, read: { row -> row.downloader })
|
||||
closureColumn(header: "Sources", preferredWidth : 10, type: Integer, read : {row -> row.downloader.activeWorkers()})
|
||||
closureColumn(header: "Speed", preferredWidth: 50, type:String, read :{row ->
|
||||
DataHelper.formatSize2Decimal(row.downloader.speed(), false) + "B/sec"
|
||||
@@ -154,12 +154,20 @@ class MainFrameView {
|
||||
panel (constraints: "uploads window"){
|
||||
gridLayout(cols : 1, rows : 2)
|
||||
panel {
|
||||
borderLayout()
|
||||
panel (constraints : BorderLayout.NORTH) {
|
||||
label(text : bind {
|
||||
if (model.hashingFile == null) {
|
||||
""
|
||||
} else {
|
||||
"hashing: " + model.hashingFile.getAbsolutePath() + " (" + DataHelper.formatSize2Decimal(model.hashingFile.length(), false).toString() + "B)"
|
||||
}
|
||||
})
|
||||
}
|
||||
panel (border : etchedBorder(), constraints : BorderLayout.CENTER) {
|
||||
gridLayout(cols : 2, rows : 1)
|
||||
panel {
|
||||
borderLayout()
|
||||
panel (constraints : BorderLayout.NORTH) {
|
||||
button(text : "Add directories to watch", actionPerformed : watchDirectories)
|
||||
}
|
||||
scrollPane (constraints : BorderLayout.CENTER) {
|
||||
table(id : "watched-directories-table", autoCreateRowSorter: true) {
|
||||
tableModel(list : model.watched) {
|
||||
@@ -170,9 +178,6 @@ class MainFrameView {
|
||||
}
|
||||
panel {
|
||||
borderLayout()
|
||||
panel (constraints : BorderLayout.NORTH) {
|
||||
button(text : "Share files", actionPerformed : shareFiles)
|
||||
}
|
||||
scrollPane(constraints : BorderLayout.CENTER) {
|
||||
table(id : "shared-files-table", autoCreateRowSorter: true) {
|
||||
tableModel(list : model.shared) {
|
||||
@@ -183,7 +188,19 @@ class MainFrameView {
|
||||
}
|
||||
}
|
||||
}
|
||||
panel (constraints : BorderLayout.SOUTH) {
|
||||
gridLayout(rows:1, cols:2)
|
||||
panel {
|
||||
button(text : "Add directories to watch", actionPerformed : watchDirectories)
|
||||
button(text : "Share files", actionPerformed : shareFiles)
|
||||
}
|
||||
panel {
|
||||
label("Shared:")
|
||||
label(text : bind {model.loadedFiles.toString()})
|
||||
}
|
||||
}
|
||||
}
|
||||
panel (border : etchedBorder()) {
|
||||
borderLayout()
|
||||
panel (constraints : BorderLayout.NORTH){
|
||||
label("Uploads")
|
||||
@@ -200,7 +217,18 @@ class MainFrameView {
|
||||
row.getDownloader()
|
||||
})
|
||||
closureColumn(header : "Remote Pieces", type : String, read : { row ->
|
||||
"${row.getDonePieces()}/${row.getTotalPieces()}".toString()
|
||||
int pieces = row.getTotalPieces()
|
||||
int done = row.getDonePieces()
|
||||
int percent = -1
|
||||
if ( pieces != 0 ) {
|
||||
percent = (done * 100) / pieces
|
||||
}
|
||||
long size = row.getTotalSize()
|
||||
String totalSize = ""
|
||||
if (size >= 0 ) {
|
||||
totalSize = " of " + DataHelper.formatSize2Decimal(size, false) + "B"
|
||||
}
|
||||
String.format("%02d", percent) + "% ${totalSize} ($done/$pieces pcs)".toString()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -247,6 +275,14 @@ class MainFrameView {
|
||||
return it.replyTo.toBase32()
|
||||
}
|
||||
})
|
||||
closureColumn(header : "Count", type : String, read : {
|
||||
it.count.toString()
|
||||
})
|
||||
closureColumn(header : "Timestamp", type : String, read : {
|
||||
String.format("%02d", it.timestamp.get(Calendar.HOUR_OF_DAY)) + ":" +
|
||||
String.format("%02d", it.timestamp.get(Calendar.MINUTE)) + ":" +
|
||||
String.format("%02d", it.timestamp.get(Calendar.SECOND))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -265,11 +301,11 @@ class MainFrameView {
|
||||
}
|
||||
}
|
||||
}
|
||||
panel (constraints : BorderLayout.EAST) {
|
||||
panel (constraints : BorderLayout.SOUTH) {
|
||||
gridBagLayout()
|
||||
button(text : "Mark Neutral", constraints : gbc(gridx: 0, gridy: 0), markNeutralFromTrustedAction)
|
||||
button(text : "Mark Distrusted", constraints : gbc(gridx: 0, gridy:1), markDistrustedAction)
|
||||
button(text : "Subscribe", constraints : gbc(gridx: 0, gridy : 2), subscribeAction)
|
||||
button(text : "Subscribe", enabled : bind {model.subscribeButtonEnabled}, constraints : gbc(gridx: 0, gridy : 0), subscribeAction)
|
||||
button(text : "Mark Neutral", enabled : bind {model.markNeutralFromTrustedButtonEnabled}, constraints : gbc(gridx: 1, gridy: 0), markNeutralFromTrustedAction)
|
||||
button(text : "Mark Distrusted", enabled : bind {model.markDistrustedButtonEnabled}, constraints : gbc(gridx: 2, gridy:0), markDistrustedAction)
|
||||
}
|
||||
}
|
||||
panel (border : etchedBorder()){
|
||||
@@ -281,10 +317,10 @@ class MainFrameView {
|
||||
}
|
||||
}
|
||||
}
|
||||
panel(constraints : BorderLayout.WEST) {
|
||||
panel(constraints : BorderLayout.SOUTH) {
|
||||
gridBagLayout()
|
||||
button(text: "Mark Neutral", constraints: gbc(gridx: 0, gridy: 0), markNeutralFromDistrustedAction)
|
||||
button(text: "Mark Trusted", constraints : gbc(gridx: 0, gridy : 1), markTrustedAction)
|
||||
button(text: "Mark Neutral", enabled : bind {model.markNeutralFromDistrustedButtonEnabled}, constraints: gbc(gridx: 0, gridy: 0), markNeutralFromDistrustedAction)
|
||||
button(text: "Mark Trusted", enabled : bind {model.markTrustedButtonEnabled}, constraints : gbc(gridx: 1, gridy : 0), markTrustedAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -296,11 +332,11 @@ class MainFrameView {
|
||||
scrollPane(constraints : BorderLayout.CENTER) {
|
||||
table(id : "subscription-table", autoCreateRowSorter : true) {
|
||||
tableModel(list : model.subscriptions) {
|
||||
closureColumn(header : "Name", type: String, read : {it.persona.getHumanReadableName()})
|
||||
closureColumn(header : "Trusted", type: Integer, read : {it.good.size()})
|
||||
closureColumn(header : "Distrusted", type: Integer, read : {it.bad.size()})
|
||||
closureColumn(header : "Status", type: String, read : {it.status.toString()})
|
||||
closureColumn(header : "Last Updated", type : String, read : {
|
||||
closureColumn(header : "Name", preferredWidth: 200, type: String, read : {it.persona.getHumanReadableName()})
|
||||
closureColumn(header : "Trusted", preferredWidth : 20, type: Integer, read : {it.good.size()})
|
||||
closureColumn(header : "Distrusted", preferredWidth: 20, type: Integer, read : {it.bad.size()})
|
||||
closureColumn(header : "Status", preferredWidth: 30, type: String, read : {it.status.toString()})
|
||||
closureColumn(header : "Last Updated", preferredWidth: 200, type : String, read : {
|
||||
if (it.timestamp == 0)
|
||||
return "Never"
|
||||
else
|
||||
@@ -375,9 +411,11 @@ class MainFrameView {
|
||||
def centerRenderer = new DefaultTableCellRenderer()
|
||||
centerRenderer.setHorizontalAlignment(JLabel.CENTER)
|
||||
downloadsTable.setDefaultRenderer(Integer.class, centerRenderer)
|
||||
downloadsTable.setDefaultRenderer(Downloader.class, new DownloadProgressRenderer())
|
||||
|
||||
downloadsTable.rowSorter.addRowSorterListener({evt -> lastDownloadSortEvent = evt})
|
||||
downloadsTable.rowSorter.setSortsOnUpdates(true)
|
||||
downloadsTable.rowSorter.setComparator(2, new DownloaderComparator())
|
||||
|
||||
downloadsTable.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
@@ -422,9 +460,18 @@ class MainFrameView {
|
||||
// searches table
|
||||
def searchesTable = builder.getVariable("searches-table")
|
||||
JPopupMenu searchTableMenu = new JPopupMenu()
|
||||
|
||||
JMenuItem copySearchToClipboard = new JMenuItem("Copy search to clipboard")
|
||||
copySearchToClipboard.addActionListener({mvcGroup.view.copySearchToClipboard(searchesTable)})
|
||||
JMenuItem trustSearcher = new JMenuItem("Trust searcher")
|
||||
trustSearcher.addActionListener({mvcGroup.controller.trustPersonaFromSearch()})
|
||||
JMenuItem distrustSearcher = new JMenuItem("Distrust searcher")
|
||||
distrustSearcher.addActionListener({mvcGroup.controller.distrustPersonaFromSearch()})
|
||||
|
||||
searchTableMenu.add(copySearchToClipboard)
|
||||
searchTableMenu.add(trustSearcher)
|
||||
searchTableMenu.add(distrustSearcher)
|
||||
|
||||
searchesTable.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
@@ -461,6 +508,7 @@ class MainFrameView {
|
||||
|
||||
// subscription table
|
||||
def subscriptionTable = builder.getVariable("subscription-table")
|
||||
subscriptionTable.setDefaultRenderer(Integer.class, centerRenderer)
|
||||
subscriptionTable.rowSorter.addRowSorterListener({evt -> trustTablesSortEvents["subscription-table"] = evt})
|
||||
subscriptionTable.rowSorter.setSortsOnUpdates(true)
|
||||
selectionModel = subscriptionTable.getSelectionModel()
|
||||
@@ -488,6 +536,11 @@ class MainFrameView {
|
||||
model.updateButtonEnabled = true
|
||||
model.unsubscribeButtonEnabled = true
|
||||
break
|
||||
case RemoteTrustList.Status.UPDATE_FAILED:
|
||||
model.reviewButtonEnabled = false
|
||||
model.updateButtonEnabled = true
|
||||
model.unsubscribeButtonEnabled = true
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
@@ -495,11 +548,37 @@ class MainFrameView {
|
||||
def trustedTable = builder.getVariable("trusted-table")
|
||||
trustedTable.rowSorter.addRowSorterListener({evt -> trustTablesSortEvents["trusted-table"] = evt})
|
||||
trustedTable.rowSorter.setSortsOnUpdates(true)
|
||||
selectionModel = trustedTable.getSelectionModel()
|
||||
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
|
||||
selectionModel.addListSelectionListener({
|
||||
int selectedRow = getSelectedTrustTablesRow("trusted-table")
|
||||
if (selectedRow < 0) {
|
||||
model.subscribeButtonEnabled = false
|
||||
model.markDistrustedButtonEnabled = false
|
||||
model.markNeutralFromTrustedButtonEnabled = false
|
||||
} else {
|
||||
model.subscribeButtonEnabled = true
|
||||
model.markDistrustedButtonEnabled = true
|
||||
model.markNeutralFromTrustedButtonEnabled = true
|
||||
}
|
||||
})
|
||||
|
||||
// distrusted table
|
||||
def distrustedTable = builder.getVariable("distrusted-table")
|
||||
distrustedTable.rowSorter.addRowSorterListener({evt -> trustTablesSortEvents["distrusted-table"] = evt})
|
||||
distrustedTable.rowSorter.setSortsOnUpdates(true)
|
||||
selectionModel = distrustedTable.getSelectionModel()
|
||||
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
|
||||
selectionModel.addListSelectionListener({
|
||||
int selectedRow = getSelectedTrustTablesRow("distrusted-table")
|
||||
if (selectedRow < 0) {
|
||||
model.markTrustedButtonEnabled = false
|
||||
model.markNeutralFromDistrustedButtonEnabled = false
|
||||
} else {
|
||||
model.markTrustedButtonEnabled = true
|
||||
model.markNeutralFromDistrustedButtonEnabled = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private static void showPopupMenu(JPopupMenu menu, MouseEvent event) {
|
||||
|
@@ -83,10 +83,12 @@ class TrustListView {
|
||||
|
||||
def trustedTable = builder.getVariable("trusted-table")
|
||||
trustedTable.rowSorter.addRowSorterListener({evt -> sortEvents["trusted-table"] = evt})
|
||||
trustedTable.rowSorter.setSortsOnUpdates(true)
|
||||
trustedTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
|
||||
|
||||
def distrustedTable = builder.getVariable("distrusted-table")
|
||||
distrustedTable.rowSorter.addRowSorterListener({evt -> sortEvents["distrusted-table"] = evt})
|
||||
distrustedTable.rowSorter.setSortsOnUpdates(true)
|
||||
distrustedTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
|
||||
|
||||
dialog.getContentPane().add(mainPanel)
|
||||
@@ -110,4 +112,9 @@ class TrustListView {
|
||||
selectedRow = table.rowSorter.convertRowIndexToModel(selectedRow)
|
||||
selectedRow
|
||||
}
|
||||
|
||||
void fireUpdate(String tableName) {
|
||||
def table = builder.getVariable(tableName)
|
||||
table.model.fireTableDataChanged()
|
||||
}
|
||||
}
|
@@ -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} ($done/$pieces pcs)".toString())
|
||||
|
||||
if (isSelected) {
|
||||
setForeground(table.getSelectionForeground())
|
||||
setBackground(table.getSelectionBackground())
|
||||
} else {
|
||||
setForeground(table.getForeground())
|
||||
setBackground(table.getBackground())
|
||||
}
|
||||
this
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
package com.muwire.gui
|
||||
|
||||
import com.muwire.core.download.Downloader
|
||||
|
||||
class DownloaderComparator implements Comparator<Downloader>{
|
||||
|
||||
@Override
|
||||
public int compare(Downloader o1, Downloader o2) {
|
||||
double d1 = o1.donePieces() * 1.0 / o1.nPieces
|
||||
double d2 = o2.donePieces() * 1.0 / o2.nPieces
|
||||
return Double.compare(d1, d2);
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
package com.muwire.gui
|
||||
|
||||
import griffon.core.test.GriffonUnitRule
|
||||
import griffon.core.test.TestFor
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
import static org.junit.Assert.fail
|
||||
|
||||
@TestFor(ContentPanelController)
|
||||
class ContentPanelControllerTest {
|
||||
private ContentPanelController controller
|
||||
|
||||
@Rule
|
||||
public final GriffonUnitRule griffon = new GriffonUnitRule()
|
||||
|
||||
@Test
|
||||
void smokeTest() {
|
||||
fail('Not yet implemented!')
|
||||
}
|
||||
}
|
@@ -11,6 +11,7 @@ import net.i2p.client.I2PSession
|
||||
import net.i2p.client.I2PSessionMuxedListener
|
||||
import net.i2p.client.datagram.I2PDatagramDissector
|
||||
import net.i2p.client.datagram.I2PDatagramMaker
|
||||
import net.i2p.crypto.SigType
|
||||
import net.i2p.util.SystemVersion
|
||||
import net.i2p.data.*
|
||||
|
||||
@@ -43,7 +44,7 @@ public class HostCache {
|
||||
def session
|
||||
if (!keyfile.exists()) {
|
||||
def os = new FileOutputStream(keyfile);
|
||||
myDest = i2pClient.createDestination(os)
|
||||
myDest = i2pClient.createDestination(os, SigType.EdDSA_SHA512_Ed25519)
|
||||
os.close()
|
||||
println "No key.dat file was found, so creating a new destination."
|
||||
println "This is the destination you want to give out for your new HostCache"
|
||||
@@ -63,6 +64,9 @@ public class HostCache {
|
||||
Timer timer = new Timer("timer", true)
|
||||
timer.schedule({hostPool.age()} as TimerTask, 1000,1000)
|
||||
timer.schedule({crawler.startCrawl()} as TimerTask, 10000, 10000)
|
||||
File verified = new File("verified.json")
|
||||
File unverified = new File("unverified.json")
|
||||
timer.schedule({hostPool.serialize(verified, unverified)} as TimerTask, 10000, 60 * 60 * 1000)
|
||||
|
||||
session.addMuxedSessionListener(new Listener(hostPool: hostPool, toReturn: 2, crawler: crawler),
|
||||
I2PSession.PROTO_DATAGRAM, I2PSession.PORT_ANY)
|
||||
|
@@ -2,6 +2,8 @@ package com.muwire.hostcache
|
||||
|
||||
import java.util.stream.Collectors
|
||||
|
||||
import groovy.json.JsonOutput
|
||||
|
||||
class HostPool {
|
||||
|
||||
final def maxFailures
|
||||
@@ -74,4 +76,25 @@ class HostPool {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
synchronized void serialize(File verifiedFile, File unverifiedFile) {
|
||||
write(verifiedFile, verified.values())
|
||||
write(unverifiedFile, unverified.values())
|
||||
}
|
||||
|
||||
private void write(File target, Collection hosts) {
|
||||
JsonOutput jsonOutput = new JsonOutput()
|
||||
target.withPrintWriter { writer ->
|
||||
hosts.each {
|
||||
def json = [:]
|
||||
json.destination = it.destination.toBase64()
|
||||
json.verifyTime = it.verifyTime
|
||||
json.leafSlots = it.leafSlots
|
||||
json.peerSlots = it.peerSlots
|
||||
json.verificationFailures = it.verificationFailures
|
||||
def str = jsonOutput.toJson(json)
|
||||
writer.println(str)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user