Compare commits

..

39 Commits

Author SHA1 Message Date
Zlatin Balevsky
a9dc636bce write pieces every time a downloader finishes 2019-07-06 00:52:49 +01:00
Zlatin Balevsky
3cc0574d11 working partial pieces 2019-07-06 00:47:45 +01:00
Zlatin Balevsky
20fab9b16d work on partial piece persistence 2019-07-06 00:17:46 +01:00
Zlatin Balevsky
4015818323 center buttons 2019-07-05 17:15:50 +01:00
Zlatin Balevsky
f569d45c8c reallign tables 2019-07-05 17:07:14 +01:00
Zlatin Balevsky
3773647869 remove diff rejects 2019-07-05 16:24:57 +01:00
Zlatin Balevsky
29cdbf018c remove trailing spaces 2019-07-05 16:24:19 +01:00
Zlatin Balevsky
94bb7022eb tabs -> spaces 2019-07-05 16:22:34 +01:00
Zlatin Balevsky
39808302df Show which file is hashing, thanks to Aegon 2019-07-05 16:20:03 +01:00
Zlatin Balevsky
2d22f9c39e override router log manager 2019-07-05 12:32:23 +01:00
Zlatin Balevsky
ee8f80bab6 up i2p to 0.9.41 2019-07-05 12:26:48 +01:00
Zlatin Balevsky
3e6242e583 break when matching search is found 2019-07-04 18:12:22 +01:00
Zlatin Balevsky
41181616ee compact display of incoming searches, thanks Aegon 2019-07-04 17:59:53 +01:00
Zlatin Balevsky
eb2530ca32 fix sorting of download/upload tables thanks Aegon 2019-07-04 17:58:06 +01:00
Zlatin Balevsky
b5233780ef Release 0.4.7 2019-07-03 20:36:54 +01:00
Zlatin Balevsky
78753d7538 shut down cache client on shutdown 2019-07-03 19:50:00 +01:00
Zlatin Balevsky
4740e8b4f5 log hostcache stats 2019-07-03 19:46:24 +01:00
Zlatin Balevsky
ad5b00fc90 prettier progress status thanks to Aegon 2019-07-03 12:50:24 +01:00
Zlatin Balevsky
d6c6880848 update readme 2019-07-03 07:27:48 +01:00
Zlatin Balevsky
4f948c1b9e Release 0.4.6 2019-07-03 07:11:59 +01:00
Zlatin Balevsky
2b68c24f9c use switch 2019-07-03 07:01:27 +01:00
Zlatin Balevsky
bcdf0422db update for embedded router 2019-07-03 07:00:04 +01:00
Zlatin Balevsky
f6434b478d remove FAQ 2019-07-03 06:56:20 +01:00
Zlatin Balevsky
e979fdd26f update list view tables 2019-07-03 06:51:21 +01:00
Zlatin Balevsky
e6bfcaaab9 size columns, center integers 2019-07-03 06:11:02 +01:00
Zlatin Balevsky
9780108e8a disable trust buttons on action 2019-07-03 06:00:09 +01:00
Zlatin Balevsky
697c7d2d6d enable/disable trust panel buttons 2019-07-03 05:41:17 +01:00
Zlatin Balevsky
887d10c8bf move buttons around 2019-07-03 05:30:39 +01:00
Zlatin Balevsky
ef6b8fe458 add a state for failed updates 2019-07-03 05:12:00 +01:00
Zlatin Balevsky
20ab55d763 update todo 2019-07-03 00:23:21 +01:00
Zlatin Balevsky
eda58c9e0d Merge branch 'trust-lists' 2019-07-03 00:04:50 +01:00
Zlatin Balevsky
1ccf6fbdfa participating bandwidth grid cell 2019-07-02 15:35:42 +01:00
Zlatin Balevsky
5711979272 Release 0.4.5 2019-07-02 15:01:51 +01:00
Zlatin Balevsky
9a5e2b1fa3 speed smoothing patch courtesy of Aegon 2019-07-02 14:46:40 +01:00
Zlatin Balevsky
a89b423dfc simpler speed calculation 2019-07-02 13:05:06 +01:00
Zlatin Balevsky
79e8438941 always assume interval is at least 1 second 2019-07-02 12:49:00 +01:00
Zlatin Balevsky
19c2c46491 prevent NPE on startup 2019-07-02 12:27:15 +01:00
Zlatin Balevsky
78f1d54b69 add new host cache 2019-07-02 10:04:24 +01:00
Zlatin Balevsky
9461649ed4 change sig type 2019-07-02 09:49:13 +01:00
141 changed files with 4978 additions and 4765 deletions

View File

@@ -4,7 +4,7 @@ MuWire is an easy to use file-sharing program which offers anonymity using [I2P
It is inspired by the LimeWire Gnutella client and developped by a former LimeWire developer. It is inspired by the LimeWire Gnutella client and developped by a former LimeWire developer.
The current stable release - 0.4.0 is avaiable for download at https://muwire.com. You can find technical documentation in the "doc" folder. The current stable release - 0.4.6 is avaiable for download at https://muwire.com. You can find technical documentation in the "doc" folder.
### Building ### Building
@@ -23,34 +23,9 @@ Some of the UI tests will fail because they haven't been written yet :-/
### Running ### Running
You need to have an I2P router up and running on the same machine. After you build the application, look inside `gui/build/distributions`. Untar/unzip one of the `shadow` files and then run the jar contained inside by typing `java -jar MuWire-x.y.z.jar` in a terminal or command prompt. If you use a custom I2CP host and port, create a file `$HOME/.MuWire/i2p.properties` and put `i2cp.tcp.host=<host>` and `i2cp.tcp.port=<port>` in there. After you build the application, look inside `gui/build/distributions`. Untar/unzip one of the `shadow` files and then run the jar contained inside by typing `java -jar MuWire-x.y.z.jar` in a terminal or command prompt.
The first time you run MuWire it will ask you to select a nickname. This nickname will be displayed with search results, so that others can verify the file was shared by you. It is best to leave MuWire running all the time, just like I2P. If you have an I2P router running on the same machine that is all you need to do. If you use a custom I2CP host and port, create a file `$HOME/.MuWire/i2p.properties` and put `i2cp.tcp.host=<host>` and `i2cp.tcp.port=<port>` in there.
If you do not have an I2P router, pass the following switch to the Java process: `-DembeddedRouter=true`. This will launch MuWire's embedded router. Be aware that this causes startup to take a lot longer.
### Known bugs and limitations
* Many UI features you would expect are not there yet
### Quick FAQ
* why is MuWire slow ?
- too few sources you're downloading from
- you can increase the number of tunnels by using more tunnels via Options->I2P Inbound/Outbound Quantity
the default is 4 and you could raise up to as high as 16 ( Caution !!!!)
* my search is not returning (enough) results !
- search is keyword or hash based
- keywords and hash(es) are NOT regexed or wildcarded so they have to be complete
so searching for 'musi' will not return results with 'music' - you have to search for 'music'
- ALL keywords have to match
- only use space for keyword separation
- if you already have the file in question it is not displayed ( can be changed via Options )
* what's this right click -> 'Copy hash to clipboard' for ?
- if you have a specific file you wish to share or download you can use the hash as a unique identifier
to make sure you have exactly the right file.
- you can share this hash with others to ensure they are getting the right file

View File

@@ -12,10 +12,6 @@ This reduces query traffic by not sending last hop queries to peers that definit
This helps with scalability This helps with scalability
##### Trust List Sharing
For helping users make better decisions whom to trust
##### Content Control Panel ##### 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 To allow every user to not route queries for content they do not like. This is mostly GUI work, the backend part is simple

View File

@@ -2,7 +2,7 @@ subprojects {
apply plugin: 'groovy' apply plugin: 'groovy'
dependencies { dependencies {
compile 'net.i2p:i2p:0.9.40' compile 'net.i2p:i2p:0.9.41'
compile 'org.codehaus.groovy:groovy-all:2.4.15' compile 'org.codehaus.groovy:groovy-all:2.4.15'
} }

View File

@@ -35,7 +35,7 @@ class Cli {
Core core Core core
try { try {
core = new Core(props, home, "0.4.4") core = new Core(props, home, "0.4.7")
} catch (Exception bad) { } catch (Exception bad) {
bad.printStackTrace(System.out) bad.printStackTrace(System.out)
println "Failed to initialize core, exiting" println "Failed to initialize core, exiting"

View File

@@ -53,7 +53,7 @@ class CliDownloader {
Core core Core core
try { try {
core = new Core(props, home, "0.4.4") core = new Core(props, home, "0.4.7")
} catch (Exception bad) { } catch (Exception bad) {
bad.printStackTrace(System.out) bad.printStackTrace(System.out)
println "Failed to initialize core, exiting" println "Failed to initialize core, exiting"

View File

@@ -2,9 +2,9 @@ apply plugin : 'application'
mainClassName = 'com.muwire.core.Core' mainClassName = 'com.muwire.core.Core'
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties'] applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties']
dependencies { dependencies {
compile 'net.i2p:router:0.9.40' compile 'net.i2p:router:0.9.41'
compile 'net.i2p.client:mstreaming:0.9.40' compile 'net.i2p.client:mstreaming:0.9.41'
compile 'net.i2p.client:streaming:0.9.40' compile 'net.i2p.client:streaming:0.9.41'
testCompile 'org.junit.jupiter:junit-jupiter-api:5.4.2' testCompile 'org.junit.jupiter:junit-jupiter-api:5.4.2'
testCompile 'junit:junit:4.12' testCompile 'junit:junit:4.12'

View File

@@ -20,6 +20,7 @@ import com.muwire.core.download.UIDownloadPausedEvent
import com.muwire.core.download.UIDownloadResumedEvent import com.muwire.core.download.UIDownloadResumedEvent
import com.muwire.core.files.FileDownloadedEvent import com.muwire.core.files.FileDownloadedEvent
import com.muwire.core.files.FileHashedEvent import com.muwire.core.files.FileHashedEvent
import com.muwire.core.files.FileHashingEvent
import com.muwire.core.files.FileHasher import com.muwire.core.files.FileHasher
import com.muwire.core.files.FileLoadedEvent import com.muwire.core.files.FileLoadedEvent
import com.muwire.core.files.FileManager import com.muwire.core.files.FileManager
@@ -140,7 +141,7 @@ public class Core {
routerProps.setProperty("i2np.udp.port", i2pOptions["i2np.udp.port"]) routerProps.setProperty("i2np.udp.port", i2pOptions["i2np.udp.port"])
routerProps.setProperty("i2np.udp.internalPort", i2pOptions["i2np.udp.port"]) routerProps.setProperty("i2np.udp.internalPort", i2pOptions["i2np.udp.port"])
router = new Router(routerProps) router = new Router(routerProps)
I2PAppContext.getGlobalContext().metaClass = new RouterContextMetaClass() router.getContext().setLogManager(new MuWireLogManager())
router.runRouter() router.runRouter()
while(!router.isRunning()) while(!router.isRunning())
Thread.sleep(100) Thread.sleep(100)
@@ -318,6 +319,8 @@ public class Core {
connectionEstablisher.stop() connectionEstablisher.stop()
log.info("shutting down directory watcher") log.info("shutting down directory watcher")
directoryWatcher.stop() directoryWatcher.stop()
log.info("shutting down cache client")
cacheClient.stop()
log.info("shutting down connection manager") log.info("shutting down connection manager")
connectionManager.shutdown() connectionManager.shutdown()
if (router != null) { if (router != null) {
@@ -326,19 +329,6 @@ public class Core {
} }
} }
static class RouterContextMetaClass extends DelegatingMetaClass {
private final Object logManager = new MuWireLogManager()
RouterContextMetaClass() {
super(RouterContext.class)
}
Object invokeMethod(Object object, String name, Object[] args) {
if (name == "logManager")
return logManager
super.invokeMethod(object, name, args)
}
}
static main(args) { static main(args) {
def home = System.getProperty("user.home") + File.separator + ".MuWire" def home = System.getProperty("user.home") + File.separator + ".MuWire"
home = new File(home) home = new File(home)
@@ -363,7 +353,7 @@ public class Core {
} }
} }
Core core = new Core(props, home, "0.4.4") Core core = new Core(props, home, "0.4.7")
core.startServices() core.startServices()
// ... at the end, sleep or execute script // ... at the end, sleep or execute script

View File

@@ -80,7 +80,7 @@ class MuWireSettings {
props.setProperty("downloadRetryInterval", String.valueOf(downloadRetryInterval)) props.setProperty("downloadRetryInterval", String.valueOf(downloadRetryInterval))
props.setProperty("updateCheckInterval", String.valueOf(updateCheckInterval)) props.setProperty("updateCheckInterval", String.valueOf(updateCheckInterval))
props.setProperty("autoDownloadUpdate", String.valueOf(autoDownloadUpdate)) props.setProperty("autoDownloadUpdate", String.valueOf(autoDownloadUpdate))
props.setProperty("updateType",updateType) props.setProperty("updateType",String.valueOf(updateType))
props.setProperty("shareDownloadedFiles", String.valueOf(shareDownloadedFiles)) props.setProperty("shareDownloadedFiles", String.valueOf(shareDownloadedFiles))
props.setProperty("downloadSequentialRatio", String.valueOf(downloadSequentialRatio)) props.setProperty("downloadSequentialRatio", String.valueOf(downloadSequentialRatio))
props.setProperty("hostClearInterval", String.valueOf(hostClearInterval)) props.setProperty("hostClearInterval", String.valueOf(hostClearInterval))

View File

@@ -14,6 +14,7 @@ import static com.muwire.core.util.DataUtil.readTillRN
import groovy.util.logging.Log import groovy.util.logging.Log
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.nio.MappedByteBuffer
import java.nio.channels.FileChannel import java.nio.channels.FileChannel
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.nio.file.Files import java.nio.file.Files
@@ -25,8 +26,6 @@ import java.util.logging.Level
@Log @Log
class DownloadSession { class DownloadSession {
private static int SAMPLES = 10
private final EventBus eventBus private final EventBus eventBus
private final String meB64 private final String meB64
private final Pieces pieces private final Pieces pieces
@@ -38,10 +37,10 @@ class DownloadSession {
private final Set<Integer> available private final Set<Integer> available
private final MessageDigest digest private final MessageDigest digest
private final LinkedList<Long> timestamps = new LinkedList<>() private long lastSpeedRead = System.currentTimeMillis()
private final LinkedList<Integer> reads = new LinkedList<>() private long dataSinceLastRead
private ByteBuffer mapped private MappedByteBuffer mapped
DownloadSession(EventBus eventBus, String meB64, Pieces pieces, InfoHash infoHash, Endpoint endpoint, File file, DownloadSession(EventBus eventBus, String meB64, Pieces pieces, InfoHash infoHash, Endpoint endpoint, File file,
int pieceSize, long fileLength, Set<Integer> available) { int pieceSize, long fileLength, Set<Integer> available) {
@@ -71,21 +70,22 @@ class DownloadSession {
OutputStream os = endpoint.getOutputStream() OutputStream os = endpoint.getOutputStream()
InputStream is = endpoint.getInputStream() InputStream is = endpoint.getInputStream()
int piece int[] pieceAndPosition
if (available.isEmpty()) if (available.isEmpty())
piece = pieces.claim() pieceAndPosition = pieces.claim()
else else
piece = pieces.claim(new HashSet<>(available)) pieceAndPosition = pieces.claim(new HashSet<>(available))
if (piece == -1) if (pieceAndPosition == null)
return false return false
int piece = pieceAndPosition[0]
int position = pieceAndPosition[1]
boolean unclaim = true boolean unclaim = true
log.info("will download piece $piece") log.info("will download piece $piece from position $position")
long start = piece * pieceSize
long end = Math.min(fileLength, start + pieceSize) - 1
long length = end - start + 1
long pieceStart = piece * pieceSize
long end = Math.min(fileLength, pieceStart + pieceSize) - 1
long start = pieceStart + position
String root = Base64.encode(infoHash.getRoot()) String root = Base64.encode(infoHash.getRoot())
try { try {
@@ -174,8 +174,9 @@ class DownloadSession {
FileChannel channel FileChannel channel
try { try {
channel = Files.newByteChannel(file.toPath(), EnumSet.of(StandardOpenOption.READ, StandardOpenOption.WRITE, channel = Files.newByteChannel(file.toPath(), EnumSet.of(StandardOpenOption.READ, StandardOpenOption.WRITE,
StandardOpenOption.SPARSE, StandardOpenOption.CREATE)) // TODO: double-check, maybe CREATE_NEW StandardOpenOption.SPARSE, StandardOpenOption.CREATE))
mapped = channel.map(FileChannel.MapMode.READ_WRITE, start, end - start + 1) mapped = channel.map(FileChannel.MapMode.READ_WRITE, pieceStart, end - pieceStart + 1)
mapped.position(position)
byte[] tmp = new byte[0x1 << 13] byte[] tmp = new byte[0x1 << 13]
while(mapped.hasRemaining()) { while(mapped.hasRemaining()) {
@@ -186,26 +187,23 @@ class DownloadSession {
throw new IOException() throw new IOException()
synchronized(this) { synchronized(this) {
mapped.put(tmp, 0, read) mapped.put(tmp, 0, read)
dataSinceLastRead += read
if (timestamps.size() == SAMPLES) { pieces.markPartial(piece, mapped.position())
timestamps.removeFirst()
reads.removeFirst()
}
timestamps.addLast(System.currentTimeMillis())
reads.addLast(read)
} }
} }
mapped.clear() mapped.clear()
digest.update(mapped) digest.update(mapped)
DataUtil.tryUnmap(mapped)
byte [] hash = digest.digest() byte [] hash = digest.digest()
byte [] expected = new byte[32] byte [] expected = new byte[32]
System.arraycopy(infoHash.getHashList(), piece * 32, expected, 0, 32) System.arraycopy(infoHash.getHashList(), piece * 32, expected, 0, 32)
if (hash != expected) if (hash != expected) {
throw new BadHashException() pieces.markPartial(piece, 0)
throw new BadHashException("bad hash on piece $piece")
}
} finally { } finally {
try { channel?.close() } catch (IOException ignore) {} try { channel?.close() } catch (IOException ignore) {}
DataUtil.tryUnmap(mapped)
} }
pieces.markDownloaded(piece) pieces.markDownloaded(piece)
unclaim = false unclaim = false
@@ -223,24 +221,11 @@ class DownloadSession {
} }
synchronized int speed() { synchronized int speed() {
if (timestamps.size() < SAMPLES)
return 0
int totalRead = 0
int idx = 0
final long now = System.currentTimeMillis() final long now = System.currentTimeMillis()
long interval = Math.max(1000, now - lastSpeedRead)
while(idx < SAMPLES && timestamps.get(idx) < now - 1000) lastSpeedRead = now;
idx++ int rv = (int) (dataSinceLastRead * 1000.0 / interval)
if (idx == SAMPLES) dataSinceLastRead = 0
return 0 rv
if (idx == SAMPLES - 1)
return reads[idx]
long interval = timestamps.last - timestamps[idx]
if (interval == 0)
interval = 1
for (int i = idx; i < SAMPLES; i++)
totalRead += reads[idx]
(int)(totalRead * 1000.0 / interval)
} }
} }

View File

@@ -7,6 +7,7 @@ import com.muwire.core.connection.Endpoint
import java.nio.file.AtomicMoveNotSupportedException import java.nio.file.AtomicMoveNotSupportedException
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.StandardCopyOption import java.nio.file.StandardCopyOption
import java.time.Instant
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ExecutorService import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors import java.util.concurrent.Executors
@@ -58,6 +59,11 @@ public class Downloader {
private final AtomicBoolean eventFired = new AtomicBoolean() private final AtomicBoolean eventFired = new AtomicBoolean()
private boolean piecesFileClosed private boolean piecesFileClosed
private ArrayList speedArr = new ArrayList<Integer>()
private int speedPos = 0
private int speedAvg = 0
private long timestamp = Instant.now().toEpochMilli()
public Downloader(EventBus eventBus, DownloadManager downloadManager, public Downloader(EventBus eventBus, DownloadManager downloadManager,
Persona me, File file, long length, InfoHash infoHash, Persona me, File file, long length, InfoHash infoHash,
int pieceSizePow2, I2PConnector connector, Set<Destination> destinations, int pieceSizePow2, I2PConnector connector, Set<Destination> destinations,
@@ -76,6 +82,10 @@ public class Downloader {
this.pieceSize = 1 << pieceSizePow2 this.pieceSize = 1 << pieceSizePow2
this.pieces = pieces this.pieces = pieces
this.nPieces = pieces.nPieces this.nPieces = pieces.nPieces
// default size suitable for an average of 5 seconds / 5 elements / 5 interval units
// it's easily adjustable by resizing the size of speedArr
this.speedArr = [ 0, 0, 0, 0, 0 ]
} }
public synchronized InfoHash getInfoHash() { public synchronized InfoHash getInfoHash() {
@@ -101,8 +111,14 @@ public class Downloader {
if (!piecesFile.exists()) if (!piecesFile.exists())
return return
piecesFile.eachLine { piecesFile.eachLine {
int piece = Integer.parseInt(it) String [] split = it.split(",")
int piece = Integer.parseInt(split[0])
if (split.length == 1)
pieces.markDownloaded(piece) pieces.markDownloaded(piece)
else {
int position = Integer.parseInt(split[1])
pieces.markPartial(piece, position)
}
} }
} }
@@ -111,9 +127,7 @@ public class Downloader {
if (piecesFileClosed) if (piecesFileClosed)
return return
piecesFile.withPrintWriter { writer -> piecesFile.withPrintWriter { writer ->
pieces.getDownloaded().each { piece -> pieces.write(writer)
writer.println(piece)
}
} }
} }
} }
@@ -124,14 +138,35 @@ public class Downloader {
public int speed() { public int speed() {
int total = 0 int currSpeed = 0
if (getCurrentState() == DownloadState.DOWNLOADING) { if (getCurrentState() == DownloadState.DOWNLOADING) {
activeWorkers.values().each { activeWorkers.values().each {
if (it.currentState == WorkerState.DOWNLOADING) if (it.currentState == WorkerState.DOWNLOADING)
total += it.speed() currSpeed += it.speed()
} }
} }
total
// normalize to speedArr.size
currSpeed /= speedArr.size()
// compute new speedAvg and update speedArr
if ( speedArr[speedPos] > speedAvg ) {
speedAvg = 0
} else {
speedAvg -= speedArr[speedPos]
}
speedAvg += currSpeed
speedArr[speedPos] = currSpeed
// this might be necessary due to rounding errors
if (speedAvg < 0)
speedAvg = 0
// rolling index over the speedArr
speedPos++
if (speedPos >= speedArr.size())
speedPos=0
speedAvg
} }
public DownloadState getCurrentState() { public DownloadState getCurrentState() {
@@ -272,6 +307,7 @@ public class Downloader {
} catch (Exception bad) { } catch (Exception bad) {
log.log(Level.WARNING,"Exception while downloading",DataUtil.findRoot(bad)) log.log(Level.WARNING,"Exception while downloading",DataUtil.findRoot(bad))
} finally { } finally {
writePieces()
currentState = WorkerState.FINISHED currentState = WorkerState.FINISHED
if (pieces.isComplete() && eventFired.compareAndSet(false, true)) { if (pieces.isComplete() && eventFired.compareAndSet(false, true)) {
synchronized(piecesFile) { synchronized(piecesFile) {

View File

@@ -5,6 +5,7 @@ class Pieces {
private final int nPieces private final int nPieces
private final float ratio private final float ratio
private final Random random = new Random() private final Random random = new Random()
private final Map<Integer,Integer> partials = new HashMap<>()
Pieces(int nPieces) { Pieces(int nPieces) {
this(nPieces, 1.0f) this(nPieces, 1.0f)
@@ -17,16 +18,16 @@ class Pieces {
claimed = new BitSet(nPieces) claimed = new BitSet(nPieces)
} }
synchronized int claim() { synchronized int[] claim() {
int claimedCardinality = claimed.cardinality() int claimedCardinality = claimed.cardinality()
if (claimedCardinality == nPieces) if (claimedCardinality == nPieces)
return -1 return null
// if fuller than ratio just do sequential // if fuller than ratio just do sequential
if ( (1.0f * claimedCardinality) / nPieces > ratio) { if ( (1.0f * claimedCardinality) / nPieces > ratio) {
int rv = claimed.nextClearBit(0) int rv = claimed.nextClearBit(0)
claimed.set(rv) claimed.set(rv)
return rv return [rv, partials.getOrDefault(rv, 0)]
} }
while(true) { while(true) {
@@ -34,11 +35,11 @@ class Pieces {
if (claimed.get(start)) if (claimed.get(start))
continue continue
claimed.set(start) claimed.set(start)
return start return [start, partials.getOrDefault(start,0)]
} }
} }
synchronized int claim(Set<Integer> available) { synchronized int[] claim(Set<Integer> available) {
for (int i = claimed.nextSetBit(0); i >= 0; i = claimed.nextSetBit(i+1)) for (int i = claimed.nextSetBit(0); i >= 0; i = claimed.nextSetBit(i+1))
available.remove(i) available.remove(i)
if (available.isEmpty()) if (available.isEmpty())
@@ -47,7 +48,7 @@ class Pieces {
Collections.shuffle(toList) Collections.shuffle(toList)
int rv = toList[0] int rv = toList[0]
claimed.set(rv) claimed.set(rv)
rv [rv, partials.getOrDefault(rv, 0)]
} }
synchronized def getDownloaded() { synchronized def getDownloaded() {
@@ -61,6 +62,11 @@ class Pieces {
synchronized void markDownloaded(int piece) { synchronized void markDownloaded(int piece) {
done.set(piece) done.set(piece)
claimed.set(piece) claimed.set(piece)
partials.remove(piece)
}
synchronized void markPartial(int piece, int position) {
partials.put(piece, position)
} }
synchronized void unclaim(int piece) { synchronized void unclaim(int piece) {
@@ -82,5 +88,15 @@ class Pieces {
synchronized void clearAll() { synchronized void clearAll() {
done.clear() done.clear()
claimed.clear() claimed.clear()
partials.clear()
}
synchronized void write(PrintWriter writer) {
for (int i = done.nextSetBit(0); i >= 0; i = done.nextSetBit(i+1)) {
writer.println(i)
}
partials.each { piece, position ->
writer.println("$piece,$position")
}
} }
} }

View File

@@ -0,0 +1,15 @@
package com.muwire.core.files
import com.muwire.core.Event
import com.muwire.core.SharedFile
class FileHashingEvent extends Event {
File hashingFile
@Override
public String toString() {
super.toString() + " hashingFile " + hashingFile.getAbsolutePath()
}
}

View File

@@ -39,6 +39,7 @@ class HasherService {
} else if (f.length() > FileHasher.MAX_SIZE) { } else if (f.length() > FileHasher.MAX_SIZE) {
eventBus.publish new FileHashedEvent(error: "$f is too large to be shared ${f.length()}") eventBus.publish new FileHashedEvent(error: "$f is too large to be shared ${f.length()}")
} else { } else {
eventBus.publish new FileHashingEvent(hashingFile: f)
def hash = hasher.hashFile f def hash = hasher.hashFile f
eventBus.publish new FileHashedEvent(sharedFile: new SharedFile(f, hash, FileHasher.getPieceSize(f.length()))) eventBus.publish new FileHashedEvent(sharedFile: new SharedFile(f, hash, FileHasher.getPieceSize(f.length())))
} }

View File

@@ -6,7 +6,8 @@ class CacheServers {
private static final int TO_GIVE = 3 private static final int TO_GIVE = 3
private static Set<Destination> CACHES = [ private static Set<Destination> CACHES = [
new Destination("Wddh2E6FyyXBF7SvUYHKdN-vjf3~N6uqQWNeBDTM0P33YjiQCOsyedrjmDZmWFrXUJfJLWnCb5bnKezfk4uDaMyj~uvDG~yvLVcFgcPWSUd7BfGgym-zqcG1q1DcM8vfun-US7YamBlmtC6MZ2j-~Igqzmgshita8aLPCfNAA6S6e2UMjjtG7QIXlxpMec75dkHdJlVWbzrk9z8Qgru3YIk0UztYgEwDNBbm9wInsbHhr3HtAfa02QcgRVqRN2PnQXuqUJs7R7~09FZPEviiIcUpkY3FeyLlX1sgQFBeGeA96blaPvZNGd6KnNdgfLgMebx5SSxC-N4KZMSMBz5cgonQF3~m2HHFRSI85zqZNG5X9bJN85t80ltiv1W1es8ZnQW4es11r7MrvJNXz5bmSH641yJIvS6qI8OJJNpFVBIQSXLD-96TayrLQPaYw~uNZ-eXaE6G5dYhiuN8xHsFI1QkdaUaVZnvDGfsRbpS5GtpUbBDbyLkdPurG0i7dN1wAAAA") new Destination("Wddh2E6FyyXBF7SvUYHKdN-vjf3~N6uqQWNeBDTM0P33YjiQCOsyedrjmDZmWFrXUJfJLWnCb5bnKezfk4uDaMyj~uvDG~yvLVcFgcPWSUd7BfGgym-zqcG1q1DcM8vfun-US7YamBlmtC6MZ2j-~Igqzmgshita8aLPCfNAA6S6e2UMjjtG7QIXlxpMec75dkHdJlVWbzrk9z8Qgru3YIk0UztYgEwDNBbm9wInsbHhr3HtAfa02QcgRVqRN2PnQXuqUJs7R7~09FZPEviiIcUpkY3FeyLlX1sgQFBeGeA96blaPvZNGd6KnNdgfLgMebx5SSxC-N4KZMSMBz5cgonQF3~m2HHFRSI85zqZNG5X9bJN85t80ltiv1W1es8ZnQW4es11r7MrvJNXz5bmSH641yJIvS6qI8OJJNpFVBIQSXLD-96TayrLQPaYw~uNZ-eXaE6G5dYhiuN8xHsFI1QkdaUaVZnvDGfsRbpS5GtpUbBDbyLkdPurG0i7dN1wAAAA"),
new Destination("JC63wJNOqSJmymkj4~UJWywBTvDGikKMoYP0HX2Wz9c5l3otXSkwnxWAFL4cKr~Ygh3BNNi2t93vuLIiI1W8AsE42kR~PwRx~Y-WvIHXR6KUejRmOp-n8WidtjKg9k4aDy428uSOedqXDxys5mpoeQXwDsv1CoPTTwnmb1GWFy~oTGIsCguCl~aJWGnqiKarPO3GJQ~ev-NbvAQzUfC3HeP1e6pdI5CGGjExahTCID5UjpJw8GaDXWlGmYWWH303Xu4x-vAHQy1dJLsOBCn8dZravsn5BKJk~j0POUon45CCx-~NYtaPe0Itt9cMdD2ciC76Rep1D0X0sm1SjlSs8sZ52KmF3oaLZ6OzgI9QLMIyBUrfi41sK5I0qTuUVBAkvW1xr~L-20dYJ9TrbOaOb2-vDIfKaxVi6xQOuhgQDiSBhd3qv2m0xGu-BM9DQYfNA0FdMjnZmqjmji9RMavzQSsVFIbQGLbrLepiEFlb7TseCK5UtRp8TxnG7L4gbYevBQAEAAcAAA==")
] ]
static List<Destination> getCacheServers() { static List<Destination> getCacheServers() {

View File

@@ -7,7 +7,7 @@ import com.muwire.core.Persona
import net.i2p.util.ConcurrentHashSet import net.i2p.util.ConcurrentHashSet
class RemoteTrustList { class RemoteTrustList {
public enum Status { NEW, UPDATING, UPDATED } public enum Status { NEW, UPDATING, UPDATED, UPDATE_FAILED }
private final Persona persona private final Persona persona
private final Set<Persona> good, bad private final Set<Persona> good, bad

View File

@@ -94,13 +94,15 @@ class TrustSubscriber {
public void run() { public void run() {
trustList.status = RemoteTrustList.Status.UPDATING trustList.status = RemoteTrustList.Status.UPDATING
eventBus.publish(new TrustSubscriptionUpdatedEvent(trustList : trustList)) eventBus.publish(new TrustSubscriptionUpdatedEvent(trustList : trustList))
check(trustList, System.currentTimeMillis()) if (check(trustList, System.currentTimeMillis()))
trustList.status = RemoteTrustList.Status.UPDATED trustList.status = RemoteTrustList.Status.UPDATED
else
trustList.status = RemoteTrustList.Status.UPDATE_FAILED
eventBus.publish(new TrustSubscriptionUpdatedEvent(trustList : trustList)) eventBus.publish(new TrustSubscriptionUpdatedEvent(trustList : trustList))
} }
} }
private void check(RemoteTrustList trustList, long now) { private boolean check(RemoteTrustList trustList, long now) {
log.info("fetching trust list from ${trustList.persona.getHumanReadableName()}") log.info("fetching trust list from ${trustList.persona.getHumanReadableName()}")
Endpoint endpoint = null Endpoint endpoint = null
try { try {
@@ -118,7 +120,7 @@ class TrustSubscriber {
if (code != 200) { if (code != 200) {
log.info("couldn't fetch trust list, code $code") log.info("couldn't fetch trust list, code $code")
return return false
} }
// swallow any headers // swallow any headers
@@ -147,8 +149,10 @@ class TrustSubscriber {
trustList.bad.clear() trustList.bad.clear()
trustList.bad.addAll(bad) trustList.bad.addAll(bad)
return true
} catch (Exception e) { } catch (Exception e) {
log.log(Level.WARNING,"exception fetching trust list from ${trustList.persona.getHumanReadableName()}",e) log.log(Level.WARNING,"exception fetching trust list from ${trustList.persona.getHumanReadableName()}",e)
return false
} finally { } finally {
endpoint?.close() endpoint?.close()
} }

View File

@@ -119,4 +119,8 @@ class ContentUploader extends Uploader {
return mesh.pieces.nPieces; return mesh.pieces.nPieces;
} }
@Override
public long getTotalSize() {
return file.length();
}
} }

View File

@@ -61,5 +61,8 @@ class HashListUploader extends Uploader {
return 1; return 1;
} }
@Override
public long getTotalSize() {
return -1;
}
} }

View File

@@ -35,5 +35,7 @@ abstract class Uploader {
abstract int getDonePieces(); abstract int getDonePieces();
abstract int getTotalPieces() abstract int getTotalPieces();
abstract long getTotalSize();
} }

View File

@@ -1,5 +1,5 @@
group = com.muwire group = com.muwire
version = 0.4.4 version = 0.4.7
groovyVersion = 2.4.15 groovyVersion = 2.4.15
slf4jVersion = 1.7.25 slf4jVersion = 1.7.25
spockVersion = 1.1-groovy-2.4 spockVersion = 1.1-groovy-2.4

View File

@@ -191,27 +191,38 @@ class MainFrameController {
int row = view.getSelectedTrustTablesRow(tableName) int row = view.getSelectedTrustTablesRow(tableName)
if (row < 0) if (row < 0)
return return
builder.getVariable(tableName).model.fireTableDataChanged()
core.eventBus.publish(new TrustEvent(persona : list[row], level : level)) core.eventBus.publish(new TrustEvent(persona : list[row], level : level))
} }
@ControllerAction @ControllerAction
void markTrusted() { void markTrusted() {
markTrust("distrusted-table", TrustLevel.TRUSTED, model.distrusted) markTrust("distrusted-table", TrustLevel.TRUSTED, model.distrusted)
model.markTrustedButtonEnabled = false
model.markNeutralFromDistrustedButtonEnabled = false
} }
@ControllerAction @ControllerAction
void markNeutralFromDistrusted() { void markNeutralFromDistrusted() {
markTrust("distrusted-table", TrustLevel.NEUTRAL, model.distrusted) markTrust("distrusted-table", TrustLevel.NEUTRAL, model.distrusted)
model.markTrustedButtonEnabled = false
model.markNeutralFromDistrustedButtonEnabled = false
} }
@ControllerAction @ControllerAction
void markDistrusted() { void markDistrusted() {
markTrust("trusted-table", TrustLevel.DISTRUSTED, model.trusted) markTrust("trusted-table", TrustLevel.DISTRUSTED, model.trusted)
model.subscribeButtonEnabled = false
model.markDistrustedButtonEnabled = false
model.markNeutralFromTrustedButtonEnabled = false
} }
@ControllerAction @ControllerAction
void markNeutralFromTrusted() { void markNeutralFromTrusted() {
markTrust("trusted-table", TrustLevel.NEUTRAL, model.trusted) markTrust("trusted-table", TrustLevel.NEUTRAL, model.trusted)
model.subscribeButtonEnabled = false
model.markDistrustedButtonEnabled = false
model.markNeutralFromTrustedButtonEnabled = false
} }
@ControllerAction @ControllerAction
@@ -223,6 +234,9 @@ class MainFrameController {
core.muOptions.trustSubscriptions.add(p) core.muOptions.trustSubscriptions.add(p)
saveMuWireSettings() saveMuWireSettings()
core.eventBus.publish(new TrustSubscriptionEvent(persona : p, subscribe : true)) core.eventBus.publish(new TrustSubscriptionEvent(persona : p, subscribe : true))
model.subscribeButtonEnabled = false
model.markDistrustedButtonEnabled = false
model.markNeutralFromTrustedButtonEnabled = false
} }
@ControllerAction @ControllerAction

View File

@@ -27,6 +27,7 @@ class TrustListController {
return return
Persona p = model.trusted[selectedRow] Persona p = model.trusted[selectedRow]
eventBus.publish(new TrustEvent(persona : p, level : TrustLevel.TRUSTED)) eventBus.publish(new TrustEvent(persona : p, level : TrustLevel.TRUSTED))
view.fireUpdate("trusted-table")
} }
@ControllerAction @ControllerAction
@@ -36,6 +37,7 @@ class TrustListController {
return return
Persona p = model.distrusted[selectedRow] Persona p = model.distrusted[selectedRow]
eventBus.publish(new TrustEvent(persona : p, level : TrustLevel.TRUSTED)) eventBus.publish(new TrustEvent(persona : p, level : TrustLevel.TRUSTED))
view.fireUpdate("distrusted-table")
} }
@ControllerAction @ControllerAction
@@ -45,6 +47,7 @@ class TrustListController {
return return
Persona p = model.trusted[selectedRow] Persona p = model.trusted[selectedRow]
eventBus.publish(new TrustEvent(persona : p, level : TrustLevel.DISTRUSTED)) eventBus.publish(new TrustEvent(persona : p, level : TrustLevel.DISTRUSTED))
view.fireUpdate("trusted-table")
} }
@ControllerAction @ControllerAction
@@ -54,5 +57,6 @@ class TrustListController {
return return
Persona p = model.distrusted[selectedRow] Persona p = model.distrusted[selectedRow]
eventBus.publish(new TrustEvent(persona : p, level : TrustLevel.DISTRUSTED)) eventBus.publish(new TrustEvent(persona : p, level : TrustLevel.DISTRUSTED))
view.fireUpdate("distrusted-table")
} }
} }

View File

@@ -49,7 +49,7 @@ class Ready extends AbstractLifecycleHandler {
log.info("creating new properties") log.info("creating new properties")
props = new MuWireSettings() props = new MuWireSettings()
props.embeddedRouter = Boolean.parseBoolean(System.getProperties().getProperty("embeddedRouter")) props.embeddedRouter = Boolean.parseBoolean(System.getProperties().getProperty("embeddedRouter"))
props.updateType = System.getProperty("updateType") props.updateType = System.getProperty("updateType","jar")
def nickname def nickname
while (true) { while (true) {
nickname = JOptionPane.showInputDialog(null, nickname = JOptionPane.showInputDialog(null,

View File

@@ -1,6 +1,8 @@
package com.muwire.gui package com.muwire.gui
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import java.util.Calendar
import java.util.UUID
import javax.annotation.Nonnull import javax.annotation.Nonnull
import javax.inject.Inject import javax.inject.Inject
@@ -20,6 +22,7 @@ import com.muwire.core.download.Downloader
import com.muwire.core.files.AllFilesLoadedEvent import com.muwire.core.files.AllFilesLoadedEvent
import com.muwire.core.files.FileDownloadedEvent import com.muwire.core.files.FileDownloadedEvent
import com.muwire.core.files.FileHashedEvent import com.muwire.core.files.FileHashedEvent
import com.muwire.core.files.FileHashingEvent
import com.muwire.core.files.FileLoadedEvent import com.muwire.core.files.FileLoadedEvent
import com.muwire.core.files.FileSharedEvent import com.muwire.core.files.FileSharedEvent
import com.muwire.core.files.FileUnsharedEvent import com.muwire.core.files.FileUnsharedEvent
@@ -70,12 +73,19 @@ class MainFrameModel {
@Observable int connections @Observable int connections
@Observable String me @Observable String me
@Observable int loadedFiles
@Observable File hashingFile
@Observable boolean downloadActionEnabled @Observable boolean downloadActionEnabled
@Observable boolean trustButtonsEnabled @Observable boolean trustButtonsEnabled
@Observable boolean cancelButtonEnabled @Observable boolean cancelButtonEnabled
@Observable boolean retryButtonEnabled @Observable boolean retryButtonEnabled
@Observable boolean pauseButtonEnabled @Observable boolean pauseButtonEnabled
@Observable String resumeButtonText @Observable String resumeButtonText
@Observable boolean subscribeButtonEnabled
@Observable boolean markNeutralFromTrustedButtonEnabled
@Observable boolean markDistrustedButtonEnabled
@Observable boolean markNeutralFromDistrustedButtonEnabled
@Observable boolean markTrustedButtonEnabled
@Observable boolean reviewButtonEnabled @Observable boolean reviewButtonEnabled
@Observable boolean updateButtonEnabled @Observable boolean updateButtonEnabled
@Observable boolean unsubscribeButtonEnabled @Observable boolean unsubscribeButtonEnabled
@@ -140,6 +150,7 @@ class MainFrameModel {
core.eventBus.register(ConnectionEvent.class, this) core.eventBus.register(ConnectionEvent.class, this)
core.eventBus.register(DisconnectionEvent.class, this) core.eventBus.register(DisconnectionEvent.class, this)
core.eventBus.register(FileHashedEvent.class, this) core.eventBus.register(FileHashedEvent.class, this)
core.eventBus.register(FileHashingEvent.class, this)
core.eventBus.register(FileLoadedEvent.class, this) core.eventBus.register(FileLoadedEvent.class, this)
core.eventBus.register(UploadEvent.class, this) core.eventBus.register(UploadEvent.class, this)
core.eventBus.register(UploadFinishedEvent.class, this) core.eventBus.register(UploadFinishedEvent.class, this)
@@ -256,7 +267,17 @@ class MainFrameModel {
} }
} }
void onFileHashingEvent(FileHashingEvent e) {
runInsideUIAsync {
loadedFiles = shared.size()
hashingFile = e.hashingFile
}
}
void onFileHashedEvent(FileHashedEvent e) { void onFileHashedEvent(FileHashedEvent e) {
runInsideUIAsync {
hashingFile = null
}
if (e.error != null) if (e.error != null)
return // TODO do something return // TODO do something
if (infoHashes.contains(e.sharedFile.infoHash)) if (infoHashes.contains(e.sharedFile.infoHash))
@@ -264,6 +285,7 @@ class MainFrameModel {
infoHashes.add(e.sharedFile.infoHash) infoHashes.add(e.sharedFile.infoHash)
runInsideUIAsync { runInsideUIAsync {
shared << e.sharedFile shared << e.sharedFile
loadedFiles = shared.size()
JTable table = builder.getVariable("shared-files-table") JTable table = builder.getVariable("shared-files-table")
table.model.fireTableDataChanged() table.model.fireTableDataChanged()
} }
@@ -275,6 +297,7 @@ class MainFrameModel {
infoHashes.add(e.loadedFile.infoHash) infoHashes.add(e.loadedFile.infoHash)
runInsideUIAsync { runInsideUIAsync {
shared << e.loadedFile shared << e.loadedFile
loadedFiles = shared.size()
JTable table = builder.getVariable("shared-files-table") JTable table = builder.getVariable("shared-files-table")
table.model.fireTableDataChanged() table.model.fireTableDataChanged()
} }
@@ -286,6 +309,7 @@ class MainFrameModel {
return return
runInsideUIAsync { runInsideUIAsync {
shared.remove(e.unsharedFile) shared.remove(e.unsharedFile)
loadedFiles = shared.size()
JTable table = builder.getVariable("shared-files-table") JTable table = builder.getVariable("shared-files-table")
table.model.fireTableDataChanged() table.model.fireTableDataChanged()
} }
@@ -355,10 +379,32 @@ class MainFrameModel {
return return
} }
runInsideUIAsync { runInsideUIAsync {
searches.addFirst(new IncomingSearch(search : search, replyTo : e.replyTo, originator : e.originator)) JTable table = builder.getVariable("searches-table")
Boolean searchFound = false
Iterator searchIter = searches.iterator()
while ( searchIter.hasNext() ) {
IncomingSearch searchEle = searchIter.next()
if ( searchEle.search == search
&& searchEle.originator == e.originator
&& searchEle.uuid == e.searchEvent.getUuid() ) {
searchIter.remove()
table.model.fireTableDataChanged()
searchFound = true
searchEle.count++
searchEle.timestamp = Calendar.getInstance()
searches.addFirst(searchEle)
break
}
}
if (!searchFound) {
searches.addFirst(new IncomingSearch(search, e.replyTo, e.originator, e.searchEvent.getUuid()))
}
while(searches.size() > 200) while(searches.size() > 200)
searches.removeLast() searches.removeLast()
JTable table = builder.getVariable("searches-table")
table.model.fireTableDataChanged() table.model.fireTableDataChanged()
} }
} }
@@ -367,6 +413,18 @@ class MainFrameModel {
String search String search
Destination replyTo Destination replyTo
Persona originator Persona originator
long count
UUID uuid
Calendar timestamp
IncomingSearch( String search, Destination replyTo, Persona originator, UUID uuid ) {
this.search = search
this.replyTo = replyTo
this.originator = originator
this.uuid = uuid
this.count = 1
this.timestamp = Calendar.getInstance()
}
} }
void onUpdateAvailableEvent(UpdateAvailableEvent e) { void onUpdateAvailableEvent(UpdateAvailableEvent e) {

View File

@@ -44,7 +44,7 @@ class I2PStatusView {
label(text : "Participating Tunnels", constraints : gbc(gridx:0, gridy:4)) label(text : "Participating Tunnels", constraints : gbc(gridx:0, gridy:4))
label(text : bind {model.participatingTunnels}, constraints : gbc(gridx: 1, gridy:4)) label(text : bind {model.participatingTunnels}, constraints : gbc(gridx: 1, gridy:4))
label(text : "Participating Bandwidth", constraints : gbc(gridx:0, gridy:5)) label(text : "Participating Bandwidth", constraints : gbc(gridx:0, gridy:5))
label(text : bind {model.participatingBW}, constraints : gbc(gridx: 1, gridy:6)) label(text : bind {model.participatingBW}, constraints : gbc(gridx: 1, gridy:5))
label(text : "Active Peers", constraints : gbc(gridx:0, gridy:6)) label(text : "Active Peers", constraints : gbc(gridx:0, gridy:6))
label(text : bind {model.activePeers}, constraints : gbc(gridx: 1, gridy:6)) label(text : bind {model.activePeers}, constraints : gbc(gridx: 1, gridy:6))
label(text : "Receive Bps (15 seconds)", constraints : gbc(gridx:0, gridy:7)) label(text : "Receive Bps (15 seconds)", constraints : gbc(gridx:0, gridy:7))

View File

@@ -129,12 +129,19 @@ class MainFrameView {
scrollPane (constraints : BorderLayout.CENTER) { scrollPane (constraints : BorderLayout.CENTER) {
downloadsTable = table(id : "downloads-table", autoCreateRowSorter : true) { downloadsTable = table(id : "downloads-table", autoCreateRowSorter : true) {
tableModel(list: model.downloads) { tableModel(list: model.downloads) {
closureColumn(header: "Name", preferredWidth: 350, type: String, read : {row -> row.downloader.file.getName()}) closureColumn(header: "Name", preferredWidth: 300, type: String, read : {row -> row.downloader.file.getName()})
closureColumn(header: "Status", preferredWidth: 50, type: String, read : {row -> row.downloader.getCurrentState().toString()}) closureColumn(header: "Status", preferredWidth: 50, type: String, read : {row -> row.downloader.getCurrentState().toString()})
closureColumn(header: "Progress", preferredWidth: 20, type: String, read: { row -> closureColumn(header: "Progress", preferredWidth: 70, type: String, read: { row ->
int pieces = row.downloader.nPieces int pieces = row.downloader.nPieces
int done = row.downloader.donePieces() int done = row.downloader.donePieces()
"$done/$pieces pieces".toString() int percent = -1
if ( row.downloader.nPieces != 0 ) {
percent = (done * 100) / pieces
}
long size = row.downloader.pieceSize
size *= pieces
String totalSize = DataHelper.formatSize2Decimal(size, false) + "B"
String.format("%02d", percent) + "% of ${totalSize} ($done/$pieces pcs)".toString()
}) })
closureColumn(header: "Sources", preferredWidth : 10, type: Integer, read : {row -> row.downloader.activeWorkers()}) closureColumn(header: "Sources", preferredWidth : 10, type: Integer, read : {row -> row.downloader.activeWorkers()})
closureColumn(header: "Speed", preferredWidth: 50, type:String, read :{row -> closureColumn(header: "Speed", preferredWidth: 50, type:String, read :{row ->
@@ -154,12 +161,20 @@ class MainFrameView {
panel (constraints: "uploads window"){ panel (constraints: "uploads window"){
gridLayout(cols : 1, rows : 2) gridLayout(cols : 1, rows : 2)
panel { panel {
borderLayout()
panel (constraints : BorderLayout.NORTH) {
label(text : bind {
if (model.hashingFile == null) {
""
} else {
"hashing: " + model.hashingFile.getAbsolutePath() + " (" + DataHelper.formatSize2Decimal(model.hashingFile.length(), false).toString() + "B)"
}
})
}
panel (border : etchedBorder(), constraints : BorderLayout.CENTER) {
gridLayout(cols : 2, rows : 1) gridLayout(cols : 2, rows : 1)
panel { panel {
borderLayout() borderLayout()
panel (constraints : BorderLayout.NORTH) {
button(text : "Add directories to watch", actionPerformed : watchDirectories)
}
scrollPane (constraints : BorderLayout.CENTER) { scrollPane (constraints : BorderLayout.CENTER) {
table(id : "watched-directories-table", autoCreateRowSorter: true) { table(id : "watched-directories-table", autoCreateRowSorter: true) {
tableModel(list : model.watched) { tableModel(list : model.watched) {
@@ -170,9 +185,6 @@ class MainFrameView {
} }
panel { panel {
borderLayout() borderLayout()
panel (constraints : BorderLayout.NORTH) {
button(text : "Share files", actionPerformed : shareFiles)
}
scrollPane(constraints : BorderLayout.CENTER) { scrollPane(constraints : BorderLayout.CENTER) {
table(id : "shared-files-table", autoCreateRowSorter: true) { table(id : "shared-files-table", autoCreateRowSorter: true) {
tableModel(list : model.shared) { tableModel(list : model.shared) {
@@ -183,7 +195,19 @@ class MainFrameView {
} }
} }
} }
panel (constraints : BorderLayout.SOUTH) {
gridLayout(rows:1, cols:2)
panel { panel {
button(text : "Add directories to watch", actionPerformed : watchDirectories)
button(text : "Share files", actionPerformed : shareFiles)
}
panel {
label("Shared:")
label(text : bind {model.loadedFiles.toString()})
}
}
}
panel (border : etchedBorder()) {
borderLayout() borderLayout()
panel (constraints : BorderLayout.NORTH){ panel (constraints : BorderLayout.NORTH){
label("Uploads") label("Uploads")
@@ -200,7 +224,18 @@ class MainFrameView {
row.getDownloader() row.getDownloader()
}) })
closureColumn(header : "Remote Pieces", type : String, read : { row -> closureColumn(header : "Remote Pieces", type : String, read : { row ->
"${row.getDonePieces()}/${row.getTotalPieces()}".toString() int pieces = row.getTotalPieces()
int done = row.getDonePieces()
int percent = -1
if ( pieces != 0 ) {
percent = (done * 100) / pieces
}
long size = row.getTotalSize()
String totalSize = ""
if (size >= 0 ) {
totalSize = " of " + DataHelper.formatSize2Decimal(size, false) + "B"
}
String.format("%02d", percent) + "% ${totalSize} ($done/$pieces pcs)".toString()
}) })
} }
} }
@@ -247,6 +282,14 @@ class MainFrameView {
return it.replyTo.toBase32() return it.replyTo.toBase32()
} }
}) })
closureColumn(header : "Count", type : String, read : {
it.count.toString()
})
closureColumn(header : "Timestamp", type : String, read : {
String.format("%02d", it.timestamp.get(Calendar.HOUR_OF_DAY)) + ":" +
String.format("%02d", it.timestamp.get(Calendar.MINUTE)) + ":" +
String.format("%02d", it.timestamp.get(Calendar.SECOND))
})
} }
} }
} }
@@ -265,11 +308,11 @@ class MainFrameView {
} }
} }
} }
panel (constraints : BorderLayout.EAST) { panel (constraints : BorderLayout.SOUTH) {
gridBagLayout() gridBagLayout()
button(text : "Mark Neutral", constraints : gbc(gridx: 0, gridy: 0), markNeutralFromTrustedAction) button(text : "Subscribe", enabled : bind {model.subscribeButtonEnabled}, constraints : gbc(gridx: 0, gridy : 0), subscribeAction)
button(text : "Mark Distrusted", constraints : gbc(gridx: 0, gridy:1), markDistrustedAction) button(text : "Mark Neutral", enabled : bind {model.markNeutralFromTrustedButtonEnabled}, constraints : gbc(gridx: 1, gridy: 0), markNeutralFromTrustedAction)
button(text : "Subscribe", constraints : gbc(gridx: 0, gridy : 2), subscribeAction) button(text : "Mark Distrusted", enabled : bind {model.markDistrustedButtonEnabled}, constraints : gbc(gridx: 2, gridy:0), markDistrustedAction)
} }
} }
panel (border : etchedBorder()){ panel (border : etchedBorder()){
@@ -281,10 +324,10 @@ class MainFrameView {
} }
} }
} }
panel(constraints : BorderLayout.WEST) { panel(constraints : BorderLayout.SOUTH) {
gridBagLayout() gridBagLayout()
button(text: "Mark Neutral", constraints: gbc(gridx: 0, gridy: 0), markNeutralFromDistrustedAction) button(text: "Mark Neutral", enabled : bind {model.markNeutralFromDistrustedButtonEnabled}, constraints: gbc(gridx: 0, gridy: 0), markNeutralFromDistrustedAction)
button(text: "Mark Trusted", constraints : gbc(gridx: 0, gridy : 1), markTrustedAction) button(text: "Mark Trusted", enabled : bind {model.markTrustedButtonEnabled}, constraints : gbc(gridx: 1, gridy : 0), markTrustedAction)
} }
} }
} }
@@ -296,11 +339,11 @@ class MainFrameView {
scrollPane(constraints : BorderLayout.CENTER) { scrollPane(constraints : BorderLayout.CENTER) {
table(id : "subscription-table", autoCreateRowSorter : true) { table(id : "subscription-table", autoCreateRowSorter : true) {
tableModel(list : model.subscriptions) { tableModel(list : model.subscriptions) {
closureColumn(header : "Name", type: String, read : {it.persona.getHumanReadableName()}) closureColumn(header : "Name", preferredWidth: 200, type: String, read : {it.persona.getHumanReadableName()})
closureColumn(header : "Trusted", type: Integer, read : {it.good.size()}) closureColumn(header : "Trusted", preferredWidth : 20, type: Integer, read : {it.good.size()})
closureColumn(header : "Distrusted", type: Integer, read : {it.bad.size()}) closureColumn(header : "Distrusted", preferredWidth: 20, type: Integer, read : {it.bad.size()})
closureColumn(header : "Status", type: String, read : {it.status.toString()}) closureColumn(header : "Status", preferredWidth: 30, type: String, read : {it.status.toString()})
closureColumn(header : "Last Updated", type : String, read : { closureColumn(header : "Last Updated", preferredWidth: 200, type : String, read : {
if (it.timestamp == 0) if (it.timestamp == 0)
return "Never" return "Never"
else else
@@ -461,6 +504,7 @@ class MainFrameView {
// subscription table // subscription table
def subscriptionTable = builder.getVariable("subscription-table") def subscriptionTable = builder.getVariable("subscription-table")
subscriptionTable.setDefaultRenderer(Integer.class, centerRenderer)
subscriptionTable.rowSorter.addRowSorterListener({evt -> trustTablesSortEvents["subscription-table"] = evt}) subscriptionTable.rowSorter.addRowSorterListener({evt -> trustTablesSortEvents["subscription-table"] = evt})
subscriptionTable.rowSorter.setSortsOnUpdates(true) subscriptionTable.rowSorter.setSortsOnUpdates(true)
selectionModel = subscriptionTable.getSelectionModel() selectionModel = subscriptionTable.getSelectionModel()
@@ -488,6 +532,11 @@ class MainFrameView {
model.updateButtonEnabled = true model.updateButtonEnabled = true
model.unsubscribeButtonEnabled = true model.unsubscribeButtonEnabled = true
break break
case RemoteTrustList.Status.UPDATE_FAILED:
model.reviewButtonEnabled = false
model.updateButtonEnabled = true
model.unsubscribeButtonEnabled = true
break
} }
}) })
@@ -495,11 +544,37 @@ class MainFrameView {
def trustedTable = builder.getVariable("trusted-table") def trustedTable = builder.getVariable("trusted-table")
trustedTable.rowSorter.addRowSorterListener({evt -> trustTablesSortEvents["trusted-table"] = evt}) trustedTable.rowSorter.addRowSorterListener({evt -> trustTablesSortEvents["trusted-table"] = evt})
trustedTable.rowSorter.setSortsOnUpdates(true) trustedTable.rowSorter.setSortsOnUpdates(true)
selectionModel = trustedTable.getSelectionModel()
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
selectionModel.addListSelectionListener({
int selectedRow = getSelectedTrustTablesRow("trusted-table")
if (selectedRow < 0) {
model.subscribeButtonEnabled = false
model.markDistrustedButtonEnabled = false
model.markNeutralFromTrustedButtonEnabled = false
} else {
model.subscribeButtonEnabled = true
model.markDistrustedButtonEnabled = true
model.markNeutralFromTrustedButtonEnabled = true
}
})
// distrusted table // distrusted table
def distrustedTable = builder.getVariable("distrusted-table") def distrustedTable = builder.getVariable("distrusted-table")
distrustedTable.rowSorter.addRowSorterListener({evt -> trustTablesSortEvents["distrusted-table"] = evt}) distrustedTable.rowSorter.addRowSorterListener({evt -> trustTablesSortEvents["distrusted-table"] = evt})
distrustedTable.rowSorter.setSortsOnUpdates(true) distrustedTable.rowSorter.setSortsOnUpdates(true)
selectionModel = distrustedTable.getSelectionModel()
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
selectionModel.addListSelectionListener({
int selectedRow = getSelectedTrustTablesRow("distrusted-table")
if (selectedRow < 0) {
model.markTrustedButtonEnabled = false
model.markNeutralFromDistrustedButtonEnabled = false
} else {
model.markTrustedButtonEnabled = true
model.markNeutralFromDistrustedButtonEnabled = true
}
})
} }
private static void showPopupMenu(JPopupMenu menu, MouseEvent event) { private static void showPopupMenu(JPopupMenu menu, MouseEvent event) {

View File

@@ -83,10 +83,12 @@ class TrustListView {
def trustedTable = builder.getVariable("trusted-table") def trustedTable = builder.getVariable("trusted-table")
trustedTable.rowSorter.addRowSorterListener({evt -> sortEvents["trusted-table"] = evt}) trustedTable.rowSorter.addRowSorterListener({evt -> sortEvents["trusted-table"] = evt})
trustedTable.rowSorter.setSortsOnUpdates(true)
trustedTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION) trustedTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
def distrustedTable = builder.getVariable("distrusted-table") def distrustedTable = builder.getVariable("distrusted-table")
distrustedTable.rowSorter.addRowSorterListener({evt -> sortEvents["distrusted-table"] = evt}) distrustedTable.rowSorter.addRowSorterListener({evt -> sortEvents["distrusted-table"] = evt})
distrustedTable.rowSorter.setSortsOnUpdates(true)
distrustedTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION) distrustedTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
dialog.getContentPane().add(mainPanel) dialog.getContentPane().add(mainPanel)
@@ -110,4 +112,9 @@ class TrustListView {
selectedRow = table.rowSorter.convertRowIndexToModel(selectedRow) selectedRow = table.rowSorter.convertRowIndexToModel(selectedRow)
selectedRow selectedRow
} }
void fireUpdate(String tableName) {
def table = builder.getVariable(tableName)
table.model.fireTableDataChanged()
}
} }

View File

@@ -11,6 +11,7 @@ import net.i2p.client.I2PSession
import net.i2p.client.I2PSessionMuxedListener import net.i2p.client.I2PSessionMuxedListener
import net.i2p.client.datagram.I2PDatagramDissector import net.i2p.client.datagram.I2PDatagramDissector
import net.i2p.client.datagram.I2PDatagramMaker import net.i2p.client.datagram.I2PDatagramMaker
import net.i2p.crypto.SigType
import net.i2p.util.SystemVersion import net.i2p.util.SystemVersion
import net.i2p.data.* import net.i2p.data.*
@@ -43,7 +44,7 @@ public class HostCache {
def session def session
if (!keyfile.exists()) { if (!keyfile.exists()) {
def os = new FileOutputStream(keyfile); def os = new FileOutputStream(keyfile);
myDest = i2pClient.createDestination(os) myDest = i2pClient.createDestination(os, SigType.EdDSA_SHA512_Ed25519)
os.close() os.close()
println "No key.dat file was found, so creating a new destination." println "No key.dat file was found, so creating a new destination."
println "This is the destination you want to give out for your new HostCache" println "This is the destination you want to give out for your new HostCache"
@@ -63,6 +64,9 @@ public class HostCache {
Timer timer = new Timer("timer", true) Timer timer = new Timer("timer", true)
timer.schedule({hostPool.age()} as TimerTask, 1000,1000) timer.schedule({hostPool.age()} as TimerTask, 1000,1000)
timer.schedule({crawler.startCrawl()} as TimerTask, 10000, 10000) timer.schedule({crawler.startCrawl()} as TimerTask, 10000, 10000)
File verified = new File("verified.json")
File unverified = new File("unverified.json")
timer.schedule({hostPool.serialize(verified, unverified)} as TimerTask, 10000, 60 * 60 * 1000)
session.addMuxedSessionListener(new Listener(hostPool: hostPool, toReturn: 2, crawler: crawler), session.addMuxedSessionListener(new Listener(hostPool: hostPool, toReturn: 2, crawler: crawler),
I2PSession.PROTO_DATAGRAM, I2PSession.PORT_ANY) I2PSession.PROTO_DATAGRAM, I2PSession.PORT_ANY)

View File

@@ -2,6 +2,8 @@ package com.muwire.hostcache
import java.util.stream.Collectors import java.util.stream.Collectors
import groovy.json.JsonOutput
class HostPool { class HostPool {
final def maxFailures final def maxFailures
@@ -74,4 +76,25 @@ class HostPool {
} }
} }
} }
synchronized void serialize(File verifiedFile, File unverifiedFile) {
write(verifiedFile, verified.values())
write(unverifiedFile, unverified.values())
}
private void write(File target, Collection hosts) {
JsonOutput jsonOutput = new JsonOutput()
target.withPrintWriter { writer ->
hosts.each {
def json = [:]
json.destination = it.destination.toBase64()
json.verifyTime = it.verifyTime
json.leafSlots = it.leafSlots
json.peerSlots = it.peerSlots
json.verificationFailures = it.verificationFailures
def str = jsonOutput.toJson(json)
writer.println(str)
}
}
}
} }