Compare commits
37 Commits
fix-piece-
...
muwire-0.4
Author | SHA1 | Date | |
---|---|---|---|
![]() |
de04b40b86 | ||
![]() |
7206a3d926 | ||
![]() |
98b98d8938 | ||
![]() |
294b8fcc2f | ||
![]() |
32f601a1b1 | ||
![]() |
8e3a398080 | ||
![]() |
720b9688b4 | ||
![]() |
e3066161c5 | ||
![]() |
a9aa3a524f | ||
![]() |
92848e818a | ||
![]() |
a7aa3008c0 | ||
![]() |
485325e824 | ||
![]() |
0df2a0e039 | ||
![]() |
fb7b4466c2 | ||
![]() |
53105245f4 | ||
![]() |
b68eab91e0 | ||
![]() |
f72cf91462 | ||
![]() |
a655c4ef50 | ||
![]() |
5d46e9b796 | ||
![]() |
642e6e67b3 | ||
![]() |
2b6b86f903 | ||
![]() |
f2706a4426 | ||
![]() |
1af75413aa | ||
![]() |
adc4077b1a | ||
![]() |
01f4e2453b | ||
![]() |
61267374dd | ||
![]() |
970f814685 | ||
![]() |
4fd9fc1991 | ||
![]() |
26207ffd1b | ||
![]() |
2614cfbe5f | ||
![]() |
f11d461ec0 | ||
![]() |
b2eb2d2755 | ||
![]() |
ea46a54f19 | ||
![]() |
627add45ad | ||
![]() |
d364855459 | ||
![]() |
14ee35e77a | ||
![]() |
8773eb4ee0 |
@@ -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
|
||||
|
||||
|
@@ -35,7 +35,7 @@ class Cli {
|
||||
|
||||
Core core
|
||||
try {
|
||||
core = new Core(props, home, "0.3.8")
|
||||
core = new Core(props, home, "0.4.2")
|
||||
} catch (Exception bad) {
|
||||
bad.printStackTrace(System.out)
|
||||
println "Failed to initialize core, exiting"
|
||||
|
@@ -53,7 +53,7 @@ class CliDownloader {
|
||||
|
||||
Core core
|
||||
try {
|
||||
core = new Core(props, home, "0.3.8")
|
||||
core = new Core(props, home, "0.4.2")
|
||||
} catch (Exception bad) {
|
||||
bad.printStackTrace(System.out)
|
||||
println "Failed to initialize core, exiting"
|
||||
|
@@ -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'
|
||||
|
||||
|
@@ -9,7 +9,5 @@ class Constants {
|
||||
public static final int MAX_HEADER_SIZE = 0x1 << 14
|
||||
public static final int MAX_HEADERS = 16
|
||||
|
||||
public static final float DOWNLOAD_SEQUENTIAL_RATIO = 0.8f
|
||||
|
||||
public static final String SPLIT_PATTERN = "[\\+\\-,\\.:;\\(\\)=_/\\\\\\!\\\"\\\'\\\$%\\|]"
|
||||
public static final String SPLIT_PATTERN = "[\\*\\+\\-,\\.:;\\(\\)=_/\\\\\\!\\\"\\\'\\\$%\\|\\[\\]\\{\\}]"
|
||||
}
|
||||
|
@@ -27,6 +27,8 @@ import com.muwire.core.files.FileSharedEvent
|
||||
import com.muwire.core.files.FileUnsharedEvent
|
||||
import com.muwire.core.files.HasherService
|
||||
import com.muwire.core.files.PersisterService
|
||||
import com.muwire.core.files.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
|
||||
@@ -58,6 +60,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 {
|
||||
|
||||
@@ -79,25 +84,15 @@ 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()
|
||||
|
||||
log.info("initializing I2P socket manager")
|
||||
def i2pClient = new I2PClientFactory().createClient()
|
||||
File keyDat = new File(home, "key.dat")
|
||||
if (!keyDat.exists()) {
|
||||
log.info("Creating new key.dat")
|
||||
keyDat.withOutputStream {
|
||||
i2pClient.createDestination(it, Constants.SIG_TYPE)
|
||||
}
|
||||
}
|
||||
|
||||
i2pOptions = new Properties()
|
||||
def i2pOptionsFile = new File(home,"i2p.properties")
|
||||
@@ -117,8 +112,46 @@ public class Core {
|
||||
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("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()
|
||||
File keyDat = new File(home, "key.dat")
|
||||
if (!keyDat.exists()) {
|
||||
log.info("Creating new key.dat")
|
||||
keyDat.withOutputStream {
|
||||
i2pClient.createDestination(it, Constants.SIG_TYPE)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// options like tunnel length and quantity
|
||||
I2PSession i2pSession
|
||||
I2PSocketManager socketManager
|
||||
@@ -171,9 +204,10 @@ 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)
|
||||
MeshManager meshManager = new MeshManager(fileManager, home, props)
|
||||
eventBus.register(SourceDiscoveredEvent.class, meshManager)
|
||||
|
||||
log.info "initializing persistence service"
|
||||
@@ -223,7 +257,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)
|
||||
@@ -236,6 +270,8 @@ public class Core {
|
||||
log.info("initializing directory watcher")
|
||||
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)
|
||||
@@ -244,7 +280,6 @@ public class Core {
|
||||
|
||||
public void startServices() {
|
||||
hasherService.start()
|
||||
directoryWatcher.start()
|
||||
trustService.start()
|
||||
trustService.waitForLoad()
|
||||
hostCache.start()
|
||||
@@ -271,6 +306,23 @@ 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) {
|
||||
@@ -297,7 +349,7 @@ public class Core {
|
||||
}
|
||||
}
|
||||
|
||||
Core core = new Core(props, home, "0.3.8")
|
||||
Core core = new Core(props, home, "0.4.2")
|
||||
core.startServices()
|
||||
|
||||
// ... at the end, sleep or execute script
|
||||
|
@@ -18,6 +18,11 @@ class MuWireSettings {
|
||||
CrawlerResponse crawlerResponse
|
||||
boolean shareDownloadedFiles
|
||||
Set<String> watchedDirectories
|
||||
float downloadSequentialRatio
|
||||
int hostClearInterval
|
||||
int meshExpiration
|
||||
boolean embeddedRouter
|
||||
int inBw, outBw
|
||||
|
||||
MuWireSettings() {
|
||||
this(new Properties())
|
||||
@@ -33,6 +38,12 @@ class MuWireSettings {
|
||||
downloadRetryInterval = Integer.parseInt(props.getProperty("downloadRetryInterval","1"))
|
||||
updateCheckInterval = Integer.parseInt(props.getProperty("updateCheckInterval","24"))
|
||||
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")) {
|
||||
@@ -52,6 +63,12 @@ class MuWireSettings {
|
||||
props.setProperty("downloadRetryInterval", String.valueOf(downloadRetryInterval))
|
||||
props.setProperty("updateCheckInterval", String.valueOf(updateCheckInterval))
|
||||
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().
|
||||
|
@@ -0,0 +1,7 @@
|
||||
package com.muwire.core.files
|
||||
|
||||
import com.muwire.core.Event
|
||||
|
||||
class DirectoryUnsharedEvent extends Event {
|
||||
File directory
|
||||
}
|
@@ -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
|
||||
|
||||
@@ -47,7 +48,7 @@ class DirectoryWatcher {
|
||||
publisherThread.setDaemon(true)
|
||||
}
|
||||
|
||||
void start() {
|
||||
void onAllFilesLoadedEvent(AllFilesLoadedEvent e) {
|
||||
watchService = FileSystems.getDefault().newWatchService()
|
||||
watcherThread.start()
|
||||
publisherThread.start()
|
||||
@@ -55,19 +56,25 @@ class DirectoryWatcher {
|
||||
|
||||
void stop() {
|
||||
shutdown = true
|
||||
watcherThread.interrupt()
|
||||
publisherThread.interrupt()
|
||||
watchService.close()
|
||||
watcherThread?.interrupt()
|
||||
publisherThread?.interrupt()
|
||||
watchService?.close()
|
||||
}
|
||||
|
||||
void onFileSharedEvent(FileSharedEvent e) {
|
||||
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 {
|
||||
while(!shutdown) {
|
||||
|
@@ -7,4 +7,10 @@ class FileHashedEvent extends Event {
|
||||
|
||||
SharedFile sharedFile
|
||||
String error
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
super.toString() + " sharedFile " + sharedFile?.file.getAbsolutePath() + " error: $error"
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -5,4 +5,9 @@ import com.muwire.core.Event
|
||||
class FileSharedEvent extends Event {
|
||||
|
||||
File file
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + " file: "+file.getAbsolutePath()
|
||||
}
|
||||
}
|
||||
|
@@ -5,14 +5,15 @@ import net.i2p.data.Destination
|
||||
class Host {
|
||||
|
||||
private static final int MAX_FAILURES = 3
|
||||
private static final int CLEAR_INTERVAL = 60 * 60 * 1000
|
||||
|
||||
final Destination destination
|
||||
private final int clearInterval
|
||||
int failures,successes
|
||||
long lastAttempt
|
||||
|
||||
public Host(Destination destination) {
|
||||
public Host(Destination destination, int clearInterval) {
|
||||
this.destination = destination
|
||||
this.clearInterval = clearInterval
|
||||
}
|
||||
|
||||
synchronized void onConnect() {
|
||||
@@ -40,6 +41,6 @@ class Host {
|
||||
}
|
||||
|
||||
synchronized void canTryAgain() {
|
||||
System.currentTimeMillis() - lastAttempt > CLEAR_INTERVAL
|
||||
System.currentTimeMillis() - lastAttempt > (clearInterval * 60 * 1000)
|
||||
}
|
||||
}
|
||||
|
@@ -52,7 +52,7 @@ class HostCache extends Service {
|
||||
hosts.get(e.destination).clearFailures()
|
||||
return
|
||||
}
|
||||
Host host = new Host(e.destination)
|
||||
Host host = new Host(e.destination, settings.hostClearInterval)
|
||||
if (allowHost(host)) {
|
||||
hosts.put(e.destination, host)
|
||||
}
|
||||
@@ -64,7 +64,7 @@ class HostCache extends Service {
|
||||
Destination dest = e.endpoint.destination
|
||||
Host host = hosts.get(dest)
|
||||
if (host == null) {
|
||||
host = new Host(dest)
|
||||
host = new Host(dest, settings.hostClearInterval)
|
||||
hosts.put(dest, host)
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ class HostCache extends Service {
|
||||
storage.eachLine {
|
||||
def entry = slurper.parseText(it)
|
||||
Destination dest = new Destination(entry.destination)
|
||||
Host host = new Host(dest)
|
||||
Host host = new Host(dest, settings.hostClearInterval)
|
||||
host.failures = Integer.valueOf(String.valueOf(entry.failures))
|
||||
host.successes = Integer.valueOf(String.valueOf(entry.successes))
|
||||
if (entry.lastAttempt != null)
|
||||
|
@@ -4,6 +4,7 @@ import java.util.stream.Collectors
|
||||
|
||||
import com.muwire.core.Constants
|
||||
import com.muwire.core.InfoHash
|
||||
import com.muwire.core.MuWireSettings
|
||||
import com.muwire.core.Persona
|
||||
import com.muwire.core.download.Pieces
|
||||
import com.muwire.core.download.SourceDiscoveredEvent
|
||||
@@ -16,15 +17,15 @@ import net.i2p.data.Base64
|
||||
|
||||
class MeshManager {
|
||||
|
||||
private static final int EXPIRATION = 60 * 60 * 1000
|
||||
|
||||
private final Map<InfoHash, Mesh> meshes = Collections.synchronizedMap(new HashMap<>())
|
||||
private final FileManager fileManager
|
||||
private final File home
|
||||
private final MuWireSettings settings
|
||||
|
||||
MeshManager(FileManager fileManager, File home) {
|
||||
MeshManager(FileManager fileManager, File home, MuWireSettings settings) {
|
||||
this.fileManager = fileManager
|
||||
this.home = home
|
||||
this.settings = settings
|
||||
load()
|
||||
}
|
||||
|
||||
@@ -36,7 +37,7 @@ class MeshManager {
|
||||
synchronized(meshes) {
|
||||
if (meshes.containsKey(infoHash))
|
||||
return meshes.get(infoHash)
|
||||
Pieces pieces = new Pieces(nPieces, Constants.DOWNLOAD_SEQUENTIAL_RATIO)
|
||||
Pieces pieces = new Pieces(nPieces, settings.downloadSequentialRatio)
|
||||
if (fileManager.rootToFiles.containsKey(infoHash)) {
|
||||
for (int i = 0; i < nPieces; i++)
|
||||
pieces.markDownloaded(i)
|
||||
@@ -80,10 +81,10 @@ class MeshManager {
|
||||
JsonSlurper slurper = new JsonSlurper()
|
||||
meshFile.eachLine {
|
||||
def json = slurper.parseText(it)
|
||||
if (now - json.timestamp > EXPIRATION)
|
||||
if (now - json.timestamp > settings.meshExpiration * 60 * 1000)
|
||||
return
|
||||
InfoHash infoHash = new InfoHash(Base64.decode(json.infoHash))
|
||||
Pieces pieces = new Pieces(json.nPieces, Constants.DOWNLOAD_SEQUENTIAL_RATIO)
|
||||
Pieces pieces = new Pieces(json.nPieces, settings.downloadSequentialRatio)
|
||||
|
||||
Mesh mesh = new Mesh(infoHash, pieces)
|
||||
json.sources.each { source ->
|
||||
|
@@ -2,5 +2,5 @@ package com.muwire.core.upload
|
||||
|
||||
class ContentRequest extends Request {
|
||||
Range range
|
||||
boolean have
|
||||
int have
|
||||
}
|
||||
|
@@ -105,4 +105,14 @@ class ContentUploader extends Uploader {
|
||||
request.downloader.getHumanReadableName()
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDonePieces() {
|
||||
return request.have;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTotalPieces() {
|
||||
return mesh.pieces.nPieces;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -51,5 +51,15 @@ class HashListUploader extends Uploader {
|
||||
request.downloader.getHumanReadableName()
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDonePieces() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTotalPieces() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -50,10 +50,10 @@ class Request {
|
||||
downloader = new Persona(new ByteArrayInputStream(decoded))
|
||||
}
|
||||
|
||||
boolean have = false
|
||||
int have = 0
|
||||
if (headers.containsKey("X-Have")) {
|
||||
def encoded = headers["X-Have"].trim()
|
||||
have = DataUtil.decodeXHave(encoded).size() > 0
|
||||
have = DataUtil.decodeXHave(encoded).size()
|
||||
}
|
||||
new ContentRequest( infoHash : infoHash, range : new Range(start, end),
|
||||
headers : headers, downloader : downloader, have : have)
|
||||
|
@@ -80,7 +80,7 @@ public class UploadManager {
|
||||
return
|
||||
}
|
||||
|
||||
if (request.have)
|
||||
if (request.have > 0)
|
||||
eventBus.publish(new SourceDiscoveredEvent(infoHash : request.infoHash, source : request.downloader))
|
||||
|
||||
Mesh mesh
|
||||
@@ -205,7 +205,7 @@ public class UploadManager {
|
||||
return
|
||||
}
|
||||
|
||||
if (request.have)
|
||||
if (request.have > 0)
|
||||
eventBus.publish(new SourceDiscoveredEvent(infoHash : request.infoHash, source : request.downloader))
|
||||
|
||||
Mesh mesh
|
||||
|
@@ -32,4 +32,8 @@ abstract class Uploader {
|
||||
abstract int getProgress();
|
||||
|
||||
abstract String getDownloader();
|
||||
|
||||
abstract int getDonePieces();
|
||||
|
||||
abstract int getTotalPieces()
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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() {
|
||||
@@ -30,11 +36,19 @@ public class SharedFile {
|
||||
long length = file.length();
|
||||
int rawPieceSize = 0x1 << pieceSize;
|
||||
int rv = (int) (length / rawPieceSize);
|
||||
if (length % pieceSize != 0)
|
||||
if (length % rawPieceSize != 0)
|
||||
rv++;
|
||||
return rv;
|
||||
}
|
||||
|
||||
public String getCachedPath() {
|
||||
return cachedPath;
|
||||
}
|
||||
|
||||
public long getCachedLength() {
|
||||
return cachedLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return file.hashCode() ^ infoHash.hashCode();
|
||||
|
@@ -1,5 +1,5 @@
|
||||
group = com.muwire
|
||||
version = 0.3.8
|
||||
version = 0.4.2
|
||||
groovyVersion = 2.4.15
|
||||
slf4jVersion = 1.7.25
|
||||
spockVersion = 1.1-groovy-2.4
|
||||
|
@@ -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'
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,40 @@
|
||||
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.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()
|
||||
}
|
||||
}
|
@@ -19,6 +19,7 @@ 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.search.QueryEvent
|
||||
import com.muwire.core.search.SearchEvent
|
||||
import com.muwire.core.trust.TrustEvent
|
||||
@@ -44,6 +45,8 @@ class MainFrameController {
|
||||
search = search.trim()
|
||||
if (search.length() == 0)
|
||||
return
|
||||
if (search.length() > 128)
|
||||
search = search.substring(0,128)
|
||||
def uuid = UUID.randomUUID()
|
||||
Map<String, Object> params = new HashMap<>()
|
||||
params["search-terms"] = search
|
||||
@@ -207,6 +210,18 @@ class MainFrameController {
|
||||
println "unsharing selected files"
|
||||
}
|
||||
|
||||
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() {
|
||||
File f = new File(core.home, "MuWire.properties")
|
||||
f.withOutputStream {
|
||||
|
@@ -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()
|
||||
}
|
||||
}
|
@@ -4,9 +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 {
|
||||
@@ -19,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
|
||||
|
||||
@@ -38,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,"")
|
||||
@@ -46,7 +64,6 @@ 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
|
||||
@@ -61,6 +78,18 @@ class OptionsController {
|
||||
model.shareDownloadedFiles = shareDownloaded
|
||||
settings.shareDownloadedFiles = shareDownloaded
|
||||
|
||||
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)
|
||||
@@ -110,4 +139,15 @@ class OptionsController {
|
||||
view.d.setVisible(false)
|
||||
mvcGroup.destroy()
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void downloadLocation() {
|
||||
def chooser = new JFileChooser()
|
||||
chooser.setFileHidingEnabled(false)
|
||||
chooser.setDialogTitle("Select location for downloaded files")
|
||||
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY)
|
||||
int rv = chooser.showOpenDialog(null)
|
||||
if (rv == JFileChooser.APPROVE_OPTION)
|
||||
model.downloadLocation = chooser.getSelectedFile().getAbsolutePath()
|
||||
}
|
||||
}
|
@@ -43,6 +43,8 @@ class Initialize extends AbstractLifecycleHandler {
|
||||
|
||||
application.context.put("muwire-home", home.getAbsolutePath())
|
||||
|
||||
System.getProperties().setProperty("awt.useSystemAAFontSettings", "true")
|
||||
|
||||
def guiPropsFile = new File(home, "gui.properties")
|
||||
UISettings uiSettings
|
||||
if (guiPropsFile.exists()) {
|
||||
|
@@ -48,6 +48,7 @@ class Ready extends AbstractLifecycleHandler {
|
||||
} else {
|
||||
log.info("creating new properties")
|
||||
props = new MuWireSettings()
|
||||
props.embeddedRouter = Boolean.parseBoolean(System.getProperties().getProperty("embeddedRouter"))
|
||||
def nickname
|
||||
while (true) {
|
||||
nickname = JOptionPane.showInputDialog(null,
|
||||
@@ -74,6 +75,7 @@ class Ready extends AbstractLifecycleHandler {
|
||||
props.downloadLocation = new File(portableDownloads)
|
||||
} else {
|
||||
def chooser = new JFileChooser()
|
||||
chooser.setFileHidingEnabled(false)
|
||||
chooser.setDialogTitle("Select a directory where downloads will be saved")
|
||||
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY)
|
||||
int rv = chooser.showOpenDialog(null)
|
||||
|
27
gui/griffon-app/models/com/muwire/gui/I2PStatusModel.groovy
Normal file
@@ -0,0 +1,27 @@
|
||||
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 int participatingTunnels
|
||||
@Observable int activePeers
|
||||
@Observable int receiveBps
|
||||
@Observable int sendBps
|
||||
@Observable int participatingBW
|
||||
|
||||
void mvcGroupInit(Map<String,String> args) {
|
||||
controller.refresh()
|
||||
}
|
||||
}
|
@@ -17,6 +17,7 @@ import com.muwire.core.connection.ConnectionEvent
|
||||
import com.muwire.core.connection.DisconnectionEvent
|
||||
import com.muwire.core.download.DownloadStartedEvent
|
||||
import com.muwire.core.download.Downloader
|
||||
import com.muwire.core.files.AllFilesLoadedEvent
|
||||
import com.muwire.core.files.FileDownloadedEvent
|
||||
import com.muwire.core.files.FileHashedEvent
|
||||
import com.muwire.core.files.FileLoadedEvent
|
||||
@@ -51,6 +52,7 @@ class MainFrameModel {
|
||||
MainFrameController controller
|
||||
@Inject @Nonnull GriffonApplication application
|
||||
@Observable boolean coreInitialized = false
|
||||
@Observable boolean routerPresent
|
||||
|
||||
def results = new ConcurrentHashMap<>()
|
||||
def downloads = []
|
||||
@@ -75,7 +77,7 @@ class MainFrameModel {
|
||||
|
||||
private final Set<InfoHash> downloadInfoHashes = new HashSet<>()
|
||||
|
||||
volatile Core core
|
||||
@Observable volatile Core core
|
||||
|
||||
private long lastRetryTime = System.currentTimeMillis()
|
||||
|
||||
@@ -123,6 +125,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)
|
||||
@@ -139,6 +142,7 @@ class MainFrameModel {
|
||||
core.eventBus.register(FileDownloadedEvent.class, this)
|
||||
core.eventBus.register(FileUnsharedEvent.class, this)
|
||||
core.eventBus.register(RouterDisconnectedEvent.class, this)
|
||||
core.eventBus.register(AllFilesLoadedEvent.class, this)
|
||||
|
||||
timer.schedule({
|
||||
if (core.shutdown.get())
|
||||
@@ -167,9 +171,6 @@ class MainFrameModel {
|
||||
trusted.addAll(core.trustService.good.values())
|
||||
distrusted.addAll(core.trustService.bad.values())
|
||||
|
||||
watched.addAll(core.muOptions.watchedDirectories)
|
||||
builder.getVariable("watched-directories-table").model.fireTableDataChanged()
|
||||
watched.each { core.eventBus.publish(new FileSharedEvent(file : new File(it))) }
|
||||
|
||||
resumeButtonText = "Retry"
|
||||
}
|
||||
@@ -177,6 +178,13 @@ class MainFrameModel {
|
||||
|
||||
}
|
||||
|
||||
void onAllFilesLoadedEvent(AllFilesLoadedEvent e) {
|
||||
runInsideUIAsync {
|
||||
watched.addAll(core.muOptions.watchedDirectories)
|
||||
builder.getVariable("watched-directories-table").model.fireTableDataChanged()
|
||||
watched.each { core.eventBus.publish(new FileSharedEvent(file : new File(it))) }
|
||||
}
|
||||
}
|
||||
void onUIResultEvent(UIResultEvent e) {
|
||||
MVCGroup resultsGroup = results.get(e.uuid)
|
||||
resultsGroup?.model.handleResult(e)
|
||||
|
@@ -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()
|
||||
}
|
||||
}
|
@@ -13,12 +13,15 @@ class OptionsModel {
|
||||
@Observable String updateCheckInterval
|
||||
@Observable boolean onlyTrusted
|
||||
@Observable boolean shareDownloadedFiles
|
||||
@Observable String downloadLocation
|
||||
|
||||
// i2p options
|
||||
@Observable String inboundLength
|
||||
@Observable String inboundQuantity
|
||||
@Observable String outboundLength
|
||||
@Observable String outboundQuantity
|
||||
@Observable String i2pUDPPort
|
||||
@Observable String i2pNTCPPort
|
||||
|
||||
// gui options
|
||||
@Observable boolean showMonitor
|
||||
@@ -29,18 +32,25 @@ 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
|
||||
onlyTrusted = !settings.allowUntrusted()
|
||||
shareDownloadedFiles = settings.shareDownloadedFiles
|
||||
downloadLocation = settings.downloadLocation.getAbsolutePath()
|
||||
|
||||
Core core = application.context.get("core")
|
||||
inboundLength = core.i2pOptions["inbound.length"]
|
||||
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
|
||||
@@ -50,5 +60,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)
|
||||
}
|
||||
}
|
||||
}
|
BIN
gui/griffon-app/resources/MuWire-128x128.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
gui/griffon-app/resources/MuWire-16x16.png
Normal file
After Width: | Height: | Size: 344 B |
BIN
gui/griffon-app/resources/MuWire-32x32.png
Normal file
After Width: | Height: | Size: 459 B |
BIN
gui/griffon-app/resources/MuWire-48x48.png
Normal file
After Width: | Height: | Size: 1003 B |
BIN
gui/griffon-app/resources/MuWire-64x64.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 5.8 KiB |
@@ -21,10 +21,10 @@ class EventListView {
|
||||
application(size: [320, 80], id: 'event-list',
|
||||
locationRelativeTo : null,
|
||||
title: application.configuration['application.title'],
|
||||
iconImage: imageIcon('/griffon-icon-48x48.png').image,
|
||||
iconImages: [imageIcon('/griffon-icon-48x48.png').image,
|
||||
imageIcon('/griffon-icon-32x32.png').image,
|
||||
imageIcon('/griffon-icon-16x16.png').image],
|
||||
iconImage: imageIcon('/MuWire-48x48.png').image,
|
||||
iconImages: [imageIcon('/MuWire-48x48.png').image,
|
||||
imageIcon('/MuWire-32x32.png').image,
|
||||
imageIcon('/MuWire-16x16.png').image],
|
||||
visible: bind { !model.coreInitialized} ) {
|
||||
panel {
|
||||
vbox {
|
||||
|
78
gui/griffon-app/views/com/muwire/gui/I2PStatusView.groovy
Normal file
@@ -0,0 +1,78 @@
|
||||
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 : "NTCP Connections", constraints : gbc(gridx:0, gridy:1))
|
||||
label(text : bind {model.ntcpConnections}, constraints : gbc(gridx: 1, gridy:1))
|
||||
label(text : "SSU Connections", constraints : gbc(gridx:0, gridy:2))
|
||||
label(text : bind {model.ssuConnections}, constraints : gbc(gridx: 1, gridy:2))
|
||||
label(text : "Participating Tunnels", constraints : gbc(gridx:0, gridy:3))
|
||||
label(text : bind {model.participatingTunnels}, constraints : gbc(gridx: 1, gridy:3))
|
||||
label(text : "Participating Bandwidth", constraints : gbc(gridx:0, gridy:4))
|
||||
label(text : bind {model.participatingBW}, constraints : gbc(gridx: 1, gridy:4))
|
||||
label(text : "Active Peers", constraints : gbc(gridx:0, gridy:5))
|
||||
label(text : bind {model.activePeers}, constraints : gbc(gridx: 1, gridy:5))
|
||||
label(text : "Receive Bps (15 seconds)", constraints : gbc(gridx:0, gridy:6))
|
||||
label(text : bind {model.receiveBps}, constraints : gbc(gridx: 1, gridy:6))
|
||||
label(text : "Send Bps (15 seconds)", constraints : gbc(gridx:0, gridy:7))
|
||||
label(text : bind {model.sendBps}, constraints : gbc(gridx: 1, gridy:7))
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
@@ -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")
|
||||
@@ -60,16 +62,21 @@ class MainFrameView {
|
||||
locationRelativeTo : null,
|
||||
title: application.configuration['application.title'] + " " +
|
||||
metadata["application.version"] + " revision " + metadata["build.revision"],
|
||||
iconImage: imageIcon('/griffon-icon-48x48.png').image,
|
||||
iconImages: [imageIcon('/griffon-icon-48x48.png').image,
|
||||
imageIcon('/griffon-icon-32x32.png').image,
|
||||
imageIcon('/griffon-icon-16x16.png').image],
|
||||
iconImage: imageIcon('/MuWire-48x48.png').image,
|
||||
iconImages: [imageIcon('/MuWire-48x48.png').image,
|
||||
imageIcon('/MuWire-32x32.png').image,
|
||||
imageIcon('/MuWire-16x16.png').image],
|
||||
pack : false,
|
||||
visible : bind { model.coreInitialized }) {
|
||||
menuBar {
|
||||
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() })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -185,11 +192,14 @@ class MainFrameView {
|
||||
closureColumn(header : "Name", type : String, read : {row -> row.getName() })
|
||||
closureColumn(header : "Progress", type : String, read : { row ->
|
||||
int percent = row.getProgress()
|
||||
"$percent%"
|
||||
"$percent% of piece".toString()
|
||||
})
|
||||
closureColumn(header : "Downloader", type : String, read : { row ->
|
||||
row.getDownloader()
|
||||
})
|
||||
closureColumn(header : "Remote Pieces", type : String, read : { row ->
|
||||
"${row.getDonePieces()}/${row.getTotalPieces()}".toString()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -394,6 +404,27 @@ 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) {
|
||||
@@ -516,6 +547,7 @@ class MainFrameView {
|
||||
|
||||
def shareFiles = {
|
||||
def chooser = new JFileChooser()
|
||||
chooser.setFileHidingEnabled(false)
|
||||
chooser.setDialogTitle("Select file to share")
|
||||
chooser.setFileSelectionMode(JFileChooser.FILES_ONLY)
|
||||
int rv = chooser.showOpenDialog(null)
|
||||
@@ -526,6 +558,7 @@ class MainFrameView {
|
||||
|
||||
def watchDirectories = {
|
||||
def chooser = new JFileChooser()
|
||||
chooser.setFileHidingEnabled(false)
|
||||
chooser.setDialogTitle("Select directory to watch")
|
||||
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY)
|
||||
int rv = chooser.showOpenDialog(null)
|
||||
@@ -538,4 +571,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]
|
||||
}
|
||||
}
|
73
gui/griffon-app/views/com/muwire/gui/MuWireStatusView.groovy
Normal 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()
|
||||
}
|
||||
}
|
@@ -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,6 +28,7 @@ class OptionsView {
|
||||
def p
|
||||
def i
|
||||
def u
|
||||
def bandwidth
|
||||
|
||||
def retryField
|
||||
def updateField
|
||||
@@ -36,6 +39,8 @@ class OptionsView {
|
||||
def inboundQuantityField
|
||||
def outboundLengthField
|
||||
def outboundQuantityField
|
||||
def i2pUDPPortField
|
||||
def i2pNTCPPortField
|
||||
|
||||
def lnfField
|
||||
def monitorCheckbox
|
||||
@@ -45,6 +50,10 @@ class OptionsView {
|
||||
def excludeLocalResultCheckbox
|
||||
def showSearchHashesCheckbox
|
||||
|
||||
|
||||
def inBwField
|
||||
def outBwField
|
||||
|
||||
def buttonsPanel
|
||||
|
||||
def mainFrame
|
||||
@@ -69,6 +78,10 @@ class OptionsView {
|
||||
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 : "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))
|
||||
|
||||
}
|
||||
i = builder.panel {
|
||||
gridBagLayout()
|
||||
@@ -81,6 +94,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()
|
||||
@@ -100,6 +122,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)
|
||||
@@ -112,6 +144,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())
|
||||
|
@@ -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!')
|
||||
}
|
||||
}
|
@@ -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!')
|
||||
}
|
||||
}
|
@@ -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!')
|
||||
}
|
||||
}
|
@@ -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!')
|
||||
}
|
||||
}
|
@@ -26,7 +26,7 @@ handlers= java.util.logging.FileHandler
|
||||
# can be overriden by a facility specific level
|
||||
# Note that the ConsoleHandler also has a separate level
|
||||
# setting to limit messages printed to the console.
|
||||
.level= SEVERE
|
||||
.level= INFO
|
||||
|
||||
############################################################
|
||||
# Handler specific properties.
|
||||
|