Compare commits
61 Commits
muwire-0.4
...
muwire-0.5
Author | SHA1 | Date | |
---|---|---|---|
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 |
10
README.md
10
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.
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
@ -19,18 +19,16 @@ If you want to run the unit tests, type
|
||||
./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
|
||||
|
||||
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 `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`
|
||||
|
||||
[Default I2CP port]\: `7654`
|
||||
|
||||
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.
|
||||
|
||||
### GPG Fingerprint
|
||||
|
||||
```
|
||||
|
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
|
||||
|
||||
##### 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.
|
||||
|
||||
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
|
||||
|
||||
* Wrapper of some kind for in-place upgrades
|
||||
* Download file sequentially
|
||||
* Multiple-selection download, Ctrl-A
|
||||
* Automatic adjustment of number of I2P tunnels
|
||||
|
@ -2,7 +2,7 @@ subprojects {
|
||||
apply plugin: 'groovy'
|
||||
|
||||
dependencies {
|
||||
compile 'net.i2p:i2p:0.9.41'
|
||||
compile 'net.i2p:i2p:0.9.42'
|
||||
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.11")
|
||||
core = new Core(props, home, "0.5.0")
|
||||
} 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.11")
|
||||
core = new Core(props, home, "0.5.0")
|
||||
} 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.41'
|
||||
compile 'net.i2p.client:mstreaming:0.9.41'
|
||||
compile 'net.i2p.client:streaming:0.9.41'
|
||||
compile 'net.i2p:router:0.9.42'
|
||||
compile 'net.i2p.client:mstreaming:0.9.42'
|
||||
compile 'net.i2p.client:streaming:0.9.42'
|
||||
|
||||
testCompile 'org.junit.jupiter:junit-jupiter-api:5.4.2'
|
||||
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,7 @@ import com.muwire.core.files.FileSharedEvent
|
||||
import com.muwire.core.files.FileUnsharedEvent
|
||||
import com.muwire.core.files.HasherService
|
||||
import com.muwire.core.files.PersisterService
|
||||
import com.muwire.core.files.UIPersistFilesEvent
|
||||
import com.muwire.core.files.AllFilesLoadedEvent
|
||||
import com.muwire.core.files.DirectoryUnsharedEvent
|
||||
import com.muwire.core.files.DirectoryWatcher
|
||||
@ -135,6 +136,7 @@ public class Core {
|
||||
} else {
|
||||
log.info("launching embedded router")
|
||||
Properties routerProps = new Properties()
|
||||
routerProps.setProperty("i2p.dir.base", home.getAbsolutePath())
|
||||
routerProps.setProperty("i2p.dir.config", home.getAbsolutePath())
|
||||
routerProps.setProperty("router.excludePeerCaps", "KLM")
|
||||
routerProps.setProperty("i2np.inboundKBytesPerSecond", String.valueOf(props.inBw))
|
||||
@ -222,6 +224,7 @@ public class Core {
|
||||
log.info "initializing persistence service"
|
||||
persisterService = new PersisterService(new File(home, "files.json"), eventBus, 60000, fileManager)
|
||||
eventBus.register(UILoadedEvent.class, persisterService)
|
||||
eventBus.register(UIPersistFilesEvent.class, persisterService)
|
||||
|
||||
log.info("initializing host cache")
|
||||
File hostStorage = new File(home, "hosts.json")
|
||||
@ -279,7 +282,7 @@ public class Core {
|
||||
i2pAcceptor, hostCache, trustService, searchManager, uploadManager, connectionEstablisher)
|
||||
|
||||
log.info("initializing directory watcher")
|
||||
directoryWatcher = new DirectoryWatcher(eventBus, fileManager)
|
||||
directoryWatcher = new DirectoryWatcher(eventBus, fileManager, home, props)
|
||||
eventBus.register(FileSharedEvent.class, directoryWatcher)
|
||||
eventBus.register(AllFilesLoadedEvent.class, directoryWatcher)
|
||||
eventBus.register(DirectoryUnsharedEvent.class, directoryWatcher)
|
||||
@ -287,6 +290,8 @@ public class Core {
|
||||
log.info("initializing hasher service")
|
||||
hasherService = new HasherService(new FileHasher(), eventBus, fileManager)
|
||||
eventBus.register(FileSharedEvent.class, hasherService)
|
||||
eventBus.register(FileUnsharedEvent.class, hasherService)
|
||||
eventBus.register(DirectoryUnsharedEvent.class, hasherService)
|
||||
|
||||
log.info("initializing trust subscriber")
|
||||
trustSubscriber = new TrustSubscriber(eventBus, i2pConnector, props)
|
||||
@ -361,7 +366,7 @@ public class Core {
|
||||
}
|
||||
}
|
||||
|
||||
Core core = new Core(props, home, "0.4.11")
|
||||
Core core = new Core(props, home, "0.5.0")
|
||||
core.startServices()
|
||||
|
||||
// ... at the end, sleep or execute script
|
||||
|
@ -6,11 +6,13 @@ import com.muwire.core.hostcache.CrawlerResponse
|
||||
import com.muwire.core.util.DataUtil
|
||||
|
||||
import net.i2p.data.Base64
|
||||
import net.i2p.util.ConcurrentHashSet
|
||||
|
||||
class MuWireSettings {
|
||||
|
||||
final boolean isLeaf
|
||||
boolean allowUntrusted
|
||||
boolean searchExtraHop
|
||||
boolean allowTrustLists
|
||||
int trustListInterval
|
||||
Set<Persona> trustSubscriptions
|
||||
@ -24,7 +26,7 @@ class MuWireSettings {
|
||||
boolean shareDownloadedFiles
|
||||
Set<String> watchedDirectories
|
||||
float downloadSequentialRatio
|
||||
int hostClearInterval
|
||||
int hostClearInterval, hostHopelessInterval, hostRejectInterval
|
||||
int meshExpiration
|
||||
boolean embeddedRouter
|
||||
int inBw, outBw
|
||||
@ -38,19 +40,22 @@ class MuWireSettings {
|
||||
MuWireSettings(Properties props) {
|
||||
isLeaf = Boolean.valueOf(props.get("leaf","false"))
|
||||
allowUntrusted = Boolean.valueOf(props.getProperty("allowUntrusted","true"))
|
||||
searchExtraHop = Boolean.valueOf(props.getProperty("searchExtraHop","false"))
|
||||
allowTrustLists = Boolean.valueOf(props.getProperty("allowTrustLists","true"))
|
||||
trustListInterval = Integer.valueOf(props.getProperty("trustListInterval","1"))
|
||||
crawlerResponse = CrawlerResponse.valueOf(props.get("crawlerResponse","REGISTERED"))
|
||||
nickname = props.getProperty("nickname","MuWireUser")
|
||||
downloadLocation = new File((String)props.getProperty("downloadLocation",
|
||||
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"))
|
||||
autoDownloadUpdate = Boolean.parseBoolean(props.getProperty("autoDownloadUpdate","true"))
|
||||
updateType = props.getProperty("updateType","jar")
|
||||
shareDownloadedFiles = Boolean.parseBoolean(props.getProperty("shareDownloadedFiles","true"))
|
||||
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"))
|
||||
embeddedRouter = Boolean.valueOf(props.getProperty("embeddedRouter","false"))
|
||||
inBw = Integer.valueOf(props.getProperty("inBw","256"))
|
||||
@ -74,6 +79,7 @@ class MuWireSettings {
|
||||
Properties props = new Properties()
|
||||
props.setProperty("leaf", isLeaf.toString())
|
||||
props.setProperty("allowUntrusted", allowUntrusted.toString())
|
||||
props.setProperty("searchExtraHop", String.valueOf(searchExtraHop))
|
||||
props.setProperty("allowTrustLists", String.valueOf(allowTrustLists))
|
||||
props.setProperty("trustListInterval", String.valueOf(trustListInterval))
|
||||
props.setProperty("crawlerResponse", crawlerResponse.toString())
|
||||
@ -86,6 +92,8 @@ class MuWireSettings {
|
||||
props.setProperty("shareDownloadedFiles", String.valueOf(shareDownloadedFiles))
|
||||
props.setProperty("downloadSequentialRatio", String.valueOf(downloadSequentialRatio))
|
||||
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("embeddedRouter", String.valueOf(embeddedRouter))
|
||||
props.setProperty("inBw", String.valueOf(inBw))
|
||||
@ -106,7 +114,7 @@ class MuWireSettings {
|
||||
}
|
||||
|
||||
private static Set<String> readEncodedSet(Properties props, String property) {
|
||||
Set<String> rv = new HashSet<>()
|
||||
Set<String> rv = new ConcurrentHashSet<>()
|
||||
if (props.containsKey(property)) {
|
||||
String[] encoded = props.getProperty(property).split(",")
|
||||
encoded.each { rv << DataUtil.readi18nString(Base64.decode(it)) }
|
||||
|
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 = "[\\*\\+\\-,\\.:;\\(\\)=_/\\\\\\!\\\"\\\'\\\$%\\|\\[\\]\\{\\}\\?]";
|
||||
|
||||
}
|
@ -31,7 +31,7 @@ class ConnectionEstablisher {
|
||||
final HostCache hostCache
|
||||
|
||||
final Timer timer
|
||||
final ExecutorService executor
|
||||
final ExecutorService executor, closer
|
||||
|
||||
final Set inProgress = new ConcurrentHashSet()
|
||||
|
||||
@ -51,6 +51,8 @@ class ConnectionEstablisher {
|
||||
rv.setName("connector-${System.currentTimeMillis()}")
|
||||
rv
|
||||
} as ThreadFactory)
|
||||
|
||||
closer = Executors.newSingleThreadExecutor()
|
||||
}
|
||||
|
||||
void start() {
|
||||
@ -60,6 +62,7 @@ class ConnectionEstablisher {
|
||||
void stop() {
|
||||
timer.cancel()
|
||||
executor.shutdownNow()
|
||||
closer.shutdown()
|
||||
}
|
||||
|
||||
private void connectIfNeeded() {
|
||||
@ -120,8 +123,10 @@ class ConnectionEstablisher {
|
||||
}
|
||||
|
||||
private void fail(Endpoint endpoint) {
|
||||
endpoint.close()
|
||||
eventBus.publish(new ConnectionEvent(endpoint: endpoint, incoming: false, leaf: false, status: ConnectionAttemptStatus.FAILED))
|
||||
closer.execute {
|
||||
endpoint.close()
|
||||
eventBus.publish(new ConnectionEvent(endpoint: endpoint, incoming: false, leaf: false, status: ConnectionAttemptStatus.FAILED))
|
||||
} as Runnable
|
||||
}
|
||||
|
||||
private void readK(Endpoint e) {
|
||||
@ -175,7 +180,7 @@ class ConnectionEstablisher {
|
||||
log.log(Level.WARNING,"Problem parsing post-rejection payload",ignore)
|
||||
} finally {
|
||||
// the end
|
||||
e.close()
|
||||
closer.execute({e.close()} as Runnable)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,7 +74,7 @@ public class DownloadManager {
|
||||
destinations.addAll(e.sources)
|
||||
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,
|
||||
infohash, pieceSize, connector, destinations,
|
||||
@ -122,8 +122,12 @@ public class DownloadManager {
|
||||
byte [] root = Base64.decode(json.hashRoot)
|
||||
infoHash = new InfoHash(root)
|
||||
}
|
||||
|
||||
boolean sequential = false
|
||||
if (json.sequential != null)
|
||||
sequential = json.sequential
|
||||
|
||||
Pieces pieces = getPieces(infoHash, (long)json.length, json.pieceSizePow2)
|
||||
Pieces pieces = getPieces(infoHash, (long)json.length, json.pieceSizePow2, sequential)
|
||||
|
||||
def downloader = new Downloader(eventBus, this, me, file, (long)json.length,
|
||||
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 nPieces = (int)(length / pieceSize)
|
||||
if (length % pieceSize != 0)
|
||||
nPieces++
|
||||
Mesh mesh = meshManager.getOrCreate(infoHash, nPieces)
|
||||
Mesh mesh = meshManager.getOrCreate(infoHash, nPieces, sequential)
|
||||
mesh.pieces
|
||||
}
|
||||
|
||||
@ -188,6 +192,9 @@ public class DownloadManager {
|
||||
json.hashRoot = Base64.encode(infoHash.getRoot())
|
||||
|
||||
json.paused = downloader.paused
|
||||
|
||||
json.sequential = downloader.pieces.ratio == 0f
|
||||
|
||||
writer.println(JsonOutput.toJson(json))
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ class Pieces {
|
||||
done = new BitSet(nPieces)
|
||||
claimed = new BitSet(nPieces)
|
||||
}
|
||||
|
||||
|
||||
synchronized int[] claim() {
|
||||
int claimedCardinality = claimed.cardinality()
|
||||
if (claimedCardinality == nPieces) {
|
||||
@ -30,7 +30,7 @@ class Pieces {
|
||||
}
|
||||
|
||||
// if fuller than ratio just do sequential
|
||||
if ( (1.0f * claimedCardinality) / nPieces > ratio) {
|
||||
if ( (1.0f * claimedCardinality) / nPieces >= ratio) {
|
||||
int rv = claimed.nextClearBit(0)
|
||||
claimed.set(rv)
|
||||
return [rv, partials.getOrDefault(rv, 0), 0]
|
||||
@ -59,7 +59,8 @@ class Pieces {
|
||||
return [rv, partials.getOrDefault(rv, 0), 1]
|
||||
}
|
||||
List<Integer> toList = availableCopy.toList()
|
||||
Collections.shuffle(toList)
|
||||
if (ratio > 0f)
|
||||
Collections.shuffle(toList)
|
||||
int rv = toList[0]
|
||||
claimed.set(rv)
|
||||
[rv, partials.getOrDefault(rv, 0), 0]
|
||||
|
@ -10,4 +10,5 @@ class UIDownloadEvent extends Event {
|
||||
UIResultEvent[] result
|
||||
Set<Destination> sources
|
||||
File target
|
||||
boolean sequential
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import java.nio.file.WatchService
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
import com.muwire.core.EventBus
|
||||
import com.muwire.core.MuWireSettings
|
||||
import com.muwire.core.SharedFile
|
||||
|
||||
import groovy.util.logging.Log
|
||||
@ -31,6 +32,8 @@ class DirectoryWatcher {
|
||||
kinds = [ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE]
|
||||
}
|
||||
|
||||
private final File home
|
||||
private final MuWireSettings muOptions
|
||||
private final EventBus eventBus
|
||||
private final FileManager fileManager
|
||||
private final Thread watcherThread, publisherThread
|
||||
@ -39,7 +42,9 @@ class DirectoryWatcher {
|
||||
private WatchService watchService
|
||||
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.fileManager = fileManager
|
||||
this.watcherThread = new Thread({watch() } as Runnable, "directory-watcher")
|
||||
@ -64,15 +69,28 @@ class DirectoryWatcher {
|
||||
void onFileSharedEvent(FileSharedEvent e) {
|
||||
if (!e.file.isDirectory())
|
||||
return
|
||||
Path path = e.file.getCanonicalFile().toPath()
|
||||
File canonical = e.file.getCanonicalFile()
|
||||
Path path = canonical.toPath()
|
||||
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) {
|
||||
WatchKey wk = watchedDirectories.remove(e.directory)
|
||||
wk?.cancel()
|
||||
|
||||
if (muOptions.watchedDirectories.remove(e.directory.toString()))
|
||||
saveMuSettings()
|
||||
}
|
||||
|
||||
private void saveMuSettings() {
|
||||
File muSettingsFile = new File(home, "MuWire.properties")
|
||||
muSettingsFile.withOutputStream {
|
||||
muOptions.write(it)
|
||||
}
|
||||
}
|
||||
|
||||
private void watch() {
|
||||
|
@ -11,6 +11,7 @@ class HasherService {
|
||||
final FileHasher hasher
|
||||
final EventBus eventBus
|
||||
final FileManager fileManager
|
||||
final Set<File> hashed = new HashSet<>()
|
||||
Executor executor
|
||||
|
||||
HasherService(FileHasher hasher, EventBus eventBus, FileManager fileManager) {
|
||||
@ -24,13 +25,22 @@ class HasherService {
|
||||
}
|
||||
|
||||
void onFileSharedEvent(FileSharedEvent evt) {
|
||||
if (fileManager.fileToSharedFile.containsKey(evt.file.getCanonicalFile()))
|
||||
File canonical = evt.file.getCanonicalFile()
|
||||
if (fileManager.fileToSharedFile.containsKey(canonical))
|
||||
return
|
||||
executor.execute( { -> process(evt.file) } as Runnable)
|
||||
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) {
|
||||
f = f.getCanonicalFile()
|
||||
if (f.isDirectory()) {
|
||||
f.listFiles().each {eventBus.publish new FileSharedEvent(file: it) }
|
||||
} else {
|
||||
|
@ -3,6 +3,9 @@ package com.muwire.core.files
|
||||
import java.nio.file.CopyOption
|
||||
import java.nio.file.Files
|
||||
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.stream.Collectors
|
||||
|
||||
@ -28,13 +31,16 @@ class PersisterService extends Service {
|
||||
final int interval
|
||||
final Timer timer
|
||||
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) {
|
||||
this.location = location
|
||||
this.listener = listener
|
||||
this.interval = interval
|
||||
this.fileManager = fileManager
|
||||
timer = new Timer("file persister", true)
|
||||
timer = new Timer("file persister timer", true)
|
||||
}
|
||||
|
||||
void stop() {
|
||||
@ -44,9 +50,16 @@ class PersisterService extends Service {
|
||||
void onUILoadedEvent(UILoadedEvent e) {
|
||||
timer.schedule({load()} as TimerTask, 1)
|
||||
}
|
||||
|
||||
void onUIPersistFilesEvent(UIPersistFilesEvent e) {
|
||||
persistFiles()
|
||||
}
|
||||
|
||||
void load() {
|
||||
Thread.currentThread().setPriority(Thread.MIN_PRIORITY)
|
||||
|
||||
if (location.exists() && location.isFile()) {
|
||||
int loaded = 0
|
||||
def slurper = new JsonSlurper()
|
||||
try {
|
||||
location.eachLine {
|
||||
@ -56,6 +69,9 @@ class PersisterService extends Service {
|
||||
if (event != null) {
|
||||
log.fine("loaded file $event.loadedFile.file")
|
||||
listener.publish event
|
||||
loaded++
|
||||
if (loaded % 10 == 0)
|
||||
Thread.sleep(20)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -109,44 +125,44 @@ class PersisterService extends Service {
|
||||
List sources = (List)json.sources
|
||||
Set<Destination> sourceSet = sources.stream().map({d -> new Destination(d.toString())}).collect Collectors.toSet()
|
||||
DownloadedFile df = new DownloadedFile(file, ih, pieceSize, sourceSet)
|
||||
df.setComment(json.comment)
|
||||
return new FileLoadedEvent(loadedFile : df)
|
||||
}
|
||||
|
||||
|
||||
SharedFile sf = new SharedFile(file, ih, pieceSize)
|
||||
sf.setComment(json.comment)
|
||||
return new FileLoadedEvent(loadedFile: sf)
|
||||
|
||||
}
|
||||
|
||||
private void persistFiles() {
|
||||
def sharedFiles = fileManager.getSharedFiles()
|
||||
persisterExecutor.submit( {
|
||||
def sharedFiles = fileManager.getSharedFiles()
|
||||
|
||||
File tmp = File.createTempFile("muwire-files", "tmp")
|
||||
tmp.deleteOnExit()
|
||||
tmp.withPrintWriter { writer ->
|
||||
sharedFiles.each { k, v ->
|
||||
def json = toJson(k,v)
|
||||
json = JsonOutput.toJson(json)
|
||||
writer.println json
|
||||
File tmp = File.createTempFile("muwire-files", "tmp")
|
||||
tmp.deleteOnExit()
|
||||
tmp.withPrintWriter { writer ->
|
||||
sharedFiles.each { k, v ->
|
||||
def json = toJson(k,v)
|
||||
json = JsonOutput.toJson(json)
|
||||
writer.println json
|
||||
}
|
||||
}
|
||||
}
|
||||
Files.copy(tmp.toPath(), location.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
||||
tmp.delete()
|
||||
Files.copy(tmp.toPath(), location.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
||||
tmp.delete()
|
||||
} as Runnable)
|
||||
}
|
||||
|
||||
private def toJson(File f, SharedFile sf) {
|
||||
def json = [:]
|
||||
json.file = Base64.encode DataUtil.encodei18nString(f.toString())
|
||||
json.file = sf.getB64EncodedFileName()
|
||||
json.length = sf.getCachedLength()
|
||||
InfoHash ih = sf.getInfoHash()
|
||||
json.infoHash = Base64.encode ih.getRoot()
|
||||
json.infoHash = sf.getB64EncodedHashRoot()
|
||||
json.pieceSize = sf.getPieceSize()
|
||||
byte [] tmp = new byte [32]
|
||||
json.hashList = []
|
||||
for (int i = 0;i < ih.getHashList().length / 32; i++) {
|
||||
System.arraycopy(ih.getHashList(), i * 32, tmp, 0, 32)
|
||||
json.hashList.add Base64.encode(tmp)
|
||||
}
|
||||
json.hashList = sf.getB64EncodedHashList()
|
||||
json.comment = sf.getComment()
|
||||
|
||||
if (sf instanceof DownloadedFile) {
|
||||
json.sources = sf.sources.stream().map( {d -> d.toBase64()}).collect(Collectors.toList())
|
||||
|
@ -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 Set<Destination> CACHES = [
|
||||
// zlatinb
|
||||
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() {
|
||||
|
@ -7,21 +7,35 @@ class Host {
|
||||
private static final int MAX_FAILURES = 3
|
||||
|
||||
final Destination destination
|
||||
private final int clearInterval
|
||||
private final int clearInterval, hopelessInterval, rejectionInterval
|
||||
int failures,successes
|
||||
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.clearInterval = clearInterval
|
||||
this.hopelessInterval = hopelessInterval
|
||||
this.rejectionInterval = rejectionInterval
|
||||
}
|
||||
|
||||
synchronized void onConnect() {
|
||||
|
||||
private void connectSuccessful() {
|
||||
failures = 0
|
||||
successes++
|
||||
lastAttempt = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
synchronized void onConnect() {
|
||||
connectSuccessful()
|
||||
lastSuccessfulAttempt = lastAttempt
|
||||
}
|
||||
|
||||
synchronized void onReject() {
|
||||
connectSuccessful()
|
||||
lastRejection = lastAttempt;
|
||||
}
|
||||
|
||||
synchronized void onFailure() {
|
||||
failures++
|
||||
successes = 0
|
||||
@ -40,7 +54,17 @@ class Host {
|
||||
failures = 0
|
||||
}
|
||||
|
||||
synchronized void canTryAgain() {
|
||||
System.currentTimeMillis() - lastAttempt > (clearInterval * 60 * 1000)
|
||||
synchronized boolean canTryAgain() {
|
||||
lastSuccessfulAttempt > 0 &&
|
||||
System.currentTimeMillis() - lastAttempt > (clearInterval * 60 * 1000)
|
||||
}
|
||||
|
||||
synchronized boolean isHopeless() {
|
||||
isFailed() &&
|
||||
System.currentTimeMillis() - lastSuccessfulAttempt > (hopelessInterval * 60 * 1000)
|
||||
}
|
||||
|
||||
synchronized boolean isRecentlyRejected() {
|
||||
System.currentTimeMillis() - lastRejection < (rejectionInterval * 60 * 1000)
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ class HostCache extends Service {
|
||||
hosts.get(e.destination).clearFailures()
|
||||
return
|
||||
}
|
||||
Host host = new Host(e.destination, settings.hostClearInterval)
|
||||
Host host = new Host(e.destination, settings.hostClearInterval, settings.hostHopelessInterval, settings.hostRejectInterval)
|
||||
if (allowHost(host)) {
|
||||
hosts.put(e.destination, host)
|
||||
}
|
||||
@ -64,15 +64,17 @@ class HostCache extends Service {
|
||||
Destination dest = e.endpoint.destination
|
||||
Host host = hosts.get(dest)
|
||||
if (host == null) {
|
||||
host = new Host(dest, settings.hostClearInterval)
|
||||
host = new Host(dest, settings.hostClearInterval, settings.hostHopelessInterval, settings.hostRejectInterval)
|
||||
hosts.put(dest, host)
|
||||
}
|
||||
|
||||
switch(e.status) {
|
||||
case ConnectionAttemptStatus.SUCCESSFUL:
|
||||
case ConnectionAttemptStatus.REJECTED:
|
||||
host.onConnect()
|
||||
break
|
||||
case ConnectionAttemptStatus.REJECTED:
|
||||
host.onReject()
|
||||
break
|
||||
case ConnectionAttemptStatus.FAILED:
|
||||
host.onFailure()
|
||||
break
|
||||
@ -82,6 +84,10 @@ class HostCache extends Service {
|
||||
List<Destination> getHosts(int n) {
|
||||
List<Destination> rv = new ArrayList<>(hosts.keySet())
|
||||
rv.retainAll {allowHost(hosts[it])}
|
||||
rv.removeAll {
|
||||
def h = hosts[it];
|
||||
(h.isFailed() && !h.canTryAgain()) || h.isRecentlyRejected()
|
||||
}
|
||||
if (rv.size() <= n)
|
||||
return rv
|
||||
Collections.shuffle(rv)
|
||||
@ -106,12 +112,16 @@ class HostCache extends Service {
|
||||
storage.eachLine {
|
||||
def entry = slurper.parseText(it)
|
||||
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.successes = Integer.valueOf(String.valueOf(entry.successes))
|
||||
if (entry.lastAttempt != null)
|
||||
host.lastAttempt = entry.lastAttempt
|
||||
if (allowHost(host))
|
||||
if (entry.lastSuccessfulAttempt != null)
|
||||
host.lastSuccessfulAttempt = entry.lastSuccessfulAttempt
|
||||
if (entry.lastRejection != null)
|
||||
host.lastRejection = entry.lastRejection
|
||||
if (allowHost(host))
|
||||
hosts.put(dest, host)
|
||||
}
|
||||
}
|
||||
@ -120,8 +130,6 @@ class HostCache extends Service {
|
||||
}
|
||||
|
||||
private boolean allowHost(Host host) {
|
||||
if (host.isFailed() && !host.canTryAgain())
|
||||
return false
|
||||
if (host.destination == myself)
|
||||
return false
|
||||
TrustLevel trust = trustService.getLevel(host.destination)
|
||||
@ -140,12 +148,14 @@ class HostCache extends Service {
|
||||
storage.delete()
|
||||
storage.withPrintWriter { writer ->
|
||||
hosts.each { dest, host ->
|
||||
if (allowHost(host)) {
|
||||
if (allowHost(host) && !host.isHopeless()) {
|
||||
def map = [:]
|
||||
map.destination = dest.toBase64()
|
||||
map.failures = host.failures
|
||||
map.successes = host.successes
|
||||
map.lastAttempt = host.lastAttempt
|
||||
map.lastSuccessfulAttempt = host.lastSuccessfulAttempt
|
||||
map.lastRejection = host.lastRejection
|
||||
def json = JsonOutput.toJson(map)
|
||||
writer.println json
|
||||
}
|
||||
|
@ -33,11 +33,12 @@ class MeshManager {
|
||||
meshes.get(infoHash)
|
||||
}
|
||||
|
||||
Mesh getOrCreate(InfoHash infoHash, int nPieces) {
|
||||
Mesh getOrCreate(InfoHash infoHash, int nPieces, boolean sequential) {
|
||||
synchronized(meshes) {
|
||||
if (meshes.containsKey(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)) {
|
||||
for (int i = 0; i < nPieces; i++)
|
||||
pieces.markDownloaded(i)
|
||||
|
@ -90,6 +90,10 @@ class ResultsParser {
|
||||
Set<Destination> sources = Collections.emptySet()
|
||||
if (json.sources != null)
|
||||
sources = json.sources.stream().map({new Destination(it)}).collect(Collectors.toSet())
|
||||
|
||||
String comment = null
|
||||
if (json.comment != null)
|
||||
comment = DataUtil.readi18nString(Base64.decode(json.comment))
|
||||
|
||||
return new UIResultEvent( sender : p,
|
||||
name : name,
|
||||
@ -97,6 +101,7 @@ class ResultsParser {
|
||||
infohash : new InfoHash(infoHash),
|
||||
pieceSize : pieceSize,
|
||||
sources : sources,
|
||||
comment : comment,
|
||||
uuid: uuid)
|
||||
} catch (Exception 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.I2PConnector
|
||||
import com.muwire.core.files.FileHasher
|
||||
import com.muwire.core.util.DataUtil
|
||||
import com.muwire.core.Persona
|
||||
|
||||
import java.nio.charset.StandardCharsets
|
||||
@ -60,13 +61,18 @@ class ResultsSender {
|
||||
Set<Destination> suggested = Collections.emptySet()
|
||||
if (it instanceof DownloadedFile)
|
||||
suggested = it.sources
|
||||
def comment = null
|
||||
if (it.getComment() != null) {
|
||||
comment = DataUtil.readi18nString(Base64.decode(it.getComment()))
|
||||
}
|
||||
def uiResultEvent = new UIResultEvent( sender : me,
|
||||
name : it.getFile().getName(),
|
||||
size : length,
|
||||
infohash : it.getInfoHash(),
|
||||
pieceSize : pieceSize,
|
||||
uuid : uuid,
|
||||
sources : suggested
|
||||
sources : suggested,
|
||||
comment : comment
|
||||
)
|
||||
eventBus.publish(uiResultEvent)
|
||||
}
|
||||
@ -121,6 +127,9 @@ class ResultsSender {
|
||||
|
||||
if (it instanceof DownloadedFile)
|
||||
obj.sources = it.sources.stream().map({dest -> dest.toBase64()}).collect(Collectors.toSet())
|
||||
|
||||
if (it.getComment() != null)
|
||||
obj.comment = it.getComment()
|
||||
|
||||
def json = jsonOutput.toJson(obj)
|
||||
os.writeShort((short)json.length())
|
||||
|
@ -1,6 +1,6 @@
|
||||
package com.muwire.core.search
|
||||
|
||||
import com.muwire.core.Constants
|
||||
import com.muwire.core.SplitPattern
|
||||
|
||||
class SearchIndex {
|
||||
|
||||
@ -32,7 +32,7 @@ class SearchIndex {
|
||||
}
|
||||
|
||||
private static String[] split(String source) {
|
||||
source = source.replaceAll(Constants.SPLIT_PATTERN, " ").toLowerCase()
|
||||
source = source.replaceAll(SplitPattern.SPLIT_PATTERN, " ").toLowerCase()
|
||||
String [] split = source.split(" ")
|
||||
def rv = []
|
||||
split.each { if (it.length() > 0) rv << it }
|
||||
|
@ -14,6 +14,7 @@ class UIResultEvent extends Event {
|
||||
long size
|
||||
InfoHash infohash
|
||||
int pieceSize
|
||||
String comment
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
@ -3,5 +3,5 @@ package com.muwire.core.update
|
||||
import net.i2p.data.Destination
|
||||
|
||||
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==")
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ public class UploadManager {
|
||||
pieceSize = downloader.pieceSizePow2
|
||||
} else {
|
||||
SharedFile sharedFile = sharedFiles.iterator().next();
|
||||
mesh = meshManager.getOrCreate(request.infoHash, sharedFile.NPieces)
|
||||
mesh = meshManager.getOrCreate(request.infoHash, sharedFile.NPieces, false)
|
||||
file = sharedFile.file
|
||||
pieceSize = sharedFile.pieceSize
|
||||
}
|
||||
@ -217,7 +217,7 @@ public class UploadManager {
|
||||
pieceSize = downloader.pieceSizePow2
|
||||
} else {
|
||||
SharedFile sharedFile = sharedFiles.iterator().next();
|
||||
mesh = meshManager.getOrCreate(request.infoHash, sharedFile.NPieces)
|
||||
mesh = meshManager.getOrCreate(request.infoHash, sharedFile.NPieces, false)
|
||||
file = sharedFile.file
|
||||
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.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.muwire.core.util.DataUtil;
|
||||
|
||||
import net.i2p.data.Base64;
|
||||
|
||||
public class SharedFile {
|
||||
|
||||
@ -11,6 +17,12 @@ public class SharedFile {
|
||||
|
||||
private final String cachedPath;
|
||||
private final long cachedLength;
|
||||
|
||||
private final String b64EncodedFileName;
|
||||
private final String b64EncodedHashRoot;
|
||||
private final List<String> b64EncodedHashList;
|
||||
|
||||
private volatile String comment;
|
||||
|
||||
public SharedFile(File file, InfoHash infoHash, int pieceSize) throws IOException {
|
||||
this.file = file;
|
||||
@ -18,6 +30,16 @@ public class SharedFile {
|
||||
this.pieceSize = pieceSize;
|
||||
this.cachedPath = file.getAbsolutePath();
|
||||
this.cachedLength = file.length();
|
||||
this.b64EncodedFileName = Base64.encode(DataUtil.encodei18nString(file.toString()));
|
||||
this.b64EncodedHashRoot = Base64.encode(infoHash.getRoot());
|
||||
|
||||
List<String> b64List = new ArrayList<String>();
|
||||
byte[] tmp = new byte[32];
|
||||
for (int i = 0; i < infoHash.getHashList().length / 32; i++) {
|
||||
System.arraycopy(infoHash.getHashList(), i * 32, tmp, 0, 32);
|
||||
b64List.add(Base64.encode(tmp));
|
||||
}
|
||||
this.b64EncodedHashList = b64List;
|
||||
}
|
||||
|
||||
public File getFile() {
|
||||
@ -40,6 +62,18 @@ public class SharedFile {
|
||||
rv++;
|
||||
return rv;
|
||||
}
|
||||
|
||||
public String getB64EncodedFileName() {
|
||||
return b64EncodedFileName;
|
||||
}
|
||||
|
||||
public String getB64EncodedHashRoot() {
|
||||
return b64EncodedHashRoot;
|
||||
}
|
||||
|
||||
public List<String> getB64EncodedHashList() {
|
||||
return b64EncodedHashList;
|
||||
}
|
||||
|
||||
public String getCachedPath() {
|
||||
return cachedPath;
|
||||
@ -48,6 +82,14 @@ public class SharedFile {
|
||||
public long getCachedLength() {
|
||||
return cachedLength;
|
||||
}
|
||||
|
||||
public void setComment(String comment) {
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
|
@ -1,122 +1,134 @@
|
||||
package com.muwire.core.util
|
||||
package com.muwire.core.util;
|
||||
|
||||
import java.lang.reflect.Field
|
||||
import java.lang.reflect.Method
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.muwire.core.Constants
|
||||
import 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)
|
||||
throw new IllegalArgumentException("$value invalid")
|
||||
throw new IllegalArgumentException("$value invalid");
|
||||
|
||||
byte lsb = (byte) (value & 0xFF)
|
||||
byte msb = (byte) (value >> 8)
|
||||
byte lsb = (byte) (value & 0xFF);
|
||||
byte msb = (byte) (value >> 8);
|
||||
|
||||
os.write(msb)
|
||||
os.write(lsb)
|
||||
os.write(msb);
|
||||
os.write(lsb);
|
||||
}
|
||||
|
||||
private final static int MAX_HEADER = 0x7FFFFF
|
||||
private final static int MAX_HEADER = 0x7FFFFF;
|
||||
|
||||
static void packHeader(int length, byte [] header) {
|
||||
if (header.length != 3)
|
||||
throw new IllegalArgumentException("header length $header.length")
|
||||
throw new IllegalArgumentException("header length $header.length");
|
||||
if (length < 0 || length > MAX_HEADER)
|
||||
throw new IllegalArgumentException("length $length")
|
||||
throw new IllegalArgumentException("length $length");
|
||||
|
||||
header[2] = (byte) (length & 0xFF)
|
||||
header[1] = (byte) ((length >> 8) & 0xFF)
|
||||
header[0] = (byte) ((length >> 16) & 0x7F)
|
||||
header[2] = (byte) (length & 0xFF);
|
||||
header[1] = (byte) ((length >> 8) & 0xFF);
|
||||
header[0] = (byte) ((length >> 16) & 0x7F);
|
||||
}
|
||||
|
||||
static int readLength(byte [] header) {
|
||||
if (header.length != 3)
|
||||
throw new IllegalArgumentException("header length $header.length")
|
||||
throw new IllegalArgumentException("header length $header.length");
|
||||
|
||||
return (((int)(header[0] & 0x7F)) << 16) |
|
||||
(((int)(header[1] & 0xFF) << 8)) |
|
||||
((int)header[2] & 0xFF)
|
||||
((int)header[2] & 0xFF);
|
||||
}
|
||||
|
||||
static String readi18nString(byte [] encoded) {
|
||||
if (encoded.length < 2)
|
||||
throw new IllegalArgumentException("encoding too short $encoded.length")
|
||||
int length = ((encoded[0] & 0xFF) << 8) | (encoded[1] & 0xFF)
|
||||
throw new IllegalArgumentException("encoding too short $encoded.length");
|
||||
int length = ((encoded[0] & 0xFF) << 8) | (encoded[1] & 0xFF);
|
||||
if (encoded.length != length + 2)
|
||||
throw new IllegalArgumentException("encoding doesn't match length, expected $length found $encoded.length")
|
||||
byte [] string = new byte[length]
|
||||
System.arraycopy(encoded, 2, string, 0, length)
|
||||
new String(string, StandardCharsets.UTF_8)
|
||||
throw new IllegalArgumentException("encoding doesn't match length, expected $length found $encoded.length");
|
||||
byte [] string = new byte[length];
|
||||
System.arraycopy(encoded, 2, string, 0, length);
|
||||
return new String(string, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
static byte[] encodei18nString(String string) {
|
||||
byte [] utf8 = string.getBytes(StandardCharsets.UTF_8)
|
||||
public static byte[] encodei18nString(String string) {
|
||||
byte [] utf8 = string.getBytes(StandardCharsets.UTF_8);
|
||||
if (utf8.length > Short.MAX_VALUE)
|
||||
throw new IllegalArgumentException("String in utf8 too long $utf8.length")
|
||||
def baos = new ByteArrayOutputStream()
|
||||
def daos = new DataOutputStream(baos)
|
||||
daos.writeShort((short) utf8.length)
|
||||
daos.write(utf8)
|
||||
daos.close()
|
||||
baos.toByteArray()
|
||||
throw new IllegalArgumentException("String in utf8 too long $utf8.length");
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
DataOutputStream daos = new DataOutputStream(baos);
|
||||
try {
|
||||
daos.writeShort((short) utf8.length);
|
||||
daos.write(utf8);
|
||||
daos.close();
|
||||
} catch (IOException impossible) {
|
||||
throw new IllegalStateException(impossible);
|
||||
}
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
public static String readTillRN(InputStream is) {
|
||||
def baos = new ByteArrayOutputStream()
|
||||
public static String readTillRN(InputStream is) throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
while(baos.size() < (Constants.MAX_HEADER_SIZE)) {
|
||||
byte read = is.read()
|
||||
int read = is.read();
|
||||
if (read == -1)
|
||||
throw new IOException()
|
||||
throw new IOException();
|
||||
if (read == '\r') {
|
||||
if (is.read() != '\n')
|
||||
throw new IOException("invalid header")
|
||||
break
|
||||
throw new IOException("invalid header");
|
||||
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) {
|
||||
int bytes = totalPieces / 8
|
||||
int bytes = totalPieces / 8;
|
||||
if (totalPieces % 8 != 0)
|
||||
bytes++
|
||||
byte[] raw = new byte[bytes]
|
||||
pieces.each {
|
||||
int byteIdx = it / 8
|
||||
int offset = it % 8
|
||||
int mask = 0x80 >>> offset
|
||||
raw[byteIdx] |= mask
|
||||
bytes++;
|
||||
byte[] raw = new byte[bytes];
|
||||
for (int it : pieces) {
|
||||
int byteIdx = it / 8;
|
||||
int offset = it % 8;
|
||||
int mask = 0x80 >>> offset;
|
||||
raw[byteIdx] |= mask;
|
||||
}
|
||||
Base64.encode(raw)
|
||||
return Base64.encode(raw);
|
||||
}
|
||||
|
||||
public static List<Integer> decodeXHave(String xHave) {
|
||||
byte [] availablePieces = Base64.decode(xHave)
|
||||
List<Integer> available = new ArrayList<>()
|
||||
availablePieces.eachWithIndex {b, i ->
|
||||
byte [] availablePieces = Base64.decode(xHave);
|
||||
List<Integer> available = new ArrayList<>();
|
||||
for (int i = 0; i < availablePieces.length; i ++) {
|
||||
byte b = availablePieces[i];
|
||||
for (int j = 0; j < 8 ; j++) {
|
||||
byte mask = 0x80 >>> j
|
||||
byte mask = (byte) (0x80 >>> j);
|
||||
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)
|
||||
e = e.getCause()
|
||||
e
|
||||
e = e.getCause();
|
||||
return e;
|
||||
}
|
||||
|
||||
public static void tryUnmap(ByteBuffer cb) {
|
@ -4,6 +4,7 @@ import static org.junit.Assert.fail
|
||||
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
|
||||
import com.muwire.core.EventBus
|
||||
@ -180,10 +181,11 @@ class DownloadSessionTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore // this needs to be rewritten with stealing in mind
|
||||
public void testSmallFileClaimed() {
|
||||
initSession(20, [0])
|
||||
long now = System.currentTimeMillis()
|
||||
downloadThread.join(100)
|
||||
downloadThread.join(150)
|
||||
assert 100 >= (System.currentTimeMillis() - now)
|
||||
assert !performed
|
||||
assert available.isEmpty()
|
||||
|
@ -16,7 +16,7 @@ class PiecesTest {
|
||||
public void testSinglePiece() {
|
||||
pieces = new Pieces(1)
|
||||
assert !pieces.isComplete()
|
||||
assert pieces.claim() == 0
|
||||
assert pieces.claim() == [0,0,0]
|
||||
pieces.markDownloaded(0)
|
||||
assert pieces.isComplete()
|
||||
}
|
||||
@ -25,28 +25,28 @@ class PiecesTest {
|
||||
public void testTwoPieces() {
|
||||
pieces = new Pieces(2)
|
||||
assert !pieces.isComplete()
|
||||
int piece = pieces.claim()
|
||||
assert piece == 0 || piece == 1
|
||||
pieces.markDownloaded(piece)
|
||||
int[] piece = pieces.claim()
|
||||
assert piece[0] == 0 || piece[0] == 1
|
||||
pieces.markDownloaded(piece[0])
|
||||
assert !pieces.isComplete()
|
||||
int piece2 = pieces.claim()
|
||||
assert piece != piece2
|
||||
pieces.markDownloaded(piece2)
|
||||
int[] piece2 = pieces.claim()
|
||||
assert piece[0] != piece2[0]
|
||||
pieces.markDownloaded(piece2[0])
|
||||
assert pieces.isComplete()
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClaimAvailable() {
|
||||
pieces = new Pieces(2)
|
||||
int claimed = pieces.claim([0].toSet())
|
||||
assert claimed == 0
|
||||
assert -1 == pieces.claim([0].toSet())
|
||||
int[] claimed = pieces.claim([0].toSet())
|
||||
assert claimed == [0,0,0]
|
||||
assert [0,0,1] == pieces.claim([0].toSet())
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClaimNoneAvailable() {
|
||||
pieces = new Pieces(20)
|
||||
int claimed = pieces.claim()
|
||||
assert -1 == pieces.claim([claimed].toSet())
|
||||
int[] claimed = pieces.claim()
|
||||
assert [0,0,0] == pieces.claim(claimed.toSet())
|
||||
}
|
||||
}
|
||||
|
@ -72,6 +72,9 @@ class HostCacheTest {
|
||||
TrustLevel.NEUTRAL
|
||||
}
|
||||
settingsMock.ignore.allowUntrusted { true }
|
||||
settingsMock.ignore.getHostClearInterval { 0 }
|
||||
settingsMock.ignore.getHostHopelessInterval { 0 }
|
||||
settingsMock.ignore.getHostRejectInterval { 0 }
|
||||
|
||||
initMocks()
|
||||
|
||||
@ -91,6 +94,10 @@ class HostCacheTest {
|
||||
TrustLevel.DISTRUSTED
|
||||
}
|
||||
|
||||
settingsMock.ignore.getHostClearInterval { 0 }
|
||||
settingsMock.ignore.getHostHopelessInterval { 0 }
|
||||
settingsMock.ignore.getHostRejectInterval { 0 }
|
||||
|
||||
initMocks()
|
||||
|
||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||
@ -104,6 +111,9 @@ class HostCacheTest {
|
||||
TrustLevel.NEUTRAL
|
||||
}
|
||||
settingsMock.ignore.allowUntrusted { false }
|
||||
settingsMock.ignore.getHostClearInterval { 0 }
|
||||
settingsMock.ignore.getHostHopelessInterval { 0 }
|
||||
settingsMock.ignore.getHostRejectInterval { 0 }
|
||||
|
||||
initMocks()
|
||||
|
||||
@ -123,6 +133,9 @@ class HostCacheTest {
|
||||
}
|
||||
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()
|
||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||
@ -139,7 +152,15 @@ class HostCacheTest {
|
||||
assert d == destinations.dest1
|
||||
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()
|
||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||
|
||||
@ -158,6 +179,10 @@ class HostCacheTest {
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
|
||||
settingsMock.ignore.getHostClearInterval { 0 }
|
||||
settingsMock.ignore.getHostHopelessInterval { 0 }
|
||||
settingsMock.ignore.getHostRejectInterval { 0 }
|
||||
|
||||
initMocks()
|
||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||
|
||||
@ -183,6 +208,10 @@ class HostCacheTest {
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
|
||||
settingsMock.ignore.getHostClearInterval { 0 }
|
||||
settingsMock.ignore.getHostHopelessInterval { 0 }
|
||||
settingsMock.ignore.getHostRejectInterval { 0 }
|
||||
|
||||
initMocks()
|
||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||
|
||||
@ -214,6 +243,10 @@ class HostCacheTest {
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
|
||||
settingsMock.ignore.getHostClearInterval { 0 }
|
||||
settingsMock.ignore.getHostHopelessInterval { 0 }
|
||||
settingsMock.ignore.getHostRejectInterval { 0 }
|
||||
|
||||
initMocks()
|
||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||
@ -229,6 +262,11 @@ class HostCacheTest {
|
||||
assert d == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
|
||||
settingsMock.ignore.getHostClearInterval { 0 }
|
||||
settingsMock.ignore.getHostHopelessInterval { 0 }
|
||||
settingsMock.ignore.getHostRejectInterval { 0 }
|
||||
|
||||
initMocks()
|
||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||
Thread.sleep(150)
|
||||
@ -260,6 +298,10 @@ class HostCacheTest {
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
|
||||
settingsMock.ignore.getHostClearInterval { 0 }
|
||||
settingsMock.ignore.getHostHopelessInterval { 0 }
|
||||
settingsMock.ignore.getHostRejectInterval { 0 }
|
||||
|
||||
initMocks()
|
||||
def rv = cache.getHosts(5)
|
||||
assert rv.size() == 1
|
||||
|
@ -9,6 +9,9 @@ import org.junit.Test
|
||||
|
||||
import com.muwire.core.InfoHash
|
||||
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 {
|
||||
|
||||
@ -52,7 +55,13 @@ class UploaderTest {
|
||||
}
|
||||
|
||||
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.setDaemon(true)
|
||||
uploadThread.start()
|
||||
@ -81,6 +90,7 @@ class UploaderTest {
|
||||
startUpload()
|
||||
assert "200 OK" == readUntilRN()
|
||||
assert "Content-Range: 0-19" == readUntilRN()
|
||||
assert readUntilRN().startsWith("X-Have")
|
||||
assert "" == readUntilRN()
|
||||
|
||||
byte [] data = new byte[20]
|
||||
@ -96,6 +106,7 @@ class UploaderTest {
|
||||
startUpload()
|
||||
assert "200 OK" == readUntilRN()
|
||||
assert "Content-Range: 5-15" == readUntilRN()
|
||||
assert readUntilRN().startsWith("X-Have")
|
||||
assert "" == readUntilRN()
|
||||
|
||||
byte [] data = new byte[11]
|
||||
@ -111,6 +122,7 @@ class UploaderTest {
|
||||
request = new ContentRequest(range : new Range(0,20))
|
||||
startUpload()
|
||||
assert "416 Range Not Satisfiable" == readUntilRN()
|
||||
assert readUntilRN().startsWith("X-Have")
|
||||
assert "" == readUntilRN()
|
||||
}
|
||||
|
||||
@ -123,6 +135,7 @@ class UploaderTest {
|
||||
readUntilRN()
|
||||
readUntilRN()
|
||||
readUntilRN()
|
||||
readUntilRN()
|
||||
|
||||
byte [] data = new byte[length]
|
||||
DataInputStream dis = new DataInputStream(is)
|
||||
|
@ -1,5 +1,5 @@
|
||||
group = com.muwire
|
||||
version = 0.4.11
|
||||
version = 0.5.0
|
||||
groovyVersion = 2.4.15
|
||||
slf4jVersion = 1.7.25
|
||||
spockVersion = 1.1-groovy-2.4
|
||||
|
@ -44,9 +44,9 @@ mainClassName = 'com.muwire.gui.Launcher'
|
||||
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties']
|
||||
|
||||
apply from: 'gradle/publishing.gradle'
|
||||
apply from: 'gradle/code-coverage.gradle'
|
||||
apply from: 'gradle/code-quality.gradle'
|
||||
apply from: 'gradle/integration-test.gradle'
|
||||
// apply from: 'gradle/code-coverage.gradle'
|
||||
// apply from: 'gradle/code-quality.gradle'
|
||||
// apply from: 'gradle/integration-test.gradle'
|
||||
// apply from: 'gradle/package.gradle'
|
||||
apply from: 'gradle/docs.gradle'
|
||||
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]) {
|
||||
executionData = files(jacocoTestReport.executionData, jacocoIntegrationTestReport.executionData)
|
||||
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")
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
BIN
gui/griffon-app/.DS_Store
vendored
Normal file
BIN
gui/griffon-app/.DS_Store
vendored
Normal file
Binary file not shown.
@ -46,4 +46,14 @@ mvcGroups {
|
||||
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'
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,39 @@
|
||||
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.util.DataUtil
|
||||
|
||||
@ArtifactProviderFor(GriffonController)
|
||||
class AddCommentController {
|
||||
@MVCMember @Nonnull
|
||||
AddCommentModel model
|
||||
@MVCMember @Nonnull
|
||||
AddCommentView view
|
||||
|
||||
@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 {
|
||||
it.setComment(comment)
|
||||
}
|
||||
mvcGroup.parentGroup.view.refreshSharedFiles()
|
||||
cancel()
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void cancel() {
|
||||
view.dialog.setVisible(false)
|
||||
mvcGroup.destroy()
|
||||
}
|
||||
}
|
@ -68,6 +68,16 @@ class ContentPanelController {
|
||||
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()
|
||||
|
@ -13,10 +13,10 @@ import javax.annotation.Nonnull
|
||||
import javax.inject.Inject
|
||||
import javax.swing.JTable
|
||||
|
||||
import com.muwire.core.Constants
|
||||
import com.muwire.core.Core
|
||||
import com.muwire.core.Persona
|
||||
import com.muwire.core.SharedFile
|
||||
import com.muwire.core.SplitPattern
|
||||
import com.muwire.core.download.Downloader
|
||||
import com.muwire.core.download.DownloadStartedEvent
|
||||
import com.muwire.core.download.UIDownloadCancelledEvent
|
||||
@ -25,6 +25,7 @@ import com.muwire.core.download.UIDownloadPausedEvent
|
||||
import com.muwire.core.download.UIDownloadResumedEvent
|
||||
import com.muwire.core.files.DirectoryUnsharedEvent
|
||||
import com.muwire.core.files.FileUnsharedEvent
|
||||
import com.muwire.core.files.UIPersistFilesEvent
|
||||
import com.muwire.core.search.QueryEvent
|
||||
import com.muwire.core.search.SearchEvent
|
||||
import com.muwire.core.trust.RemoteTrustList
|
||||
@ -80,13 +81,14 @@ class MainFrameController {
|
||||
searchEvent = new SearchEvent(searchHash : root, uuid : uuid, oobInfohash: true)
|
||||
} else {
|
||||
// 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 nonEmpty = []
|
||||
terms.each { if (it.length() > 0) nonEmpty << it }
|
||||
searchEvent = new SearchEvent(searchTerms : nonEmpty, uuid : uuid, oobInfohash: true)
|
||||
}
|
||||
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,
|
||||
originator : core.me))
|
||||
}
|
||||
@ -269,22 +271,24 @@ class MainFrameController {
|
||||
}
|
||||
|
||||
void unshareSelectedFile() {
|
||||
SharedFile sf = view.selectedSharedFile()
|
||||
def sf = view.selectedSharedFiles()
|
||||
if (sf == null)
|
||||
return
|
||||
core.eventBus.publish(new FileUnsharedEvent(unsharedFile : sf))
|
||||
sf.each {
|
||||
core.eventBus.publish(new FileUnsharedEvent(unsharedFile : it))
|
||||
}
|
||||
core.eventBus.publish(new UIPersistFilesEvent())
|
||||
}
|
||||
|
||||
void stopWatchingDirectory() {
|
||||
String directory = mvcGroup.view.getSelectedWatchedDirectory()
|
||||
if (directory == null)
|
||||
|
||||
@ControllerAction
|
||||
void addComment() {
|
||||
def selectedFiles = view.selectedSharedFiles()
|
||||
if (selectedFiles == null || selectedFiles.isEmpty())
|
||||
return
|
||||
core.muOptions.watchedDirectories.remove(directory)
|
||||
saveMuWireSettings()
|
||||
core.eventBus.publish(new DirectoryUnsharedEvent(directory : new File(directory)))
|
||||
|
||||
model.watched.remove(directory)
|
||||
builder.getVariable("watched-directories-table").model.fireTableDataChanged()
|
||||
|
||||
Map<String, Object> params = new HashMap<>()
|
||||
params['selectedFiles'] = selectedFiles
|
||||
mvcGroup.createMVCGroup("add-comment", "Add Comment", params)
|
||||
}
|
||||
|
||||
void saveMuWireSettings() {
|
||||
|
@ -96,6 +96,10 @@ class OptionsController {
|
||||
model.onlyTrusted = onlyTrusted
|
||||
settings.setAllowUntrusted(!onlyTrusted)
|
||||
|
||||
boolean searchExtraHop = view.searchExtraHopCheckbox.model.isSelected()
|
||||
model.searchExtraHop = searchExtraHop
|
||||
settings.searchExtraHop = searchExtraHop
|
||||
|
||||
boolean trustLists = view.allowTrustListsCheckbox.model.isSelected()
|
||||
model.trustLists = trustLists
|
||||
settings.allowTrustLists = trustLists
|
||||
@ -139,7 +143,7 @@ class OptionsController {
|
||||
// boolean showSearchHashes = view.showSearchHashesCheckbox.model.isSelected()
|
||||
// model.showSearchHashes = showSearchHashes
|
||||
// uiSettings.showSearchHashes = showSearchHashes
|
||||
|
||||
|
||||
File uiSettingsFile = new File(core.home, "gui.properties")
|
||||
uiSettingsFile.withOutputStream {
|
||||
uiSettings.write(it)
|
||||
@ -163,5 +167,5 @@ class OptionsController {
|
||||
int rv = chooser.showOpenDialog(null)
|
||||
if (rv == JFileChooser.APPROVE_OPTION)
|
||||
model.downloadLocation = chooser.getSelectedFile().getAbsolutePath()
|
||||
}
|
||||
}
|
||||
}
|
@ -8,63 +8,81 @@ import javax.annotation.Nonnull
|
||||
|
||||
import com.muwire.core.Core
|
||||
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)
|
||||
class SearchTabController {
|
||||
|
||||
|
||||
@MVCMember @Nonnull
|
||||
SearchTabModel model
|
||||
@MVCMember @Nonnull
|
||||
SearchTabView view
|
||||
|
||||
|
||||
Core core
|
||||
|
||||
private def selectedResult() {
|
||||
int row = view.resultsTable.getSelectedRow()
|
||||
if (row == -1)
|
||||
|
||||
private def selectedResults() {
|
||||
int[] rows = view.resultsTable.getSelectedRows()
|
||||
if (rows.length == 0)
|
||||
return null
|
||||
def sortEvt = view.lastSortEvent
|
||||
if (sortEvt != null) {
|
||||
row = view.resultsTable.rowSorter.convertRowIndexToModel(row)
|
||||
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
|
||||
}
|
||||
model.results[row]
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void download() {
|
||||
def result = selectedResult()
|
||||
if (result == null)
|
||||
return
|
||||
|
||||
if (!mvcGroup.parentGroup.model.canDownload(result.infohash))
|
||||
return
|
||||
@ControllerAction
|
||||
void download() {
|
||||
def results = selectedResults()
|
||||
if (results == null)
|
||||
return
|
||||
|
||||
def file = new File(application.context.get("muwire-settings").downloadLocation, result.name)
|
||||
results.removeAll {
|
||||
!mvcGroup.parentGroup.model.canDownload(it.infohash)
|
||||
}
|
||||
|
||||
def resultsBucket = model.hashBucket[result.infohash]
|
||||
def sources = model.sourcesBucket[result.infohash]
|
||||
results.each { result ->
|
||||
def file = new File(application.context.get("muwire-settings").downloadLocation, result.name)
|
||||
|
||||
core.eventBus.publish(new UIDownloadEvent(result : resultsBucket, sources: sources, target : file))
|
||||
mvcGroup.parentGroup.view.showDownloadsWindow.call()
|
||||
}
|
||||
def resultsBucket = model.hashBucket[result.infohash]
|
||||
def sources = model.sourcesBucket[result.infohash]
|
||||
|
||||
@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))
|
||||
}
|
||||
core.eventBus.publish(new UIDownloadEvent(result : resultsBucket, sources: sources,
|
||||
target : file, sequential : view.sequentialDownloadCheckbox.model.isSelected()))
|
||||
}
|
||||
mvcGroup.parentGroup.view.showDownloadsWindow.call()
|
||||
}
|
||||
|
||||
@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 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))
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -43,7 +43,7 @@ class Initialize extends AbstractLifecycleHandler {
|
||||
|
||||
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")
|
||||
UISettings uiSettings
|
||||
@ -86,6 +86,8 @@ class Initialize extends AbstractLifecycleHandler {
|
||||
}
|
||||
} else {
|
||||
LookAndFeel chosen = lookAndFeel('system', 'gtk')
|
||||
if (chosen == null)
|
||||
chosen = lookAndFeel('metal')
|
||||
uiSettings.lnf = chosen.getID()
|
||||
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
|
||||
}
|
@ -40,11 +40,15 @@ class ContentPanelModel {
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -1,6 +1,8 @@
|
||||
package com.muwire.gui
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.util.Calendar
|
||||
import java.util.UUID
|
||||
|
||||
@ -8,12 +10,16 @@ import javax.annotation.Nonnull
|
||||
import javax.inject.Inject
|
||||
import javax.swing.JOptionPane
|
||||
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.InfoHash
|
||||
import com.muwire.core.MuWireSettings
|
||||
import com.muwire.core.Persona
|
||||
import com.muwire.core.RouterDisconnectedEvent
|
||||
import com.muwire.core.SharedFile
|
||||
import com.muwire.core.connection.ConnectionAttemptStatus
|
||||
import com.muwire.core.connection.ConnectionEvent
|
||||
import com.muwire.core.connection.DisconnectionEvent
|
||||
@ -21,6 +27,7 @@ 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.DirectoryUnsharedEvent
|
||||
import com.muwire.core.files.FileDownloadedEvent
|
||||
import com.muwire.core.files.FileHashedEvent
|
||||
import com.muwire.core.files.FileHashingEvent
|
||||
@ -57,6 +64,8 @@ class MainFrameModel {
|
||||
FactoryBuilderSupport builder
|
||||
@MVCMember @Nonnull
|
||||
MainFrameController controller
|
||||
@MVCMember @Nonnull
|
||||
MainFrameView view
|
||||
@Inject @Nonnull GriffonApplication application
|
||||
@Observable boolean coreInitialized = false
|
||||
@Observable boolean routerPresent
|
||||
@ -64,8 +73,11 @@ class MainFrameModel {
|
||||
def results = new ConcurrentHashMap<>()
|
||||
def downloads = []
|
||||
def uploads = []
|
||||
def shared = []
|
||||
def watched = []
|
||||
boolean treeVisible = true
|
||||
def shared
|
||||
def sharedTree
|
||||
def treeRoot
|
||||
final Map<SharedFile, TreeNode> fileToNode = new HashMap<>()
|
||||
def connectionList = []
|
||||
def searches = new LinkedList()
|
||||
def trusted = []
|
||||
@ -81,6 +93,7 @@ class MainFrameModel {
|
||||
@Observable boolean pauseButtonEnabled
|
||||
@Observable boolean clearButtonEnabled
|
||||
@Observable String resumeButtonText
|
||||
@Observable boolean addCommentButtonEnabled
|
||||
@Observable boolean subscribeButtonEnabled
|
||||
@Observable boolean markNeutralFromTrustedButtonEnabled
|
||||
@Observable boolean markDistrustedButtonEnabled
|
||||
@ -109,13 +122,22 @@ class MainFrameModel {
|
||||
void updateTablePreservingSelection(String tableName) {
|
||||
def downloadTable = builder.getVariable(tableName)
|
||||
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)
|
||||
}
|
||||
|
||||
void mvcGroupInit(Map<String, Object> args) {
|
||||
|
||||
uiSettings = application.context.get("ui-settings")
|
||||
|
||||
shared = []
|
||||
treeRoot = new DefaultMutableTreeNode()
|
||||
sharedTree = new DefaultTreeModel(treeRoot)
|
||||
|
||||
Timer timer = new Timer("download-pumper", true)
|
||||
timer.schedule({
|
||||
@ -191,7 +213,7 @@ class MainFrameModel {
|
||||
return
|
||||
int retryInterval = core.muOptions.downloadRetryInterval
|
||||
if (retryInterval > 0) {
|
||||
retryInterval *= 60000
|
||||
retryInterval *= 1000
|
||||
long now = System.currentTimeMillis()
|
||||
if (now - lastRetryTime > retryInterval) {
|
||||
lastRetryTime = now
|
||||
@ -207,7 +229,7 @@ class MainFrameModel {
|
||||
|
||||
}
|
||||
}
|
||||
}, 60000, 60000)
|
||||
}, 1000, 1000)
|
||||
|
||||
runInsideUIAsync {
|
||||
trusted.addAll(core.trustService.good.values())
|
||||
@ -227,9 +249,7 @@ class MainFrameModel {
|
||||
|
||||
void onAllFilesLoadedEvent(AllFilesLoadedEvent e) {
|
||||
runInsideUIAsync {
|
||||
watched.addAll(core.muOptions.watchedDirectories)
|
||||
builder.getVariable("watched-directories-table").model.fireTableDataChanged()
|
||||
watched.each { core.eventBus.publish(new FileSharedEvent(file : new File(it))) }
|
||||
core.muOptions.watchedDirectories.each { core.eventBus.publish(new FileSharedEvent(file : new File(it))) }
|
||||
|
||||
core.muOptions.trustSubscriptions.each {
|
||||
core.eventBus.publish(new TrustSubscriptionEvent(persona : it, subscribe : true))
|
||||
@ -297,7 +317,6 @@ class MainFrameModel {
|
||||
|
||||
void onFileHashingEvent(FileHashingEvent e) {
|
||||
runInsideUIAsync {
|
||||
loadedFiles = shared.size()
|
||||
hashingFile = e.hashingFile
|
||||
}
|
||||
}
|
||||
@ -313,6 +332,8 @@ class MainFrameModel {
|
||||
loadedFiles = shared.size()
|
||||
JTable table = builder.getVariable("shared-files-table")
|
||||
table.model.fireTableDataChanged()
|
||||
insertIntoTree(e.sharedFile)
|
||||
loadedFiles = fileToNode.size()
|
||||
}
|
||||
}
|
||||
|
||||
@ -322,6 +343,8 @@ class MainFrameModel {
|
||||
loadedFiles = shared.size()
|
||||
JTable table = builder.getVariable("shared-files-table")
|
||||
table.model.fireTableDataChanged()
|
||||
insertIntoTree(e.loadedFile)
|
||||
loadedFiles = fileToNode.size()
|
||||
}
|
||||
}
|
||||
|
||||
@ -329,8 +352,26 @@ class MainFrameModel {
|
||||
runInsideUIAsync {
|
||||
shared.remove(e.unsharedFile)
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@ -473,8 +514,44 @@ class MainFrameModel {
|
||||
shared << e.downloadedFile
|
||||
JTable table = builder.getVariable("shared-files-table")
|
||||
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 {
|
||||
Destination destination
|
||||
|
@ -38,6 +38,7 @@ class OptionsModel {
|
||||
|
||||
// trust options
|
||||
@Observable boolean onlyTrusted
|
||||
@Observable boolean searchExtraHop
|
||||
@Observable boolean trustLists
|
||||
@Observable String trustListInterval
|
||||
|
||||
@ -73,6 +74,7 @@ class OptionsModel {
|
||||
}
|
||||
|
||||
onlyTrusted = !settings.allowUntrusted()
|
||||
searchExtraHop = settings.searchExtraHop
|
||||
trustLists = settings.allowTrustLists
|
||||
trustListInterval = String.valueOf(settings.trustListInterval)
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
@ -84,6 +84,7 @@ class ContentPanelView {
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
@ -16,13 +16,18 @@ import javax.swing.JMenuItem
|
||||
import javax.swing.JPopupMenu
|
||||
import javax.swing.JSplitPane
|
||||
import javax.swing.JTable
|
||||
import javax.swing.JTree
|
||||
import javax.swing.ListSelectionModel
|
||||
import javax.swing.SwingConstants
|
||||
import javax.swing.TransferHandler
|
||||
import javax.swing.border.Border
|
||||
import javax.swing.table.DefaultTableCellRenderer
|
||||
import javax.swing.tree.TreeNode
|
||||
import javax.swing.tree.TreePath
|
||||
|
||||
import com.muwire.core.Constants
|
||||
import com.muwire.core.MuWireSettings
|
||||
import com.muwire.core.SharedFile
|
||||
import com.muwire.core.download.Downloader
|
||||
import com.muwire.core.files.FileSharedEvent
|
||||
import com.muwire.core.trust.RemoteTrustList
|
||||
@ -33,6 +38,7 @@ import java.awt.GridBagConstraints
|
||||
import java.awt.GridBagLayout
|
||||
import java.awt.Insets
|
||||
import java.awt.Toolkit
|
||||
import java.awt.datatransfer.DataFlavor
|
||||
import java.awt.datatransfer.StringSelection
|
||||
import java.awt.event.MouseAdapter
|
||||
import java.awt.event.MouseEvent
|
||||
@ -53,16 +59,17 @@ class MainFrameView {
|
||||
def downloadsTable
|
||||
def lastDownloadSortEvent
|
||||
def lastSharedSortEvent
|
||||
def lastWatchedSortEvent
|
||||
def trustTablesSortEvents = [:]
|
||||
|
||||
UISettings settings
|
||||
|
||||
void initUI() {
|
||||
UISettings settings = application.context.get("ui-settings")
|
||||
settings = application.context.get("ui-settings")
|
||||
builder.with {
|
||||
application(size : [1024,768], id: 'main-frame',
|
||||
locationRelativeTo : null,
|
||||
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,
|
||||
iconImages: [imageIcon('/MuWire-48x48.png').image,
|
||||
imageIcon('/MuWire-32x32.png').image,
|
||||
@ -99,7 +106,7 @@ class MainFrameView {
|
||||
panel(id: "top-panel", constraints: BorderLayout.CENTER) {
|
||||
cardLayout()
|
||||
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") {
|
||||
borderLayout()
|
||||
panel(constraints: BorderLayout.CENTER) {
|
||||
@ -158,8 +165,8 @@ class MainFrameView {
|
||||
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 : 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))
|
||||
@ -182,46 +189,55 @@ class MainFrameView {
|
||||
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)"
|
||||
}
|
||||
})
|
||||
if (model.hashingFile == null) {
|
||||
"You can drag-and-drop files and directories here"
|
||||
} else {
|
||||
"hashing: " + model.hashingFile.getAbsolutePath() + " (" + DataHelper.formatSize2Decimal(model.hashingFile.length(), false).toString() + "B)"
|
||||
}
|
||||
})
|
||||
}
|
||||
panel (border : etchedBorder(), constraints : BorderLayout.CENTER) {
|
||||
gridLayout(cols : 2, rows : 1)
|
||||
panel {
|
||||
borderLayout()
|
||||
scrollPane (constraints : BorderLayout.CENTER) {
|
||||
table(id : "watched-directories-table", autoCreateRowSorter: true) {
|
||||
tableModel(list : model.watched) {
|
||||
closureColumn(header: "Watched Directories", type : String, read : { it })
|
||||
borderLayout()
|
||||
panel (id : "shared-files-panel", constraints : BorderLayout.CENTER){
|
||||
cardLayout()
|
||||
panel (constraints : "shared files table") {
|
||||
borderLayout()
|
||||
scrollPane(constraints : BorderLayout.CENTER) {
|
||||
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 {
|
||||
borderLayout()
|
||||
scrollPane(constraints : BorderLayout.CENTER) {
|
||||
table(id : "shared-files-table", autoCreateRowSorter: true) {
|
||||
tableModel(list : model.shared) {
|
||||
closureColumn(header : "Name", preferredWidth : 500, type : String, read : {row -> row.getCachedPath()})
|
||||
closureColumn(header : "Size", preferredWidth : 100, type : Long, read : {row -> row.getCachedLength() })
|
||||
}
|
||||
panel (constraints : "shared files tree") {
|
||||
borderLayout()
|
||||
scrollPane(constraints : BorderLayout.CENTER) {
|
||||
def jtree = new JTree(model.sharedTree)
|
||||
jtree.setCellRenderer(new SharedTreeRenderer())
|
||||
tree(id : "shared-files-tree", rootVisible : false, jtree)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
panel (constraints : BorderLayout.SOUTH) {
|
||||
gridLayout(rows:1, cols:2)
|
||||
gridLayout(rows:1, cols:3)
|
||||
panel {
|
||||
button(text : "Add directories to watch", actionPerformed : watchDirectories)
|
||||
button(text : "Share files", actionPerformed : shareFiles)
|
||||
buttonGroup(id : "sharedViewType")
|
||||
radioButton(text : "Tree", selected : true, buttonGroup : sharedViewType, actionPerformed : showSharedFilesTree)
|
||||
radioButton(text : "Table", selected : false, buttonGroup : sharedViewType, actionPerformed : showSharedFilesTable)
|
||||
}
|
||||
panel {
|
||||
label("Shared:")
|
||||
label(text : bind {model.loadedFiles.toString()})
|
||||
button(text : "Share files", actionPerformed : shareFiles)
|
||||
button(text : "Add Comment", enabled : bind {model.addCommentButtonEnabled}, addCommentAction)
|
||||
}
|
||||
panel {
|
||||
panel {
|
||||
label("Shared:")
|
||||
label(text : bind {model.loadedFiles}, id : "shared-files-count")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -305,8 +321,8 @@ class MainFrameView {
|
||||
})
|
||||
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))
|
||||
String.format("%02d", it.timestamp.get(Calendar.MINUTE)) + ":" +
|
||||
String.format("%02d", it.timestamp.get(Calendar.SECOND))
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -392,6 +408,22 @@ class MainFrameView {
|
||||
}
|
||||
|
||||
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 selectionModel = downloadsTable.getSelectionModel()
|
||||
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
|
||||
@ -407,7 +439,7 @@ class MainFrameView {
|
||||
return
|
||||
}
|
||||
def downloader = model.downloads[selectedRow]?.downloader
|
||||
if (downloader == null)
|
||||
if (downloader == null)
|
||||
return
|
||||
model.downloader = downloader
|
||||
downloadDetailsPanel.getLayout().show(downloadDetailsPanel,"download-selected")
|
||||
@ -415,26 +447,26 @@ class MainFrameView {
|
||||
case Downloader.DownloadState.CONNECTING :
|
||||
case Downloader.DownloadState.DOWNLOADING :
|
||||
case Downloader.DownloadState.HASHLIST:
|
||||
model.cancelButtonEnabled = true
|
||||
model.pauseButtonEnabled = true
|
||||
model.retryButtonEnabled = false
|
||||
break
|
||||
model.cancelButtonEnabled = true
|
||||
model.pauseButtonEnabled = true
|
||||
model.retryButtonEnabled = false
|
||||
break
|
||||
case Downloader.DownloadState.FAILED:
|
||||
model.cancelButtonEnabled = true
|
||||
model.retryButtonEnabled = true
|
||||
model.resumeButtonText = "Retry"
|
||||
model.pauseButtonEnabled = false
|
||||
break
|
||||
model.cancelButtonEnabled = true
|
||||
model.retryButtonEnabled = true
|
||||
model.resumeButtonText = "Retry"
|
||||
model.pauseButtonEnabled = false
|
||||
break
|
||||
case Downloader.DownloadState.PAUSED:
|
||||
model.cancelButtonEnabled = true
|
||||
model.retryButtonEnabled = true
|
||||
model.resumeButtonText = "Resume"
|
||||
model.pauseButtonEnabled = false
|
||||
break
|
||||
model.cancelButtonEnabled = true
|
||||
model.retryButtonEnabled = true
|
||||
model.resumeButtonText = "Resume"
|
||||
model.pauseButtonEnabled = false
|
||||
break
|
||||
default:
|
||||
model.cancelButtonEnabled = false
|
||||
model.retryButtonEnabled = false
|
||||
model.pauseButtonEnabled = false
|
||||
model.cancelButtonEnabled = false
|
||||
model.retryButtonEnabled = false
|
||||
model.pauseButtonEnabled = false
|
||||
}
|
||||
})
|
||||
|
||||
@ -448,25 +480,19 @@ class MainFrameView {
|
||||
downloadsTable.rowSorter.setComparator(2, new DownloaderComparator())
|
||||
|
||||
downloadsTable.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
if (e.isPopupTrigger())
|
||||
showDownloadsMenu(e)
|
||||
}
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
if (e.isPopupTrigger())
|
||||
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)
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
if (e.isPopupTrigger())
|
||||
showDownloadsMenu(e)
|
||||
}
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
if (e.isPopupTrigger())
|
||||
showDownloadsMenu(e)
|
||||
}
|
||||
})
|
||||
|
||||
// shared files menu
|
||||
JPopupMenu sharedFilesMenu = new JPopupMenu()
|
||||
JMenuItem copyHashToClipboard = new JMenuItem("Copy hash to clipboard")
|
||||
copyHashToClipboard.addActionListener({mvcGroup.view.copyHashToClipboard()})
|
||||
@ -474,17 +500,46 @@ class MainFrameView {
|
||||
JMenuItem unshareSelectedFiles = new JMenuItem("Unshare selected files")
|
||||
unshareSelectedFiles.addActionListener({mvcGroup.controller.unshareSelectedFile()})
|
||||
sharedFilesMenu.add(unshareSelectedFiles)
|
||||
sharedFilesTable.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
if (e.isPopupTrigger())
|
||||
showPopupMenu(sharedFilesMenu, e)
|
||||
}
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
if (e.isPopupTrigger())
|
||||
showPopupMenu(sharedFilesMenu, e)
|
||||
}
|
||||
JMenuItem commentSelectedFiles = new JMenuItem("Comment selected files")
|
||||
commentSelectedFiles.addActionListener({mvcGroup.controller.addComment()})
|
||||
sharedFilesMenu.add(commentSelectedFiles)
|
||||
|
||||
def sharedFilesMouseListener = new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
if (e.isPopupTrigger())
|
||||
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
|
||||
@ -503,38 +558,17 @@ class MainFrameView {
|
||||
searchTableMenu.add(distrustSearcher)
|
||||
|
||||
searchesTable.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
if (e.isPopupTrigger())
|
||||
showPopupMenu(searchTableMenu, e)
|
||||
}
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
if (e.isPopupTrigger())
|
||||
showPopupMenu(searchTableMenu, e)
|
||||
}
|
||||
})
|
||||
|
||||
// watched directories table
|
||||
def watchedTable = builder.getVariable("watched-directories-table")
|
||||
watchedTable.rowSorter.addRowSorterListener({evt -> lastWatchedSortEvent = evt})
|
||||
watchedTable.rowSorter.setSortsOnUpdates(true)
|
||||
JPopupMenu watchedMenu = new JPopupMenu()
|
||||
JMenuItem stopWatching = new JMenuItem("Stop sharing")
|
||||
stopWatching.addActionListener({mvcGroup.controller.stopWatchingDirectory()})
|
||||
watchedMenu.add(stopWatching)
|
||||
watchedTable.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
if (e.isPopupTrigger())
|
||||
showPopupMenu(watchedMenu, e)
|
||||
}
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
if (e.isPopupTrigger())
|
||||
showPopupMenu(watchedMenu, e)
|
||||
}
|
||||
})
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
if (e.isPopupTrigger())
|
||||
showPopupMenu(searchTableMenu, e)
|
||||
}
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
if (e.isPopupTrigger())
|
||||
showPopupMenu(searchTableMenu, e)
|
||||
}
|
||||
})
|
||||
|
||||
// subscription table
|
||||
def subscriptionTable = builder.getVariable("subscription-table")
|
||||
@ -557,20 +591,20 @@ class MainFrameView {
|
||||
switch(trustList.status) {
|
||||
case RemoteTrustList.Status.NEW:
|
||||
case RemoteTrustList.Status.UPDATING:
|
||||
model.reviewButtonEnabled = false
|
||||
model.updateButtonEnabled = false
|
||||
model.unsubscribeButtonEnabled = false
|
||||
break
|
||||
model.reviewButtonEnabled = false
|
||||
model.updateButtonEnabled = false
|
||||
model.unsubscribeButtonEnabled = false
|
||||
break
|
||||
case RemoteTrustList.Status.UPDATED:
|
||||
model.reviewButtonEnabled = true
|
||||
model.updateButtonEnabled = true
|
||||
model.unsubscribeButtonEnabled = true
|
||||
break
|
||||
model.reviewButtonEnabled = true
|
||||
model.updateButtonEnabled = true
|
||||
model.unsubscribeButtonEnabled = true
|
||||
break
|
||||
case RemoteTrustList.Status.UPDATE_FAILED:
|
||||
model.reviewButtonEnabled = false
|
||||
model.updateButtonEnabled = true
|
||||
model.unsubscribeButtonEnabled = true
|
||||
break
|
||||
model.reviewButtonEnabled = false
|
||||
model.updateButtonEnabled = true
|
||||
model.unsubscribeButtonEnabled = true
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
@ -609,28 +643,65 @@ class MainFrameView {
|
||||
model.markNeutralFromDistrustedButtonEnabled = true
|
||||
}
|
||||
})
|
||||
|
||||
// show tree by default
|
||||
showSharedFilesTree.call()
|
||||
}
|
||||
|
||||
private static void showPopupMenu(JPopupMenu menu, MouseEvent event) {
|
||||
menu.show(event.getComponent(), event.getX(), event.getY())
|
||||
}
|
||||
|
||||
def selectedSharedFile() {
|
||||
def sharedFilesTable = builder.getVariable("shared-files-table")
|
||||
int selected = sharedFilesTable.getSelectedRow()
|
||||
if (selected < 0)
|
||||
return null
|
||||
if (lastSharedSortEvent != null)
|
||||
selected = sharedFilesTable.rowSorter.convertRowIndexToModel(selected)
|
||||
model.shared[selected]
|
||||
def selectedSharedFiles() {
|
||||
if (!model.treeVisible) {
|
||||
def sharedFilesTable = builder.getVariable("shared-files-table")
|
||||
int[] selected = sharedFilesTable.getSelectedRows()
|
||||
if (selected.length == 0)
|
||||
return null
|
||||
List<SharedFile> rv = new ArrayList<>()
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
def copyHashToClipboard() {
|
||||
def selected = selectedSharedFile()
|
||||
if (selected == null)
|
||||
|
||||
private static void getLeafs(TreeNode node, List<SharedFile> dest) {
|
||||
if (node.isLeaf()) {
|
||||
dest.add(node.getUserObject())
|
||||
return
|
||||
String root = Base64.encode(selected.infoHash.getRoot())
|
||||
StringSelection selection = new StringSelection(root)
|
||||
}
|
||||
def children = node.children()
|
||||
while(children.hasMoreElements()) {
|
||||
getLeafs(children.nextElement(), dest)
|
||||
}
|
||||
}
|
||||
|
||||
def copyHashToClipboard() {
|
||||
def selectedFiles = selectedSharedFiles()
|
||||
if (selectedFiles == null)
|
||||
return
|
||||
String roots = ""
|
||||
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()
|
||||
clipboard.setContents(selection, null)
|
||||
}
|
||||
@ -726,7 +797,7 @@ class MainFrameView {
|
||||
model.monitorPaneButtonEnabled = true
|
||||
model.trustPaneButtonEnabled = true
|
||||
}
|
||||
|
||||
|
||||
def showDownloadsWindow = {
|
||||
def cardsPanel = builder.getVariable("cards-panel")
|
||||
cardsPanel.getLayout().show(cardsPanel, "downloads window")
|
||||
@ -766,44 +837,34 @@ class MainFrameView {
|
||||
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 chooser = new JFileChooser()
|
||||
chooser.setFileHidingEnabled(false)
|
||||
chooser.setDialogTitle("Select file to share")
|
||||
chooser.setFileSelectionMode(JFileChooser.FILES_ONLY)
|
||||
chooser.setMultiSelectionEnabled(true)
|
||||
int rv = chooser.showOpenDialog(null)
|
||||
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) {
|
||||
def table = builder.getVariable(tableName)
|
||||
int selectedRow = table.getSelectedRow()
|
||||
@ -813,4 +874,9 @@ class MainFrameView {
|
||||
selectedRow = table.rowSorter.convertRowIndexToModel(selectedRow)
|
||||
selectedRow
|
||||
}
|
||||
|
||||
public void refreshSharedFiles() {
|
||||
model.sharedTree.nodeStructureChanged(model.treeRoot)
|
||||
builder.getVariable("shared-files-table").model.fireTableDataChanged()
|
||||
}
|
||||
}
|
@ -55,6 +55,7 @@ class OptionsView {
|
||||
def outBwField
|
||||
|
||||
def allowUntrustedCheckbox
|
||||
def searchExtraHopCheckbox
|
||||
def allowTrustListsCheckbox
|
||||
def trustListIntervalField
|
||||
|
||||
@ -70,7 +71,7 @@ class OptionsView {
|
||||
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 : "seconds", 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))
|
||||
@ -138,11 +139,13 @@ class OptionsView {
|
||||
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))
|
||||
label(text : "Search extra hop", constraints : gbc(gridx:0, gridy:1))
|
||||
searchExtraHopCheckbox = checkBox(selected : bind {model.searchExtraHop}, constraints : gbc(gridx: 1, gridy : 1))
|
||||
label(text : "Allow others to view my trust list", constraints : gbc(gridx: 0, gridy : 2))
|
||||
allowTrustListsCheckbox = checkBox(selected : bind {model.trustLists}, constraints : gbc(gridx: 1, 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))
|
||||
}
|
||||
|
||||
|
||||
|
@ -18,10 +18,13 @@ import javax.swing.SwingConstants
|
||||
import javax.swing.table.DefaultTableCellRenderer
|
||||
|
||||
import com.muwire.core.Persona
|
||||
import com.muwire.core.search.UIResultEvent
|
||||
import com.muwire.core.util.DataUtil
|
||||
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.Color
|
||||
import java.awt.FlowLayout
|
||||
import java.awt.GridBagConstraints
|
||||
import java.awt.Toolkit
|
||||
import java.awt.datatransfer.StringSelection
|
||||
import java.awt.event.MouseAdapter
|
||||
@ -43,11 +46,13 @@ class SearchTabView {
|
||||
def lastSendersSortEvent
|
||||
def resultsTable
|
||||
def lastSortEvent
|
||||
def sequentialDownloadCheckbox
|
||||
|
||||
void initUI() {
|
||||
builder.with {
|
||||
def resultsTable
|
||||
def sendersTable
|
||||
def sequentialDownloadCheckbox
|
||||
def pane = panel {
|
||||
gridLayout(rows :1, cols : 1)
|
||||
splitPane(orientation: JSplitPane.VERTICAL_SPLIT, continuousLayout : true, dividerLocation: 300 ) {
|
||||
@ -66,6 +71,7 @@ class SearchTabView {
|
||||
}
|
||||
panel(constraints : BorderLayout.SOUTH) {
|
||||
button(text : "Trust", enabled: bind {model.trustButtonsEnabled }, trustAction)
|
||||
button(text : "Neutral", enabled: bind {model.trustButtonsEnabled}, neutralAction)
|
||||
button(text : "Distrust", enabled : bind {model.trustButtonsEnabled}, distrustAction)
|
||||
}
|
||||
}
|
||||
@ -78,11 +84,22 @@ class SearchTabView {
|
||||
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) {
|
||||
button(text : "Download", enabled : bind {model.downloadActionEnabled}, downloadAction)
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -94,18 +111,26 @@ class SearchTabView {
|
||||
|
||||
this.resultsTable = resultsTable
|
||||
this.sendersTable = sendersTable
|
||||
this.sequentialDownloadCheckbox = sequentialDownloadCheckbox
|
||||
|
||||
def selectionModel = resultsTable.getSelectionModel()
|
||||
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
|
||||
selectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
|
||||
selectionModel.addListSelectionListener( {
|
||||
int row = resultsTable.getSelectedRow()
|
||||
if (row < 0) {
|
||||
int[] rows = resultsTable.getSelectedRows()
|
||||
if (rows.length == 0) {
|
||||
model.downloadActionEnabled = false
|
||||
return
|
||||
}
|
||||
if (lastSortEvent != null)
|
||||
row = resultsTable.rowSorter.convertRowIndexToModel(row)
|
||||
model.downloadActionEnabled = mvcGroup.parentGroup.model.canDownload(model.results[row].infohash)
|
||||
if (lastSortEvent != null) {
|
||||
for (int i = 0; i < rows.length; i ++) {
|
||||
rows[i] = resultsTable.rowSorter.convertRowIndexToModel(rows[i])
|
||||
}
|
||||
}
|
||||
boolean downloadActionEnabled = true
|
||||
rows.each {
|
||||
downloadActionEnabled &= mvcGroup.parentGroup.model.canDownload(model.results[it].infohash)
|
||||
}
|
||||
model.downloadActionEnabled = downloadActionEnabled
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -190,21 +215,38 @@ class SearchTabView {
|
||||
|
||||
def showPopupMenu(MouseEvent e) {
|
||||
JPopupMenu menu = new JPopupMenu()
|
||||
boolean showMenu = false
|
||||
if (model.downloadActionEnabled) {
|
||||
JMenuItem download = new JMenuItem("Download")
|
||||
download.addActionListener({mvcGroup.controller.download()})
|
||||
menu.add(download)
|
||||
showMenu = true
|
||||
}
|
||||
JMenuItem copyHashToClipboard = new JMenuItem("Copy hash to clipboard")
|
||||
copyHashToClipboard.addActionListener({mvcGroup.view.copyHashToClipboard()})
|
||||
menu.add(copyHashToClipboard)
|
||||
menu.show(e.getComponent(), e.getX(), e.getY())
|
||||
if (resultsTable.getSelectedRows().length == 1) {
|
||||
JMenuItem copyHashToClipboard = new JMenuItem("Copy hash to clipboard")
|
||||
copyHashToClipboard.addActionListener({mvcGroup.view.copyHashToClipboard()})
|
||||
menu.add(copyHashToClipboard)
|
||||
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())
|
||||
}
|
||||
|
||||
def copyHashToClipboard() {
|
||||
int selected = resultsTable.getSelectedRow()
|
||||
if (selected < 0)
|
||||
int[] selectedRows = resultsTable.getSelectedRows()
|
||||
if (selectedRows.length != 1)
|
||||
return
|
||||
int selected = selectedRows[0]
|
||||
if (lastSortEvent != null)
|
||||
selected = resultsTable.rowSorter.convertRowIndexToModel(selected)
|
||||
String hash = Base64.encode(model.results[selected].infohash.getRoot())
|
||||
@ -213,6 +255,23 @@ class SearchTabView {
|
||||
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)
|
||||
|
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()
|
||||
}
|
||||
}
|
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
|
||||
}
|
||||
}
|
@ -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(ContentPanelController)
|
||||
class ContentPanelControllerTest {
|
||||
private ContentPanelController 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(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'
|
||||
mainClassName = 'com.muwire.hostcache.HostCache'
|
||||
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties']
|
||||
|
||||
dependencies {
|
||||
testCompile 'org.junit.jupiter:junit-jupiter-api:5.4.2'
|
||||
testCompile 'junit:junit:4.12'
|
||||
}
|
||||
|
@ -9,6 +9,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
|
||||
|
||||
|
||||
@Log
|
||||
@ -28,7 +29,7 @@ class UpdateServer {
|
||||
def session
|
||||
if (!keyFile.exists()) {
|
||||
def os = new FileOutputStream(keyFile);
|
||||
myDest = i2pClient.createDestination(os)
|
||||
myDest = i2pClient.createDestination(os, SigType.EdDSA_SHA512_Ed25519)
|
||||
os.close()
|
||||
log.info "No key.dat file was found, so creating a new destination."
|
||||
log.info "This is the destination you want to give out for your new UpdateServer"
|
||||
|
Reference in New Issue
Block a user