Compare commits

...

42 Commits

Author SHA1 Message Date
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
Zlatin Balevsky
31e30e3d31 excludePeerCaps 2019-07-01 18:31:58 +01:00
Zlatin Balevsky
8caf6e99b0 show floodfill status 2019-07-01 13:18:31 +01:00
Zlatin Balevsky
624155debd update todo 2019-07-01 06:17:46 +01:00
Zlatin Balevsky
4468a262ae actually add timestamps to the list 2019-06-30 21:40:18 +01:00
Zlatin Balevsky
1780901cb0 throttle connections to 10 searches per second 2019-06-30 21:22:49 +01:00
Zlatin Balevsky
d830d9261f canonicalize before checking if file is already shared 2019-06-30 17:12:25 +01:00
Zlatin Balevsky
f5e1833a48 Release 0.4.4 2019-06-30 15:55:23 +01:00
Zlatin Balevsky
9feb2a3c8f fix NPE on update search 2019-06-30 15:11:13 +01:00
Zlatin Balevsky
b27665f5dd Merge pull request #5 from 0rC0/patch-1
code markdown for commands and paths in README.md
2019-06-30 13:45:36 +01:00
orco
4465aa4134 code markdown for commands and paths in README.md
... instead of quotes
2019-06-30 14:27:33 +02:00
Zlatin Balevsky
ad766ac748 try to unmap files when done 2019-06-30 13:20:26 +01:00
Zlatin Balevsky
d9e7d67d86 javadoc 2019-06-30 12:51:34 +01:00
Zlatin Balevsky
3fefbc94b3 utility to decode personas 2019-06-30 10:41:42 +01:00
Zlatin Balevsky
21034209a5 add ? to split pattern 2019-06-30 06:29:46 +01:00
Zlatin Balevsky
7c04c0f83c unshare individual file 2019-06-30 05:44:08 +01:00
Zlatin Balevsky
f5293d65dd update todo 2019-06-29 16:00:49 +01:00
Zlatin Balevsky
8191bf6066 Release 0.4.3 2019-06-29 10:44:15 +01:00
Zlatin Balevsky
29b6bfd463 support different update types 2019-06-29 10:31:27 +01:00
Zlatin Balevsky
2f3d23bc34 fixes 2019-06-29 10:12:50 +01:00
Zlatin Balevsky
98dd80c4b8 fix 2019-06-29 10:03:58 +01:00
Zlatin Balevsky
d9edb2e128 ability to download updates automatically 2019-06-29 09:23:27 +01:00
Zlatin Balevsky
de04b40b86 Release 0.4.2 2019-06-29 07:17:45 +01:00
Zlatin Balevsky
7206a3d926 more i2p metrics 2019-06-29 07:07:48 +01:00
Zlatin Balevsky
98b98d8938 I2P status panel 2019-06-29 06:33:53 +01:00
Zlatin Balevsky
294b8fcc2f MW status window 2019-06-29 05:58:46 +01:00
Zlatin Balevsky
32f601a1b1 add ability to change i2p port 2019-06-28 23:53:22 +01:00
Zlatin Balevsky
8e3a398080 Release 0.4.1 2019-06-28 16:42:37 +01:00
Zlatin Balevsky
720b9688b4 Add unsharing of directories 2019-06-28 16:08:04 +01:00
Zlatin Balevsky
e3066161c5 do not perform filesystem operations in the UI thread 2019-06-27 23:29:48 +01:00
Zlatin Balevsky
a9aa3a524f disable i2cp interface on embedded router 2019-06-27 09:56:18 +01:00
Zlatin Balevsky
92848e818a on empty properties source from java props 2019-06-27 03:47:56 +01:00
Zlatin Balevsky
a7aa3008c0 bandwidth settings 2019-06-27 00:42:27 +01:00
Zlatin Balevsky
485325e824 embedded router except for logs 2019-06-26 23:25:22 +01:00
Zlatin Balevsky
0df2a0e039 start work on embedded router 2019-06-26 22:39:25 +01:00
Zlatin Balevsky
fb7b4466c2 update readme 2019-06-26 22:05:04 +01:00
44 changed files with 921 additions and 102 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.
The current stable release - 0.2.5 is avaiable for download at http://muwire.com. You can find technical documentation in the "doc" folder.
The current stable release - 0.4.0 is avaiable for download at https://muwire.com. You can find technical documentation in the "doc" folder.
### Building
@@ -23,7 +23,7 @@ Some of the UI tests will fail because they haven't been written yet :-/
### Running
You need to have an I2P router up and running on the same machine. After you build the application, look inside "gui/build/distributions". Untar/unzip one of the "shadow" files and then run the jar contained inside by typing "java -jar MuWire-x.y.z.jar" in a terminal or command prompt. If you use a custom I2CP host and port, create a file $HOME/.MuWire/i2p.properties and put "i2cp.tcp.host=<host>" and "i2cp.tcp.port=<port>" in there.
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.
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.

View File

@@ -20,10 +20,6 @@ For helping users make better decisions whom to trust
To allow every user to not route queries for content they do not like. This is mostly GUI work, the backend part is simple
##### Packaging With JRE, Embedded Router
For ease of deployment for new users, and so that users do not need to run a separate I2P router
##### Web UI, REST Interface, etc.
Basically any non-gui non-cli user interface
@@ -36,5 +32,4 @@ To enable parsing of metadata from known file types and the user editing it or a
* Wrapper of some kind for in-place upgrades
* Download file sequentially
* Unsharing of files
* Multiple-selection download, Ctrl-A

View File

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

View File

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

View File

@@ -2,6 +2,7 @@ apply plugin : 'application'
mainClassName = 'com.muwire.core.Core'
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties']
dependencies {
compile 'net.i2p:router:0.9.40'
compile 'net.i2p.client:mstreaming:0.9.40'
compile 'net.i2p.client:streaming:0.9.40'

View File

@@ -9,5 +9,5 @@ class Constants {
public static final int MAX_HEADER_SIZE = 0x1 << 14
public static final int MAX_HEADERS = 16
public static final String SPLIT_PATTERN = "[\\*\\+\\-,\\.:;\\(\\)=_/\\\\\\!\\\"\\\'\\\$%\\|\\[\\]\\{\\}]"
public static final String SPLIT_PATTERN = "[\\*\\+\\-,\\.:;\\(\\)=_/\\\\\\!\\\"\\\'\\\$%\\|\\[\\]\\{\\}\\?]"
}

View File

@@ -28,6 +28,7 @@ import com.muwire.core.files.FileUnsharedEvent
import com.muwire.core.files.HasherService
import com.muwire.core.files.PersisterService
import com.muwire.core.files.AllFilesLoadedEvent
import com.muwire.core.files.DirectoryUnsharedEvent
import com.muwire.core.files.DirectoryWatcher
import com.muwire.core.hostcache.CacheClient
import com.muwire.core.hostcache.HostCache
@@ -38,6 +39,7 @@ import com.muwire.core.search.ResultsEvent
import com.muwire.core.search.ResultsSender
import com.muwire.core.search.SearchEvent
import com.muwire.core.search.SearchManager
import com.muwire.core.search.UIResultBatchEvent
import com.muwire.core.trust.TrustEvent
import com.muwire.core.trust.TrustService
import com.muwire.core.update.UpdateClient
@@ -59,6 +61,9 @@ import net.i2p.data.PrivateKey
import net.i2p.data.Signature
import net.i2p.data.SigningPrivateKey
import net.i2p.router.Router
import net.i2p.router.RouterContext
@Log
public class Core {
@@ -80,15 +85,63 @@ public class Core {
private final DownloadManager downloadManager
private final DirectoryWatcher directoryWatcher
final FileManager fileManager
final UploadManager uploadManager
private final Router router
final AtomicBoolean shutdown = new AtomicBoolean()
public Core(MuWireSettings props, File home, String myVersion) {
this.home = home
this.muOptions = props
log.info "Initializing I2P context"
I2PAppContext.getGlobalContext().logManager()
I2PAppContext.getGlobalContext()._logManager = new MuWireLogManager()
i2pOptions = new Properties()
def i2pOptionsFile = new File(home,"i2p.properties")
if (i2pOptionsFile.exists()) {
i2pOptionsFile.withInputStream { i2pOptions.load(it) }
if (!i2pOptions.containsKey("inbound.nickname"))
i2pOptions["inbound.nickname"] = "MuWire"
if (!i2pOptions.containsKey("outbound.nickname"))
i2pOptions["outbound.nickname"] = "MuWire"
} else {
i2pOptions["inbound.nickname"] = "MuWire"
i2pOptions["outbound.nickname"] = "MuWire"
i2pOptions["inbound.length"] = "3"
i2pOptions["inbound.quantity"] = "4"
i2pOptions["outbound.length"] = "3"
i2pOptions["outbound.quantity"] = "4"
i2pOptions["i2cp.tcp.host"] = "127.0.0.1"
i2pOptions["i2cp.tcp.port"] = "7654"
Random r = new Random()
int port = r.nextInt(60000) + 4000
i2pOptions["i2np.ntcp.port"] = String.valueOf(port)
i2pOptions["i2np.udp.port"] = String.valueOf(port)
i2pOptionsFile.withOutputStream { i2pOptions.store(it, "") }
}
if (!props.embeddedRouter) {
log.info "Initializing I2P context"
I2PAppContext.getGlobalContext().logManager()
I2PAppContext.getGlobalContext()._logManager = new MuWireLogManager()
router = null
} else {
log.info("launching embedded router")
Properties routerProps = new Properties()
routerProps.setProperty("i2p.dir.config", home.getAbsolutePath())
routerProps.setProperty("router.excludePeerCaps", "KLM")
routerProps.setProperty("i2np.inboundKBytesPerSecond", String.valueOf(props.inBw))
routerProps.setProperty("i2np.outboundKBytesPerSecond", String.valueOf(props.outBw))
routerProps.setProperty("i2cp.disableInterface", "true")
routerProps.setProperty("i2np.ntcp.port", i2pOptions["i2np.ntcp.port"])
routerProps.setProperty("i2np.udp.port", i2pOptions["i2np.udp.port"])
routerProps.setProperty("i2np.udp.internalPort", i2pOptions["i2np.udp.port"])
router = new Router(routerProps)
I2PAppContext.getGlobalContext().metaClass = new RouterContextMetaClass()
router.runRouter()
while(!router.isRunning())
Thread.sleep(100)
}
log.info("initializing I2P socket manager")
def i2pClient = new I2PClientFactory().createClient()
@@ -100,25 +153,6 @@ public class Core {
}
}
i2pOptions = new Properties()
def i2pOptionsFile = new File(home,"i2p.properties")
if (i2pOptionsFile.exists()) {
i2pOptionsFile.withInputStream { i2pOptions.load(it) }
if (!i2pOptions.containsKey("inbound.nickname"))
i2pOptions["inbound.nickname"] = "MuWire"
if (!i2pOptions.containsKey("outbound.nickname"))
i2pOptions["outbound.nickname"] = "MuWire"
} else {
i2pOptions["inbound.nickname"] = "MuWire"
i2pOptions["outbound.nickname"] = "MuWire"
i2pOptions["inbound.length"] = "3"
i2pOptions["inbound.quantity"] = "4"
i2pOptions["outbound.length"] = "3"
i2pOptions["outbound.quantity"] = "4"
i2pOptions["i2cp.tcp.host"] = "127.0.0.1"
i2pOptions["i2cp.tcp.port"] = "7654"
}
// options like tunnel length and quantity
I2PSession i2pSession
@@ -172,6 +206,7 @@ public class Core {
eventBus.register(FileDownloadedEvent.class, fileManager)
eventBus.register(FileUnsharedEvent.class, fileManager)
eventBus.register(SearchEvent.class, fileManager)
eventBus.register(DirectoryUnsharedEvent.class, fileManager)
log.info("initializing mesh manager")
MeshManager meshManager = new MeshManager(fileManager, home, props)
@@ -200,7 +235,9 @@ public class Core {
cacheClient = new CacheClient(eventBus,hostCache, connectionManager, i2pSession, props, 10000)
log.info("initializing update client")
updateClient = new UpdateClient(eventBus, i2pSession, myVersion, props)
updateClient = new UpdateClient(eventBus, i2pSession, myVersion, props, fileManager, me)
eventBus.register(FileDownloadedEvent.class, updateClient)
eventBus.register(UIResultBatchEvent.class, updateClient)
log.info("initializing connector")
I2PConnector i2pConnector = new I2PConnector(socketManager)
@@ -224,7 +261,7 @@ public class Core {
eventBus.register(UIDownloadResumedEvent.class, downloadManager)
log.info("initializing upload manager")
UploadManager uploadManager = new UploadManager(eventBus, fileManager, meshManager, downloadManager)
uploadManager = new UploadManager(eventBus, fileManager, meshManager, downloadManager)
log.info("initializing connection establisher")
connectionEstablisher = new ConnectionEstablisher(eventBus, i2pConnector, props, connectionManager, hostCache)
@@ -238,6 +275,7 @@ public class Core {
directoryWatcher = new DirectoryWatcher(eventBus, fileManager)
eventBus.register(FileSharedEvent.class, directoryWatcher)
eventBus.register(AllFilesLoadedEvent.class, directoryWatcher)
eventBus.register(DirectoryUnsharedEvent.class, directoryWatcher)
log.info("initializing hasher service")
hasherService = new HasherService(new FileHasher(), eventBus, fileManager)
@@ -272,8 +310,25 @@ public class Core {
directoryWatcher.stop()
log.info("shutting down connection manager")
connectionManager.shutdown()
if (router != null) {
log.info("shutting down embedded router")
router.shutdown(0)
}
}
static class RouterContextMetaClass extends DelegatingMetaClass {
private final Object logManager = new MuWireLogManager()
RouterContextMetaClass() {
super(RouterContext.class)
}
Object invokeMethod(Object object, String name, Object[] args) {
if (name == "logManager")
return logManager
super.invokeMethod(object, name, args)
}
}
static main(args) {
def home = System.getProperty("user.home") + File.separator + ".MuWire"
home = new File(home)
@@ -298,7 +353,7 @@ public class Core {
}
}
Core core = new Core(props, home, "0.4.0")
Core core = new Core(props, home, "0.4.5")
core.startServices()
// ... at the end, sleep or execute script

View File

@@ -13,6 +13,8 @@ class MuWireSettings {
boolean allowUntrusted
int downloadRetryInterval
int updateCheckInterval
boolean autoDownloadUpdate
String updateType
String nickname
File downloadLocation
CrawlerResponse crawlerResponse
@@ -21,6 +23,8 @@ class MuWireSettings {
float downloadSequentialRatio
int hostClearInterval
int meshExpiration
boolean embeddedRouter
int inBw, outBw
MuWireSettings() {
this(new Properties())
@@ -35,10 +39,15 @@ class MuWireSettings {
System.getProperty("user.home")))
downloadRetryInterval = Integer.parseInt(props.getProperty("downloadRetryInterval","1"))
updateCheckInterval = Integer.parseInt(props.getProperty("updateCheckInterval","24"))
autoDownloadUpdate = Boolean.parseBoolean(props.getProperty("autoDownloadUpdate","true"))
updateType = props.getProperty("updateType","jar")
shareDownloadedFiles = Boolean.parseBoolean(props.getProperty("shareDownloadedFiles","true"))
downloadSequentialRatio = Float.valueOf(props.getProperty("downloadSequentialRatio","0.8"))
hostClearInterval = Integer.valueOf(props.getProperty("hostClearInterval","60"))
meshExpiration = Integer.valueOf(props.getProperty("meshExpiration","60"))
embeddedRouter = Boolean.valueOf(props.getProperty("embeddedRouter","false"))
inBw = Integer.valueOf(props.getProperty("inBw","256"))
outBw = Integer.valueOf(props.getProperty("outBw","128"))
watchedDirectories = new HashSet<>()
if (props.containsKey("watchedDirectories")) {
@@ -57,10 +66,15 @@ class MuWireSettings {
props.setProperty("downloadLocation", downloadLocation.getAbsolutePath())
props.setProperty("downloadRetryInterval", String.valueOf(downloadRetryInterval))
props.setProperty("updateCheckInterval", String.valueOf(updateCheckInterval))
props.setProperty("autoDownloadUpdate", String.valueOf(autoDownloadUpdate))
props.setProperty("updateType",String.valueOf(updateType))
props.setProperty("shareDownloadedFiles", String.valueOf(shareDownloadedFiles))
props.setProperty("downloadSequentialRatio", String.valueOf(downloadSequentialRatio))
props.setProperty("hostClearInterval", String.valueOf(hostClearInterval))
props.setProperty("meshExpiration", String.valueOf(meshExpiration))
props.setProperty("embeddedRouter", String.valueOf(embeddedRouter))
props.setProperty("inBw", String.valueOf(inBw))
props.setProperty("outBw", String.valueOf(outBw))
if (!watchedDirectories.isEmpty()) {
String encoded = watchedDirectories.stream().

View File

@@ -82,4 +82,13 @@ public class Persona {
Persona other = (Persona)o
name.equals(other.name) && destination.equals(other.destination)
}
public static void main(String []args) {
if (args.length != 1) {
println "This utility decodes a bas64-encoded persona"
System.exit(1)
}
Persona p = new Persona(new ByteArrayInputStream(Base64.decode(args[0])))
println p.getHumanReadableName()
}
}

View File

@@ -21,6 +21,9 @@ import net.i2p.data.Destination
@Log
abstract class Connection implements Closeable {
private static final int SEARCHES = 10
private static final long INTERVAL = 1000
final EventBus eventBus
final Endpoint endpoint
@@ -32,6 +35,7 @@ abstract class Connection implements Closeable {
private final AtomicBoolean running = new AtomicBoolean()
private final BlockingQueue messages = new LinkedBlockingQueue()
private final Thread reader, writer
private final LinkedList<Long> searchTimestamps = new LinkedList<>()
protected final String name
@@ -156,7 +160,25 @@ abstract class Connection implements Closeable {
}
}
private boolean throttleSearch() {
final long now = System.currentTimeMillis()
if (searchTimestamps.size() < SEARCHES) {
searchTimestamps.addLast(now)
return false
}
Long oldest = searchTimestamps.getFirst()
if (now - oldest.longValue() < INTERVAL)
return true
searchTimestamps.addLast(now)
searchTimestamps.removeFirst()
false
}
protected void handleSearch(def search) {
if (throttleSearch()) {
log.info("dropping excessive search")
return
}
UUID uuid = UUID.fromString(search.uuid)
byte [] infohash = null
if (search.infohash != null) {

View File

@@ -25,8 +25,6 @@ import java.util.logging.Level
@Log
class DownloadSession {
private static int SAMPLES = 10
private final EventBus eventBus
private final String meB64
private final Pieces pieces
@@ -37,9 +35,9 @@ class DownloadSession {
private final long fileLength
private final Set<Integer> available
private final MessageDigest digest
private final LinkedList<Long> timestamps = new LinkedList<>()
private final LinkedList<Integer> reads = new LinkedList<>()
private long lastSpeedRead = System.currentTimeMillis()
private long dataSinceLastRead
private ByteBuffer mapped
@@ -186,18 +184,13 @@ class DownloadSession {
throw new IOException()
synchronized(this) {
mapped.put(tmp, 0, read)
if (timestamps.size() == SAMPLES) {
timestamps.removeFirst()
reads.removeFirst()
}
timestamps.addLast(System.currentTimeMillis())
reads.addLast(read)
dataSinceLastRead += read
}
}
mapped.clear()
digest.update(mapped)
DataUtil.tryUnmap(mapped)
byte [] hash = digest.digest()
byte [] expected = new byte[32]
System.arraycopy(infoHash.getHashList(), piece * 32, expected, 0, 32)
@@ -222,24 +215,11 @@ class DownloadSession {
}
synchronized int speed() {
if (timestamps.size() < SAMPLES)
return 0
int totalRead = 0
int idx = 0
final long now = System.currentTimeMillis()
while(idx < SAMPLES && timestamps.get(idx) < now - 1000)
idx++
if (idx == SAMPLES)
return 0
if (idx == SAMPLES - 1)
return reads[idx]
long interval = timestamps.last - timestamps[idx]
if (interval == 0)
interval = 1
for (int i = idx; i < SAMPLES; i++)
totalRead += reads[idx]
(int)(totalRead * 1000.0 / interval)
long interval = Math.max(1000, now - lastSpeedRead)
lastSpeedRead = now;
int rv = (int) (dataSinceLastRead * 1000.0 / interval)
dataSinceLastRead = 0
rv
}
}

View File

@@ -7,6 +7,7 @@ import com.muwire.core.connection.Endpoint
import java.nio.file.AtomicMoveNotSupportedException
import java.nio.file.Files
import java.nio.file.StandardCopyOption
import java.time.Instant
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
@@ -58,6 +59,11 @@ public class Downloader {
private final AtomicBoolean eventFired = new AtomicBoolean()
private boolean piecesFileClosed
private ArrayList speedArr = new ArrayList<Integer>()
private int speedPos = 0
private int speedAvg = 0
private long timestamp = Instant.now().toEpochMilli()
public Downloader(EventBus eventBus, DownloadManager downloadManager,
Persona me, File file, long length, InfoHash infoHash,
int pieceSizePow2, I2PConnector connector, Set<Destination> destinations,
@@ -76,6 +82,10 @@ public class Downloader {
this.pieceSize = 1 << pieceSizePow2
this.pieces = pieces
this.nPieces = pieces.nPieces
// default size suitable for an average of 5 seconds / 5 elements / 5 interval units
// it's easily adjustable by resizing the size of speedArr
this.speedArr = [ 0, 0, 0, 0, 0 ]
}
public synchronized InfoHash getInfoHash() {
@@ -124,14 +134,35 @@ public class Downloader {
public int speed() {
int total = 0
int currSpeed = 0
if (getCurrentState() == DownloadState.DOWNLOADING) {
activeWorkers.values().each {
if (it.currentState == WorkerState.DOWNLOADING)
total += it.speed()
currSpeed += it.speed()
}
}
total
// normalize to speedArr.size
currSpeed /= speedArr.size()
// compute new speedAvg and update speedArr
if ( speedArr[speedPos] > speedAvg ) {
speedAvg = 0
} else {
speedAvg -= speedArr[speedPos]
}
speedAvg += currSpeed
speedArr[speedPos] = currSpeed
// this might be necessary due to rounding errors
if (speedAvg < 0)
speedAvg = 0
// rolling index over the speedArr
speedPos++
if (speedPos >= speedArr.size())
speedPos=0
speedAvg
}
public DownloadState getCurrentState() {

View File

@@ -0,0 +1,7 @@
package com.muwire.core.files
import com.muwire.core.Event
class DirectoryUnsharedEvent extends Event {
File directory
}

View File

@@ -35,6 +35,7 @@ class DirectoryWatcher {
private final FileManager fileManager
private final Thread watcherThread, publisherThread
private final Map<File, Long> waitingFiles = new ConcurrentHashMap<>()
private final Map<File, WatchKey> watchedDirectories = new ConcurrentHashMap<>()
private WatchService watchService
private volatile boolean shutdown
@@ -64,9 +65,15 @@ class DirectoryWatcher {
if (!e.file.isDirectory())
return
Path path = e.file.getCanonicalFile().toPath()
path.register(watchService, kinds)
WatchKey wk = path.register(watchService, kinds)
watchedDirectories.put(e.file, wk)
}
void onDirectoryUnsharedEvent(DirectoryUnsharedEvent e) {
WatchKey wk = watchedDirectories.remove(e.directory)
wk?.cancel()
}
private void watch() {
try {
@@ -113,7 +120,7 @@ class DirectoryWatcher {
private static File join(Path parent, Path path) {
File parentFile = parent.toFile().getCanonicalFile()
new File(parentFile, path.toFile().getName())
new File(parentFile, path.toFile().getName()).getCanonicalFile()
}
private void publish() {

View File

@@ -1,6 +1,7 @@
package com.muwire.core.files
import com.muwire.core.InfoHash
import com.muwire.core.util.DataUtil
import net.i2p.data.Base64
@@ -18,6 +19,8 @@ class FileHasher {
/**
* @param size of the file to be shared
* @return the size of each piece in power of 2
* piece size is minimum 128 KBytees and maximum 16 MBytes in power of 2 steps (2^17 - 2^24)
* there can be up to 8192 pieces maximum per file
*/
static int getPieceSize(long size) {
if (size <= 0x1 << 30)
@@ -57,6 +60,7 @@ class FileHasher {
for (int i = 0; i < numPieces - 1; i++) {
buf = raf.getChannel().map(MapMode.READ_ONLY, ((long)size) * i, size)
digest.update buf
DataUtil.tryUnmap(buf)
output.write(digest.digest(), 0, 32)
}
def lastPieceLength = length - (numPieces - 1) * ((long)size)

View File

@@ -135,4 +135,16 @@ class FileManager {
}
rv
}
void onDirectoryUnsharedEvent(DirectoryUnsharedEvent e) {
e.directory.listFiles().each {
if (it.isDirectory())
eventBus.publish(new DirectoryUnsharedEvent(directory : it))
else {
SharedFile sf = fileToSharedFile.get(it)
if (sf != null)
eventBus.publish(new FileUnsharedEvent(unsharedFile : sf))
}
}
}
}

View File

@@ -24,7 +24,7 @@ class HasherService {
}
void onFileSharedEvent(FileSharedEvent evt) {
if (fileManager.fileToSharedFile.containsKey(evt.file))
if (fileManager.fileToSharedFile.containsKey(evt.file.getCanonicalFile()))
return
executor.execute( { -> process(evt.file) } as Runnable)
}

View File

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

View File

@@ -3,7 +3,15 @@ package com.muwire.core.update
import java.util.logging.Level
import com.muwire.core.EventBus
import com.muwire.core.InfoHash
import com.muwire.core.MuWireSettings
import com.muwire.core.Persona
import com.muwire.core.download.UIDownloadEvent
import com.muwire.core.files.FileDownloadedEvent
import com.muwire.core.files.FileManager
import com.muwire.core.search.QueryEvent
import com.muwire.core.search.SearchEvent
import com.muwire.core.search.UIResultBatchEvent
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
@@ -13,6 +21,7 @@ import net.i2p.client.I2PSessionMuxedListener
import net.i2p.client.SendMessageOptions
import net.i2p.client.datagram.I2PDatagramDissector
import net.i2p.client.datagram.I2PDatagramMaker
import net.i2p.data.Base64
import net.i2p.util.VersionComparator
@Log
@@ -21,16 +30,24 @@ class UpdateClient {
final I2PSession session
final String myVersion
final MuWireSettings settings
final FileManager fileManager
final Persona me
private final Timer timer
private long lastUpdateCheckTime
UpdateClient(EventBus eventBus, I2PSession session, String myVersion, MuWireSettings settings) {
private volatile InfoHash updateInfoHash
private volatile String version, signer
private volatile boolean updateDownloading
UpdateClient(EventBus eventBus, I2PSession session, String myVersion, MuWireSettings settings, FileManager fileManager, Persona me) {
this.eventBus = eventBus
this.session = session
this.myVersion = myVersion
this.settings = settings
this.fileManager = fileManager
this.me = me
timer = new Timer("update-client",true)
}
@@ -43,6 +60,24 @@ class UpdateClient {
timer.cancel()
}
void onUIResultBatchEvent(UIResultBatchEvent results) {
if (results.results[0].infohash != updateInfoHash)
return
if (updateDownloading)
return
updateDownloading = true
def file = new File(settings.downloadLocation, results.results[0].name)
def downloadEvent = new UIDownloadEvent(result: results.results[0], sources : results.results[0].sources, target : file)
eventBus.publish(downloadEvent)
}
void onFileDownloadedEvent(FileDownloadedEvent e) {
if (e.downloadedFile.infoHash != updateInfoHash)
return
updateDownloading = false
eventBus.publish(new UpdateDownloadedEvent(version : version, signer : signer))
}
private void checkUpdate() {
final long now = System.currentTimeMillis()
if (lastUpdateCheckTime > 0) {
@@ -106,8 +141,32 @@ class UpdateClient {
return
}
log.info("new version $payload.version available, publishing event")
eventBus.publish(new UpdateAvailableEvent(version : payload.version, signer : payload.signer, infoHash : payload.infoHash))
String infoHash
if (settings.updateType == "jar") {
infoHash = payload.infoHash
} else
infoHash = payload[settings.updateType]
if (!settings.autoDownloadUpdate) {
log.info("new version $payload.version available, publishing event")
eventBus.publish(new UpdateAvailableEvent(version : payload.version, signer : payload.signer, infoHash : infoHash))
} else {
log.info("new version $payload.version available")
updateInfoHash = new InfoHash(Base64.decode(infoHash))
if (fileManager.rootToFiles.containsKey(updateInfoHash))
eventBus.publish(new UpdateDownloadedEvent(version : payload.version, signer : payload.signer))
else {
updateDownloading = false
version = payload.version
signer = payload.signer
log.info("starting search for new version hash $payload.infoHash")
def searchEvent = new SearchEvent(searchHash : updateInfoHash.getRoot(), uuid : UUID.randomUUID(), oobInfohash : true)
def queryEvent = new QueryEvent(searchEvent : searchEvent, firstHop : true, replyTo : me.destination,
receivedOn : me.destination, originator : me)
eventBus.publish(queryEvent)
}
}
} catch (Exception e) {
log.log(Level.WARNING,"Invalid datagram",e)

View File

@@ -0,0 +1,8 @@
package com.muwire.core.update
import com.muwire.core.Event
class UpdateDownloadedEvent extends Event {
String version
String signer
}

View File

@@ -56,7 +56,7 @@ class ContentUploader extends Uploader {
writeMesh(request.downloader)
os.write("\r\n".getBytes(StandardCharsets.US_ASCII))
FileChannel channel
FileChannel channel = null
try {
channel = Files.newByteChannel(file.toPath(), EnumSet.of(StandardOpenOption.READ))
mapped = channel.map(FileChannel.MapMode.READ_ONLY, range.start, range.end - range.start + 1)
@@ -72,6 +72,10 @@ class ContentUploader extends Uploader {
} finally {
try {channel?.close() } catch (IOException ignored) {}
endpoint.getOutputStream().flush()
synchronized(this) {
DataUtil.tryUnmap(mapped)
mapped = null
}
}
}

View File

@@ -1,5 +1,8 @@
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 com.muwire.core.Constants
@@ -115,4 +118,39 @@ class DataUtil {
e = e.getCause()
e
}
public static void tryUnmap(ByteBuffer cb) {
if (cb==null || !cb.isDirect()) return;
// we could use this type cast and call functions without reflection code,
// but static import from sun.* package is risky for non-SUN virtual machine.
//try { ((sun.nio.ch.DirectBuffer)cb).cleaner().clean(); } catch (Exception ex) { }
// JavaSpecVer: 1.6, 1.7, 1.8, 9, 10
boolean isOldJDK = System.getProperty("java.specification.version","99").startsWith("1.");
try {
if (isOldJDK) {
Method cleaner = cb.getClass().getMethod("cleaner");
cleaner.setAccessible(true);
Method clean = Class.forName("sun.misc.Cleaner").getMethod("clean");
clean.setAccessible(true);
clean.invoke(cleaner.invoke(cb));
} else {
Class unsafeClass;
try {
unsafeClass = Class.forName("sun.misc.Unsafe");
} catch(Exception ex) {
// jdk.internal.misc.Unsafe doesn't yet have an invokeCleaner() method,
// but that method should be added if sun.misc.Unsafe is removed.
unsafeClass = Class.forName("jdk.internal.misc.Unsafe");
}
Method clean = unsafeClass.getMethod("invokeCleaner", ByteBuffer.class);
clean.setAccessible(true);
Field theUnsafeField = unsafeClass.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
Object theUnsafe = theUnsafeField.get(null);
clean.invoke(theUnsafe, cb);
}
} catch(Exception ex) { }
cb = null;
}
}

View File

@@ -1,6 +1,7 @@
package com.muwire.core;
import java.io.File;
import java.io.IOException;
import java.util.Set;
import net.i2p.data.Destination;
@@ -9,7 +10,8 @@ public class DownloadedFile extends SharedFile {
private final Set<Destination> sources;
public DownloadedFile(File file, InfoHash infoHash, int pieceSize, Set<Destination> sources) {
public DownloadedFile(File file, InfoHash infoHash, int pieceSize, Set<Destination> sources)
throws IOException {
super(file, infoHash, pieceSize);
this.sources = sources;
}

View File

@@ -1,6 +1,7 @@
package com.muwire.core;
import java.io.File;
import java.io.IOException;
public class SharedFile {
@@ -8,10 +9,15 @@ public class SharedFile {
private final InfoHash infoHash;
private final int pieceSize;
public SharedFile(File file, InfoHash infoHash, int pieceSize) {
private final String cachedPath;
private final long cachedLength;
public SharedFile(File file, InfoHash infoHash, int pieceSize) throws IOException {
this.file = file;
this.infoHash = infoHash;
this.pieceSize = pieceSize;
this.cachedPath = file.getAbsolutePath();
this.cachedLength = file.length();
}
public File getFile() {
@@ -35,6 +41,14 @@ public class SharedFile {
return rv;
}
public String getCachedPath() {
return cachedPath;
}
public long getCachedLength() {
return cachedLength;
}
@Override
public int hashCode() {
return file.hashCode() ^ infoHash.hashCode();

View File

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

View File

@@ -26,4 +26,14 @@ mvcGroups {
view = 'com.muwire.gui.OptionsView'
controller = 'com.muwire.gui.OptionsController'
}
"mu-wire-status" {
model = 'com.muwire.gui.MuWireStatusModel'
view = 'com.muwire.gui.MuWireStatusView'
controller = 'com.muwire.gui.MuWireStatusController'
}
'i-2-p-status' {
model = 'com.muwire.gui.I2PStatusModel'
view = 'com.muwire.gui.I2PStatusView'
controller = 'com.muwire.gui.I2PStatusController'
}
}

View File

@@ -0,0 +1,41 @@
package com.muwire.gui
import griffon.core.artifact.GriffonController
import griffon.core.controller.ControllerAction
import griffon.inject.MVCMember
import griffon.metadata.ArtifactProviderFor
import net.i2p.router.Router
import javax.annotation.Nonnull
import com.muwire.core.Core
@ArtifactProviderFor(GriffonController)
class I2PStatusController {
@MVCMember @Nonnull
I2PStatusModel model
@MVCMember @Nonnull
I2PStatusView view
@ControllerAction
void refresh() {
Core core = application.context.get("core")
Router router = core.router
model.networkStatus = router._context.commSystem().status.toStatusString()
model.floodfill = router._context.netDb().floodfillEnabled()
model.ntcpConnections = router._context.commSystem().getTransports()["NTCP"].countPeers()
model.ssuConnections = router._context.commSystem().getTransports()["SSU"].countPeers()
model.participatingTunnels = router._context.tunnelManager().getParticipatingCount()
model.activePeers = router._context.profileOrganizer().countActivePeers()
model.receiveBps = router._context.bandwidthLimiter().getReceiveBps15s()
model.sendBps = router._context.bandwidthLimiter().getSendBps15s()
model.participatingBW = router._context.bandwidthLimiter().getCurrentParticipatingBandwidth()
}
@ControllerAction
void close() {
view.dialog.setVisible(false)
mvcGroup.destroy()
}
}

View File

@@ -14,11 +14,14 @@ import javax.inject.Inject
import com.muwire.core.Constants
import com.muwire.core.Core
import com.muwire.core.SharedFile
import com.muwire.core.download.DownloadStartedEvent
import com.muwire.core.download.UIDownloadCancelledEvent
import com.muwire.core.download.UIDownloadEvent
import com.muwire.core.download.UIDownloadPausedEvent
import com.muwire.core.download.UIDownloadResumedEvent
import com.muwire.core.files.DirectoryUnsharedEvent
import com.muwire.core.files.FileUnsharedEvent
import com.muwire.core.search.QueryEvent
import com.muwire.core.search.SearchEvent
import com.muwire.core.trust.TrustEvent
@@ -32,6 +35,8 @@ class MainFrameController {
@MVCMember @Nonnull
MainFrameModel model
@MVCMember @Nonnull
MainFrameView view
private volatile Core core
@@ -205,8 +210,23 @@ class MainFrameController {
markTrust("trusted-table", TrustLevel.NEUTRAL, model.trusted)
}
void unshareSelectedFiles() {
println "unsharing selected files"
void unshareSelectedFile() {
SharedFile sf = view.selectedSharedFile()
if (sf == null)
return
core.eventBus.publish(new FileUnsharedEvent(unsharedFile : sf))
}
void stopWatchingDirectory() {
String directory = mvcGroup.view.getSelectedWatchedDirectory()
if (directory == null)
return
core.muOptions.watchedDirectories.remove(directory)
saveMuWireSettings()
core.eventBus.publish(new DirectoryUnsharedEvent(directory : new File(directory)))
model.watched.remove(directory)
builder.getVariable("watched-directories-table").model.fireTableDataChanged()
}
void saveMuWireSettings() {

View File

@@ -0,0 +1,45 @@
package com.muwire.gui
import griffon.core.artifact.GriffonController
import griffon.core.controller.ControllerAction
import griffon.inject.MVCMember
import griffon.metadata.ArtifactProviderFor
import javax.annotation.Nonnull
import com.muwire.core.Core
@ArtifactProviderFor(GriffonController)
class MuWireStatusController {
@MVCMember @Nonnull
MuWireStatusModel model
@MVCMember @Nonnull
MuWireStatusView view
@ControllerAction
void refresh() {
Core core = application.context.get("core")
int incoming = 0
int outgoing = 0
core.connectionManager.getConnections().each {
if (it.incoming)
incoming++
else
outgoing++
}
model.incomingConnections = incoming
model.outgoingConnections = outgoing
model.knownHosts = core.hostCache.hosts.size()
model.sharedFiles = core.fileManager.fileToSharedFile.size()
model.downloads = core.downloadManager.downloaders.size()
}
@ControllerAction
void close() {
view.dialog.setVisible(false)
mvcGroup.destroy()
}
}

View File

@@ -4,10 +4,15 @@ import griffon.core.artifact.GriffonController
import griffon.core.controller.ControllerAction
import griffon.inject.MVCMember
import griffon.metadata.ArtifactProviderFor
import groovy.util.logging.Log
import java.util.logging.Level
import javax.annotation.Nonnull
import javax.swing.JFileChooser
import com.muwire.core.Core
import com.muwire.core.MuWireSettings
@ArtifactProviderFor(GriffonController)
class OptionsController {
@@ -20,6 +25,7 @@ class OptionsController {
void save() {
String text
Core core = application.context.get("core")
MuWireSettings settings = application.context.get("muwire-settings")
def i2pProps = core.i2pOptions
@@ -39,6 +45,17 @@ class OptionsController {
model.outboundLength = text
i2pProps["outbound.length"] = text
if (settings.embeddedRouter) {
text = view.i2pNTCPPortField.text
model.i2pNTCPPort = text
i2pProps["i2np.ntcp.port"] = text
text = view.i2pUDPPortField.text
model.i2pUDPPort = text
i2pProps["i2np.udp.port"] = text
}
File i2pSettingsFile = new File(core.home, "i2p.properties")
i2pSettingsFile.withOutputStream {
i2pProps.store(it,"")
@@ -47,13 +64,16 @@ class OptionsController {
text = view.retryField.text
model.downloadRetryInterval = text
def settings = application.context.get("muwire-settings")
settings.downloadRetryInterval = Integer.valueOf(text)
text = view.updateField.text
model.updateCheckInterval = text
settings.updateCheckInterval = Integer.valueOf(text)
boolean autoDownloadUpdate = view.autoDownloadUpdateCheckbox.model.isSelected()
model.autoDownloadUpdate = autoDownloadUpdate
settings.autoDownloadUpdate = autoDownloadUpdate
boolean onlyTrusted = view.allowUntrustedCheckbox.model.isSelected()
model.onlyTrusted = onlyTrusted
settings.setAllowUntrusted(!onlyTrusted)
@@ -64,7 +84,16 @@ class OptionsController {
String downloadLocation = model.downloadLocation
settings.downloadLocation = new File(downloadLocation)
if (settings.embeddedRouter) {
text = view.inBwField.text
model.inBw = text
settings.inBw = Integer.valueOf(text)
text = view.outBwField.text
model.outBw = text
settings.outBw = Integer.valueOf(text)
}
File settingsFile = new File(core.home, "MuWire.properties")
settingsFile.withOutputStream {
settings.write(it)

View File

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

View File

@@ -0,0 +1,28 @@
package com.muwire.gui
import javax.annotation.Nonnull
import griffon.core.artifact.GriffonModel
import griffon.inject.MVCMember
import griffon.transform.Observable
import griffon.metadata.ArtifactProviderFor
@ArtifactProviderFor(GriffonModel)
class I2PStatusModel {
@MVCMember @Nonnull
I2PStatusController controller
@Observable int ntcpConnections
@Observable int ssuConnections
@Observable String networkStatus
@Observable boolean floodfill
@Observable int participatingTunnels
@Observable int activePeers
@Observable int receiveBps
@Observable int sendBps
@Observable int participatingBW
void mvcGroupInit(Map<String,String> args) {
controller.refresh()
}
}

View File

@@ -29,6 +29,7 @@ import com.muwire.core.search.UIResultEvent
import com.muwire.core.trust.TrustEvent
import com.muwire.core.trust.TrustService
import com.muwire.core.update.UpdateAvailableEvent
import com.muwire.core.update.UpdateDownloadedEvent
import com.muwire.core.upload.UploadEvent
import com.muwire.core.upload.UploadFinishedEvent
@@ -52,6 +53,7 @@ class MainFrameModel {
MainFrameController controller
@Inject @Nonnull GriffonApplication application
@Observable boolean coreInitialized = false
@Observable boolean routerPresent
def results = new ConcurrentHashMap<>()
def downloads = []
@@ -76,7 +78,7 @@ class MainFrameModel {
private final Set<InfoHash> downloadInfoHashes = new HashSet<>()
volatile Core core
@Observable volatile Core core
private long lastRetryTime = System.currentTimeMillis()
@@ -124,6 +126,7 @@ class MainFrameModel {
application.addPropertyChangeListener("core", {e ->
coreInitialized = (e.getNewValue() != null)
core = e.getNewValue()
routerPresent = core.router != null
me = core.me.getHumanReadableName()
core.eventBus.register(UIResultEvent.class, this)
core.eventBus.register(UIResultBatchEvent.class, this)
@@ -141,6 +144,7 @@ class MainFrameModel {
core.eventBus.register(FileUnsharedEvent.class, this)
core.eventBus.register(RouterDisconnectedEvent.class, this)
core.eventBus.register(AllFilesLoadedEvent.class, this)
core.eventBus.register(UpdateDownloadedEvent.class, this)
timer.schedule({
if (core.shutdown.get())
@@ -183,6 +187,14 @@ class MainFrameModel {
watched.each { core.eventBus.publish(new FileSharedEvent(file : new File(it))) }
}
}
void onUpdateDownloadedEvent(UpdateDownloadedEvent e) {
runInsideUIAsync {
JOptionPane.showMessageDialog(null, "MuWire $e.version has been downloaded. You can update now",
"Update Downloaded", JOptionPane.INFORMATION_MESSAGE)
}
}
void onUIResultEvent(UIResultEvent e) {
MVCGroup resultsGroup = results.get(e.uuid)
resultsGroup?.model.handleResult(e)
@@ -190,7 +202,7 @@ class MainFrameModel {
void onUIResultBatchEvent(UIResultBatchEvent e) {
MVCGroup resultsGroup = results.get(e.uuid)
resultsGroup?.model.handleResultBatch(e.results)
resultsGroup?.model?.handleResultBatch(e.results)
}
void onDownloadStartedEvent(DownloadStartedEvent e) {

View File

@@ -0,0 +1,25 @@
package com.muwire.gui
import javax.annotation.Nonnull
import griffon.core.artifact.GriffonModel
import griffon.inject.MVCMember
import griffon.transform.Observable
import griffon.metadata.ArtifactProviderFor
@ArtifactProviderFor(GriffonModel)
class MuWireStatusModel {
@MVCMember @Nonnull
MuWireStatusController controller
@Observable int incomingConnections
@Observable int outgoingConnections
@Observable int knownHosts
@Observable int sharedFiles
@Observable int downloads
void mvcGroupInit(Map<String,String> args) {
controller.refresh()
}
}

View File

@@ -11,6 +11,7 @@ import griffon.metadata.ArtifactProviderFor
class OptionsModel {
@Observable String downloadRetryInterval
@Observable String updateCheckInterval
@Observable boolean autoDownloadUpdate
@Observable boolean onlyTrusted
@Observable boolean shareDownloadedFiles
@Observable String downloadLocation
@@ -20,6 +21,8 @@ class OptionsModel {
@Observable String inboundQuantity
@Observable String outboundLength
@Observable String outboundQuantity
@Observable String i2pUDPPort
@Observable String i2pNTCPPort
// gui options
@Observable boolean showMonitor
@@ -30,10 +33,15 @@ class OptionsModel {
@Observable boolean excludeLocalResult
@Observable boolean showSearchHashes
// bw options
@Observable String inBw
@Observable String outBw
void mvcGroupInit(Map<String, String> args) {
MuWireSettings settings = application.context.get("muwire-settings")
downloadRetryInterval = settings.downloadRetryInterval
updateCheckInterval = settings.updateCheckInterval
autoDownloadUpdate = settings.autoDownloadUpdate
onlyTrusted = !settings.allowUntrusted()
shareDownloadedFiles = settings.shareDownloadedFiles
downloadLocation = settings.downloadLocation.getAbsolutePath()
@@ -43,6 +51,8 @@ class OptionsModel {
inboundQuantity = core.i2pOptions["inbound.quantity"]
outboundLength = core.i2pOptions["outbound.length"]
outboundQuantity = core.i2pOptions["outbound.quantity"]
i2pUDPPort = core.i2pOptions["i2np.udp.port"]
i2pNTCPPort = core.i2pOptions["i2np.ntcp.port"]
UISettings uiSettings = application.context.get("ui-settings")
showMonitor = uiSettings.showMonitor
@@ -52,5 +62,10 @@ class OptionsModel {
clearFinishedDownloads = uiSettings.clearFinishedDownloads
excludeLocalResult = uiSettings.excludeLocalResult
showSearchHashes = uiSettings.showSearchHashes
if (core.router != null) {
inBw = String.valueOf(settings.inBw)
outBw = String.valueOf(settings.outBw)
}
}
}

View File

@@ -0,0 +1,80 @@
package com.muwire.gui
import griffon.core.artifact.GriffonView
import griffon.inject.MVCMember
import griffon.metadata.ArtifactProviderFor
import javax.swing.JDialog
import javax.swing.JPanel
import javax.swing.SwingConstants
import java.awt.BorderLayout
import java.awt.event.WindowAdapter
import java.awt.event.WindowEvent
import javax.annotation.Nonnull
@ArtifactProviderFor(GriffonView)
class I2PStatusView {
@MVCMember @Nonnull
FactoryBuilderSupport builder
@MVCMember @Nonnull
I2PStatusModel model
def mainFrame
def dialog
def panel
def buttonsPanel
void initUI() {
mainFrame = application.windowManager.findWindow("main-frame")
dialog = new JDialog(mainFrame, "I2P Status", true)
panel = builder.panel {
gridBagLayout()
label(text : "Network status", constraints : gbc(gridx:0, gridy:0))
label(text : bind {model.networkStatus}, constraints : gbc(gridx: 1, gridy:0))
label(text: "Floodfill", constraints : gbc(gridx: 0, gridy : 1))
label(text : bind {model.floodfill}, constraints : gbc(gridx:1, gridy:1))
label(text : "NTCP Connections", constraints : gbc(gridx:0, gridy:2))
label(text : bind {model.ntcpConnections}, constraints : gbc(gridx: 1, gridy:2))
label(text : "SSU Connections", constraints : gbc(gridx:0, gridy:3))
label(text : bind {model.ssuConnections}, constraints : gbc(gridx: 1, gridy:3))
label(text : "Participating Tunnels", constraints : gbc(gridx:0, gridy:4))
label(text : bind {model.participatingTunnels}, constraints : gbc(gridx: 1, gridy:4))
label(text : "Participating Bandwidth", constraints : gbc(gridx:0, gridy:5))
label(text : bind {model.participatingBW}, constraints : gbc(gridx: 1, gridy:6))
label(text : "Active Peers", constraints : gbc(gridx:0, gridy:6))
label(text : bind {model.activePeers}, constraints : gbc(gridx: 1, gridy:6))
label(text : "Receive Bps (15 seconds)", constraints : gbc(gridx:0, gridy:7))
label(text : bind {model.receiveBps}, constraints : gbc(gridx: 1, gridy:7))
label(text : "Send Bps (15 seconds)", constraints : gbc(gridx:0, gridy:8))
label(text : bind {model.sendBps}, constraints : gbc(gridx: 1, gridy:8))
}
buttonsPanel = builder.panel {
gridBagLayout()
button(text : "Refresh", constraints: gbc(gridx: 0, gridy: 0), refreshAction)
button(text : "Close", constraints : gbc(gridx : 1, gridy :0), closeAction)
}
}
void mvcGroupInit(Map<String,String> args) {
JPanel statusPanel = new JPanel()
statusPanel.setLayout(new BorderLayout())
statusPanel.add(panel, BorderLayout.CENTER)
statusPanel.add(buttonsPanel, BorderLayout.SOUTH)
dialog.getContentPane().add(statusPanel)
dialog.pack()
dialog.setLocationRelativeTo(mainFrame)
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
dialog.addWindowListener(new WindowAdapter() {
public void windowClosed(WindowEvent e) {
mvcGroup.destroy()
}
})
dialog.show()
}
}

View File

@@ -22,6 +22,7 @@ import javax.swing.border.Border
import javax.swing.table.DefaultTableCellRenderer
import com.muwire.core.Constants
import com.muwire.core.MuWireSettings
import com.muwire.core.download.Downloader
import com.muwire.core.files.FileSharedEvent
@@ -52,6 +53,7 @@ class MainFrameView {
def downloadsTable
def lastDownloadSortEvent
def lastSharedSortEvent
def lastWatchedSortEvent
void initUI() {
UISettings settings = application.context.get("ui-settings")
@@ -70,6 +72,11 @@ class MainFrameView {
menu (text : "Options") {
menuItem("Configuration", actionPerformed : {mvcGroup.createMVCGroup("Options")})
}
menu (text : "Status") {
menuItem("MuWire", actionPerformed : {mvcGroup.createMVCGroup("mu-wire-status")})
MuWireSettings muSettings = application.context.get("muwire-settings")
menuItem("I2P", enabled : bind {model.routerPresent}, actionPerformed: {mvcGroup.createMVCGroup("i-2-p-status")})
}
}
borderLayout()
panel (border: etchedBorder(), constraints : BorderLayout.NORTH) {
@@ -167,8 +174,8 @@ class MainFrameView {
scrollPane(constraints : BorderLayout.CENTER) {
table(id : "shared-files-table", autoCreateRowSorter: true) {
tableModel(list : model.shared) {
closureColumn(header : "Name", preferredWidth : 500, type : String, read : {row -> row.file.getAbsolutePath()})
closureColumn(header : "Size", preferredWidth : 100, type : Long, read : {row -> row.file.length() })
closureColumn(header : "Name", preferredWidth : 500, type : String, read : {row -> row.getCachedPath()})
closureColumn(header : "Size", preferredWidth : 100, type : Long, read : {row -> row.getCachedLength() })
}
}
}
@@ -360,11 +367,12 @@ class MainFrameView {
sharedFilesTable.rowSorter.setSortsOnUpdates(true)
JPopupMenu sharedFilesMenu = new JPopupMenu()
// JMenuItem unshareSelectedFiles = new JMenuItem("Unshare selected files")
// unshareSelectedFiles.addActionListener({mvcGroup.controller.unshareSelectedFiles()})
JMenuItem copyHashToClipboard = new JMenuItem("Copy hash to clipboard")
copyHashToClipboard.addActionListener({mvcGroup.view.copyHashToClipboard(sharedFilesTable)})
copyHashToClipboard.addActionListener({mvcGroup.view.copyHashToClipboard()})
sharedFilesMenu.add(copyHashToClipboard)
JMenuItem unshareSelectedFiles = new JMenuItem("Unshare selected files")
unshareSelectedFiles.addActionListener({mvcGroup.controller.unshareSelectedFile()})
sharedFilesMenu.add(unshareSelectedFiles)
sharedFilesTable.addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
@@ -397,19 +405,48 @@ class MainFrameView {
}
})
// watched directories table
def watchedTable = builder.getVariable("watched-directories-table")
watchedTable.rowSorter.addRowSorterListener({evt -> lastWatchedSortEvent = evt})
watchedTable.rowSorter.setSortsOnUpdates(true)
JPopupMenu watchedMenu = new JPopupMenu()
JMenuItem stopWatching = new JMenuItem("Stop sharing")
stopWatching.addActionListener({mvcGroup.controller.stopWatchingDirectory()})
watchedMenu.add(stopWatching)
watchedTable.addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger())
showPopupMenu(watchedMenu, e)
}
@Override
public void mousePressed(MouseEvent e) {
if (e.isPopupTrigger())
showPopupMenu(watchedMenu, e)
}
})
}
private static void showPopupMenu(JPopupMenu menu, MouseEvent event) {
menu.show(event.getComponent(), event.getX(), event.getY())
}
def copyHashToClipboard(JTable sharedFilesTable) {
def selectedSharedFile() {
def sharedFilesTable = builder.getVariable("shared-files-table")
int selected = sharedFilesTable.getSelectedRow()
if (selected < 0)
return
if (lastSharedSortEvent != null)
return null
if (lastSharedSortEvent != null)
selected = sharedFilesTable.rowSorter.convertRowIndexToModel(selected)
String root = Base64.encode(model.shared[selected].infoHash.getRoot())
model.shared[selected]
}
def copyHashToClipboard() {
def selected = selectedSharedFile()
if (selected == null)
return
String root = Base64.encode(selected.infoHash.getRoot())
StringSelection selection = new StringSelection(root)
def clipboard = Toolkit.getDefaultToolkit().getSystemClipboard()
clipboard.setContents(selection, null)
@@ -543,4 +580,14 @@ class MainFrameView {
model.core.eventBus.publish(new FileSharedEvent(file : f))
}
}
String getSelectedWatchedDirectory() {
def watchedTable = builder.getVariable("watched-directories-table")
int selectedRow = watchedTable.getSelectedRow()
if (selectedRow < 0)
return null
if (lastWatchedSortEvent != null)
selectedRow = watchedTable.rowSorter.convertRowIndexToModel(selectedRow)
model.watched[selectedRow]
}
}

View File

@@ -0,0 +1,73 @@
package com.muwire.gui
import griffon.core.artifact.GriffonView
import griffon.inject.MVCMember
import griffon.metadata.ArtifactProviderFor
import javax.swing.JDialog
import javax.swing.JPanel
import javax.swing.SwingConstants
import com.muwire.core.Core
import java.awt.BorderLayout
import java.awt.event.WindowAdapter
import java.awt.event.WindowEvent
import javax.annotation.Nonnull
@ArtifactProviderFor(GriffonView)
class MuWireStatusView {
@MVCMember @Nonnull
FactoryBuilderSupport builder
@MVCMember @Nonnull
MuWireStatusModel model
def mainFrame
def dialog
def panel
def buttonsPanel
void initUI() {
mainFrame = application.windowManager.findWindow("main-frame")
dialog = new JDialog(mainFrame, "MuWire Status", true)
panel = builder.panel {
gridBagLayout()
label(text : "Incoming connections", constraints : gbc(gridx:0, gridy:0))
label(text : bind {model.incomingConnections}, constraints : gbc(gridx:1, gridy:0))
label(text : "Outgoing connections", constraints : gbc(gridx:0, gridy:1))
label(text : bind {model.outgoingConnections}, constraints : gbc(gridx:1, gridy:1))
label(text : "Known hosts", constraints : gbc(gridx:0, gridy:2))
label(text : bind {model.knownHosts}, constraints : gbc(gridx:1, gridy:2))
label(text : "Shared files", constraints : gbc(gridx:0, gridy:3))
label(text : bind {model.sharedFiles}, constraints : gbc(gridx:1, gridy:3))
label(text : "Downloads", constraints : gbc(gridx:0, gridy:4))
label(text : bind {model.downloads}, constraints : gbc(gridx:1, gridy:4))
}
buttonsPanel = builder.panel {
gridBagLayout()
button(text : "Refresh", constraints: gbc(gridx: 0, gridy: 0), refreshAction)
button(text : "Close", constraints : gbc(gridx : 1, gridy :0), closeAction)
}
}
void mvcGroupInit(Map<String,String> args) {
JPanel statusPanel = new JPanel()
statusPanel.setLayout(new BorderLayout())
statusPanel.add(panel, BorderLayout.CENTER)
statusPanel.add(buttonsPanel, BorderLayout.SOUTH)
dialog.getContentPane().add(statusPanel)
dialog.pack()
dialog.setLocationRelativeTo(mainFrame)
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
dialog.addWindowListener(new WindowAdapter() {
public void windowClosed(WindowEvent e) {
mvcGroup.destroy()
}
})
dialog.show()
}
}

View File

@@ -9,6 +9,8 @@ import javax.swing.JPanel
import javax.swing.JTabbedPane
import javax.swing.SwingConstants
import com.muwire.core.Core
import java.awt.BorderLayout
import java.awt.event.WindowAdapter
import java.awt.event.WindowEvent
@@ -26,9 +28,11 @@ class OptionsView {
def p
def i
def u
def bandwidth
def retryField
def updateField
def autoDownloadUpdateCheckbox
def allowUntrustedCheckbox
def shareDownloadedCheckbox
@@ -36,6 +40,8 @@ class OptionsView {
def inboundQuantityField
def outboundLengthField
def outboundQuantityField
def i2pUDPPortField
def i2pNTCPPortField
def lnfField
def monitorCheckbox
@@ -45,6 +51,10 @@ class OptionsView {
def excludeLocalResultCheckbox
def showSearchHashesCheckbox
def inBwField
def outBwField
def buttonsPanel
def mainFrame
@@ -62,16 +72,19 @@ class OptionsView {
label(text : "Check for updates every", constraints : gbc(gridx : 0, gridy: 1))
updateField = textField(text : bind {model.updateCheckInterval }, columns : 2, constraints : gbc(gridx : 1, gridy: 1))
label(text : "hours", constraints : gbc(gridx: 2, gridy : 1))
label(text : "Download updates automatically", constraints: gbc(gridx :0, gridy : 2))
autoDownloadUpdateCheckbox = checkBox(selected : bind {model.autoDownloadUpdate}, constraints : gbc(gridx:1, gridy : 2))
label(text : "Allow only trusted connections", constraints : gbc(gridx: 0, gridy : 2))
allowUntrustedCheckbox = checkBox(selected : bind {model.onlyTrusted}, constraints : gbc(gridx: 1, gridy : 2))
label(text : "Allow only trusted connections", constraints : gbc(gridx: 0, gridy : 3))
allowUntrustedCheckbox = checkBox(selected : bind {model.onlyTrusted}, constraints : gbc(gridx: 1, gridy : 3))
label(text : "Share downloaded files", constraints : gbc(gridx : 0, gridy:3))
shareDownloadedCheckbox = checkBox(selected : bind {model.shareDownloadedFiles}, constraints : gbc(gridx :1, gridy:3))
label(text : "Share downloaded files", constraints : gbc(gridx : 0, gridy:4))
shareDownloadedCheckbox = checkBox(selected : bind {model.shareDownloadedFiles}, constraints : gbc(gridx :1, gridy:4))
label(text : "Save downloaded files to:", constraints: gbc(gridx:0, gridy:4))
button(text : "Choose", constraints : gbc(gridx : 1, gridy:4), downloadLocationAction)
label(text : bind {model.downloadLocation}, constraints: gbc(gridx:0, gridy:5, gridwidth:2))
label(text : "Save downloaded files to:", constraints: gbc(gridx:0, gridy:5))
button(text : "Choose", constraints : gbc(gridx : 1, gridy:5), downloadLocationAction)
label(text : bind {model.downloadLocation}, constraints: gbc(gridx:0, gridy:6, gridwidth:2))
}
i = builder.panel {
@@ -85,6 +98,15 @@ class OptionsView {
outboundLengthField = textField(text : bind {model.outboundLength}, columns : 2, constraints : gbc(gridx:1, gridy:3))
label(text : "Outbound Quantity", constraints : gbc(gridx:0, gridy:4))
outboundQuantityField = textField(text : bind {model.outboundQuantity}, columns : 2, constraints : gbc(gridx:1, gridy:4))
Core core = application.context.get("core")
if (core.router != null) {
label(text : "TCP Port", constraints : gbc(gridx :0, gridy: 5))
i2pNTCPPortField = textField(text : bind {model.i2pNTCPPort}, columns : 4, constraints : gbc(gridx:1, gridy:5))
label(text : "UDP Port", constraints : gbc(gridx :0, gridy: 6))
i2pUDPPortField = textField(text : bind {model.i2pUDPPort}, columns : 4, constraints : gbc(gridx:1, gridy:6))
}
}
u = builder.panel {
gridBagLayout()
@@ -104,6 +126,16 @@ class OptionsView {
// label(text : "Show Hash Searches In Monitor", constraints: gbc(gridx:0, gridy:7))
// showSearchHashesCheckbox = checkBox(selected : bind {model.showSearchHashes}, constraints : gbc(gridx: 1, gridy: 7))
}
bandwidth = builder.panel {
gridBagLayout()
label(text : "Changing these settings requires a restart", constraints : gbc(gridx : 0, gridy : 0, gridwidth: 2))
label(text : "Inbound bandwidth (KB)", constraints : gbc(gridx: 0, gridy : 1))
inBwField = textField(text : bind {model.inBw}, columns : 3, constraints : gbc(gridx : 1, gridy : 1))
label(text : "Outbound bandwidth (KB)", constraints : gbc(gridx: 0, gridy : 2))
outBwField = textField(text : bind {model.outBw}, columns : 3, constraints : gbc(gridx : 1, gridy : 2))
}
buttonsPanel = builder.panel {
gridBagLayout()
button(text : "Save", constraints : gbc(gridx : 1, gridy: 2), saveAction)
@@ -116,6 +148,10 @@ class OptionsView {
tabbedPane.addTab("MuWire", p)
tabbedPane.addTab("I2P", i)
tabbedPane.addTab("GUI", u)
Core core = application.context.get("core")
if (core.router != null) {
tabbedPane.addTab("Bandwidth", bandwidth)
}
JPanel panel = new JPanel()
panel.setLayout(new BorderLayout())

View File

@@ -0,0 +1,25 @@
package com.muwire.gui
import griffon.core.test.GriffonFestRule
import org.fest.swing.fixture.FrameFixture
import org.junit.Rule
import org.junit.Test
import static org.junit.Assert.fail
class I2PStatusIntegrationTest {
static {
System.setProperty('griffon.swing.edt.violations.check', 'true')
System.setProperty('griffon.swing.edt.hang.monitor', 'true')
}
@Rule
public final GriffonFestRule fest = new GriffonFestRule()
private FrameFixture window
@Test
void smokeTest() {
fail('Not implemented yet!')
}
}

View File

@@ -0,0 +1,25 @@
package com.muwire.gui
import griffon.core.test.GriffonFestRule
import org.fest.swing.fixture.FrameFixture
import org.junit.Rule
import org.junit.Test
import static org.junit.Assert.fail
class MuWireStatusIntegrationTest {
static {
System.setProperty('griffon.swing.edt.violations.check', 'true')
System.setProperty('griffon.swing.edt.hang.monitor', 'true')
}
@Rule
public final GriffonFestRule fest = new GriffonFestRule()
private FrameFixture window
@Test
void smokeTest() {
fail('Not implemented yet!')
}
}

View File

@@ -0,0 +1,21 @@
package com.muwire.gui
import griffon.core.test.GriffonUnitRule
import griffon.core.test.TestFor
import org.junit.Rule
import org.junit.Test
import static org.junit.Assert.fail
@TestFor(I2PStatusController)
class I2PStatusControllerTest {
private I2PStatusController controller
@Rule
public final GriffonUnitRule griffon = new GriffonUnitRule()
@Test
void smokeTest() {
fail('Not yet implemented!')
}
}

View File

@@ -0,0 +1,21 @@
package com.muwire.gui
import griffon.core.test.GriffonUnitRule
import griffon.core.test.TestFor
import org.junit.Rule
import org.junit.Test
import static org.junit.Assert.fail
@TestFor(MuWireStatusController)
class MuWireStatusControllerTest {
private MuWireStatusController controller
@Rule
public final GriffonUnitRule griffon = new GriffonUnitRule()
@Test
void smokeTest() {
fail('Not yet implemented!')
}
}

View File

@@ -11,6 +11,7 @@ import net.i2p.client.I2PSession
import net.i2p.client.I2PSessionMuxedListener
import net.i2p.client.datagram.I2PDatagramDissector
import net.i2p.client.datagram.I2PDatagramMaker
import net.i2p.crypto.SigType
import net.i2p.util.SystemVersion
import net.i2p.data.*
@@ -43,7 +44,7 @@ public class HostCache {
def session
if (!keyfile.exists()) {
def os = new FileOutputStream(keyfile);
myDest = i2pClient.createDestination(os)
myDest = i2pClient.createDestination(os, SigType.EdDSA_SHA512_Ed25519)
os.close()
println "No key.dat file was found, so creating a new destination."
println "This is the destination you want to give out for your new HostCache"