Compare commits
18 Commits
muwire-0.4
...
muwire-0.4
Author | SHA1 | Date | |
---|---|---|---|
![]() |
337605dc0f | ||
![]() |
14bdfa6b2e | ||
![]() |
ed3f9da773 | ||
![]() |
251080d08f | ||
![]() |
f530ab999d | ||
![]() |
4133384e48 | ||
![]() |
600fc98868 | ||
![]() |
129eeb3b88 | ||
![]() |
20b51b78a0 | ||
![]() |
33fe755b60 | ||
![]() |
8b0668a134 | ||
![]() |
730d2202fd | ||
![]() |
69906a986d | ||
![]() |
5bc8fa8633 | ||
![]() |
7de7c9d8f3 | ||
![]() |
e943f6019d | ||
![]() |
2eec7bec5b | ||
![]() |
c36110cf76 |
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.13 is avaiable for download at https://muwire.com. You can find technical documentation in the "doc" folder.
|
||||
The current stable release - 0.4.14 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,13 +19,11 @@ 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 for Windows and Mac that do not depend on Java or I2P, see the https://github.com/zlatinb/muwire-pkg project
|
||||
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`
|
||||
|
||||
|
2
TODO.md
2
TODO.md
@@ -23,6 +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
|
||||
|
@@ -35,7 +35,7 @@ class Cli {
|
||||
|
||||
Core core
|
||||
try {
|
||||
core = new Core(props, home, "0.4.14")
|
||||
core = new Core(props, home, "0.4.15")
|
||||
} 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.14")
|
||||
core = new Core(props, home, "0.4.15")
|
||||
} catch (Exception bad) {
|
||||
bad.printStackTrace(System.out)
|
||||
println "Failed to initialize core, exiting"
|
||||
|
@@ -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 = "[\\*\\+\\-,\\.:;\\(\\)=_/\\\\\\!\\\"\\\'\\\$%\\|\\[\\]\\{\\}\\?]"
|
||||
}
|
@@ -135,6 +135,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))
|
||||
@@ -361,7 +362,7 @@ public class Core {
|
||||
}
|
||||
}
|
||||
|
||||
Core core = new Core(props, home, "0.4.14")
|
||||
Core core = new Core(props, home, "0.4.15")
|
||||
core.startServices()
|
||||
|
||||
// ... at the end, sleep or execute script
|
||||
|
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 = "[\\*\\+\\-,\\.:;\\(\\)=_/\\\\\\!\\\"\\\'\\\$%\\|\\[\\]\\{\\}\\?]";
|
||||
|
||||
}
|
@@ -46,7 +46,10 @@ class PersisterService extends Service {
|
||||
}
|
||||
|
||||
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 +59,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,17 +142,12 @@ class PersisterService extends Service {
|
||||
|
||||
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()
|
||||
|
||||
if (sf instanceof DownloadedFile) {
|
||||
json.sources = sf.sources.stream().map( {d -> d.toBase64()}).collect(Collectors.toList())
|
||||
|
@@ -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 }
|
||||
|
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,10 @@ public class SharedFile {
|
||||
|
||||
private final String cachedPath;
|
||||
private final long cachedLength;
|
||||
|
||||
private final String b64EncodedFileName;
|
||||
private final String b64EncodedHashRoot;
|
||||
private final List<String> b64EncodedHashList;
|
||||
|
||||
public SharedFile(File file, InfoHash infoHash, int pieceSize) throws IOException {
|
||||
this.file = file;
|
||||
@@ -18,6 +28,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 +60,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;
|
||||
|
@@ -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.14
|
||||
version = 0.4.15
|
||||
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")
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
@@ -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
|
||||
@@ -80,7 +80,7 @@ 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 }
|
||||
@@ -270,10 +270,12 @@ 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))
|
||||
}
|
||||
}
|
||||
|
||||
void stopWatchingDirectory() {
|
||||
|
@@ -8,73 +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, sequential : view.sequentialDownloadCheckbox.model.isSelected()))
|
||||
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 neutral() {
|
||||
int row = view.selectedSenderRow()
|
||||
if (row < 0)
|
||||
return
|
||||
def sender = model.senders[row]
|
||||
core.eventBus.publish( new TrustEvent(persona : sender, level : TrustLevel.NEUTRAL))
|
||||
}
|
||||
}
|
||||
@ControllerAction
|
||||
void 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))
|
||||
}
|
||||
}
|
@@ -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) {
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -23,6 +23,7 @@ import javax.swing.table.DefaultTableCellRenderer
|
||||
|
||||
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
|
||||
@@ -615,22 +616,36 @@ class MainFrameView {
|
||||
menu.show(event.getComponent(), event.getX(), event.getY())
|
||||
}
|
||||
|
||||
def selectedSharedFile() {
|
||||
def selectedSharedFiles() {
|
||||
def sharedFilesTable = builder.getVariable("shared-files-table")
|
||||
int selected = sharedFilesTable.getSelectedRow()
|
||||
if (selected < 0)
|
||||
int[] selected = sharedFilesTable.getSelectedRows()
|
||||
if (selected.length == 0)
|
||||
return null
|
||||
if (lastSharedSortEvent != null)
|
||||
selected = sharedFilesTable.rowSorter.convertRowIndexToModel(selected)
|
||||
model.shared[selected]
|
||||
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])
|
||||
}
|
||||
rv
|
||||
}
|
||||
|
||||
def copyHashToClipboard() {
|
||||
def selected = selectedSharedFile()
|
||||
if (selected == null)
|
||||
def selectedFiles = selectedSharedFiles()
|
||||
if (selectedFiles == null)
|
||||
return
|
||||
String root = Base64.encode(selected.infoHash.getRoot())
|
||||
StringSelection selection = new StringSelection(root)
|
||||
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)
|
||||
}
|
||||
@@ -772,9 +787,12 @@ class MainFrameView {
|
||||
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 {
|
||||
model.core.eventBus.publish(new FileSharedEvent(file : it))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -783,14 +801,16 @@ class MainFrameView {
|
||||
chooser.setFileHidingEnabled(false)
|
||||
chooser.setDialogTitle("Select directory to watch")
|
||||
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY)
|
||||
chooser.setMultiSelectionEnabled(true)
|
||||
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))
|
||||
chooser.getSelectedFiles().each { f ->
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -112,16 +112,23 @@ class SearchTabView {
|
||||
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
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -206,21 +213,28 @@ 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
|
||||
}
|
||||
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())
|
||||
|
@@ -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'
|
||||
}
|
||||
|
Reference in New Issue
Block a user