Compare commits

...

53 Commits

Author SHA1 Message Date
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
Zlatin Balevsky
53105245f4 Release 0.4.0 2019-06-26 21:59:28 +01:00
Zlatin Balevsky
b68eab91e0 Release 0.3.10 2019-06-25 22:39:43 +01:00
Zlatin Balevsky
f72cf91462 wait for files to be loaded before sharing watched directories 2019-06-25 22:24:32 +01:00
Zlatin Balevsky
a655c4ef50 add toString 2019-06-25 22:24:15 +01:00
Zlatin Balevsky
5d46e9b796 switch 4_ to INFO 2019-06-25 21:50:15 +01:00
Zlatin Balevsky
642e6e67b3 wait for all files loaded before watching dirs 2019-06-25 21:43:07 +01:00
Zlatin Balevsky
2b6b86f903 show how many pieces the remote side already has 2019-06-25 17:44:05 +01:00
Zlatin Balevsky
f2706a4426 clarify upload column 2019-06-25 17:24:42 +01:00
Zlatin Balevsky
1af75413aa update for brackets 2019-06-25 16:27:02 +01:00
Zlatin Balevsky
adc4077b1a filter asterix 2019-06-25 15:54:30 +01:00
Zlatin Balevsky
01f4e2453b limit search length to 128 characters 2019-06-25 15:53:53 +01:00
Zlatin Balevsky
61267374dd move button around 2019-06-25 08:10:20 +01:00
Zlatin Balevsky
970f814685 make mesh expiration configurable 2019-06-25 08:04:57 +01:00
Zlatin Balevsky
4fd9fc1991 add option to change download location 2019-06-25 07:59:30 +01:00
Zlatin Balevsky
26207ffd1b add constructor 2019-06-25 07:53:24 +01:00
Zlatin Balevsky
2614cfbe5f make host clear interval configurable 2019-06-25 07:41:20 +01:00
Zlatin Balevsky
f11d461ec0 make download sequential ratio a property 2019-06-25 07:34:26 +01:00
Zlatin Balevsky
b2eb2d2755 show hidden files in file choosers 2019-06-24 23:09:20 +01:00
Zlatin Balevsky
ea46a54f19 enable AA by default 2019-06-24 22:55:26 +01:00
Zlatin Balevsky
627add45ad remove griffon icons 2019-06-24 22:51:43 +01:00
Zlatin Balevsky
d364855459 logo 2019-06-24 22:13:03 +01:00
Zlatin Balevsky
14ee35e77a Release 0.3.9 2019-06-24 18:39:59 +01:00
Zlatin Balevsky
8773eb4ee0 fix piece size calculation 2019-06-24 18:29:00 +01:00
Zlatin Balevsky
51425bbfd9 Release 0.3.8 2019-06-24 07:38:39 +01:00
Zlatin Balevsky
6a4879bc0b always save pieces 2019-06-24 07:29:49 +01:00
Zlatin Balevsky
e7fe56439b persist X-Have, fix flickering bug 2019-06-24 07:20:53 +01:00
Zlatin Balevsky
2886feab4a do not modify the set of available pieces 2019-06-23 17:08:07 +01:00
Zlatin Balevsky
fb91194026 even noisier log 2019-06-23 16:39:38 +01:00
Zlatin Balevsky
4527478b0d even noisier 4_ 2019-06-23 12:42:44 +01:00
Zlatin Balevsky
b0062f146e log roots of download exceptions 2019-06-23 12:10:19 +01:00
Zlatin Balevsky
bf16561170 Release 0.3.7 2019-06-23 11:25:19 +01:00
Zlatin Balevsky
3b23dc29c4 if all sources are expired forget mesh 2019-06-23 11:21:39 +01:00
Zlatin Balevsky
c0645b670e no split on list 2019-06-23 10:50:19 +01:00
Zlatin Balevsky
30613fe530 update todo 2019-06-23 09:56:51 +01:00
Zlatin Balevsky
e7822f6edc expire sources, fix compilation 2019-06-23 09:43:56 +01:00
Zlatin Balevsky
7e5c9ba115 actually save 2019-06-23 09:41:20 +01:00
Zlatin Balevsky
647fa3a481 persist download mesh 2019-06-23 09:38:42 +01:00
Zlatin Balevsky
538eca9297 Release 0.3.6 2019-06-23 08:54:28 +01:00
Zlatin Balevsky
e73a23d4a4 fix space not showing 2019-06-23 08:44:51 +01:00
Zlatin Balevsky
76e41a0383 fix restoring paused downloads 2019-06-23 08:42:45 +01:00
Zlatin Balevsky
7045927666 hide monitor options from gui 2019-06-23 08:02:28 +01:00
Zlatin Balevsky
5fb3086b42 update faq 2019-06-23 07:52:01 +01:00
Zlatin Balevsky
2de18227c1 persist pause state 2019-06-23 07:48:49 +01:00
Zlatin Balevsky
bd12a1de3d pause/resume downloads 2019-06-23 06:59:52 +01:00
Zlatin Balevsky
a3a91050c8 update todo 2019-06-23 01:50:30 +01:00
Zlatin Balevsky
6c1cc28e49 shutdown if connection to I2P router is lost 2019-06-22 17:32:12 +01:00
Zlatin Balevsky
b6e5b54f05 do not show monitor by default 2019-06-22 14:51:26 +01:00
Zlatin Balevsky
a6e559ec67 change some defaults 2019-06-22 06:54:49 +01:00
Zlatin Balevsky
f11badb824 update todo 2019-06-21 22:43:46 +01:00
52 changed files with 419 additions and 79 deletions

View File

@@ -4,7 +4,7 @@ MuWire is an easy to use file-sharing program which offers anonymity using [I2P
It is inspired by the LimeWire Gnutella client and developped by a former LimeWire developer. It is inspired by the LimeWire Gnutella client and developped by a former LimeWire developer.
The current stable release - 0.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 ### Building
@@ -37,8 +37,8 @@ The first time you run MuWire it will ask you to select a nickname. This nickna
* why is MuWire slow ? * why is MuWire slow ?
- too few sources you're downloading from - too few sources you're downloading from
- you can increse the number of tunnels by using more tunnels via Options->I2P Inbound/Outbound Quantity - you can increase the number of tunnels by using more tunnels via Options->I2P Inbound/Outbound Quantity
the default is 4 and you could raise it and even can go up as high as 16 ( Caution !!!!) the default is 4 and you could raise up to as high as 16 ( Caution !!!!)
* my search is not returning (enough) results ! * my search is not returning (enough) results !
@@ -46,4 +46,11 @@ The first time you run MuWire it will ask you to select a nickname. This nickna
- keywords and hash(es) are NOT regexed or wildcarded so they have to be complete - keywords and hash(es) are NOT regexed or wildcarded so they have to be complete
so searching for 'musi' will not return results with 'music' - you have to search for 'music' so searching for 'musi' will not return results with 'music' - you have to search for 'music'
- ALL keywords have to match - ALL keywords have to match
- only use <SPACE> for keyword separation - only use space for keyword separation
- if you already have the file in question it is not displayed ( can be changed via Options )
* what's this right click -> 'Copy hash to clipboard' for ?
- if you have a specific file you wish to share or download you can use the hash as a unique identifier
to make sure you have exactly the right file.
- you can share this hash with others to ensure they are getting the right file

View File

@@ -4,10 +4,6 @@ Not in any particular order yet
### Big Items ### Big Items
##### Alternate Locations
This helps peers discover new sources for a file while the download is in progress. Also makes sharing of partial files possible.
##### Bloom Filters ##### Bloom Filters
This reduces query traffic by not sending last hop queries to peers that definitely do not have the file This reduces query traffic by not sending last hop queries to peers that definitely do not have the file
@@ -38,7 +34,6 @@ To enable parsing of metadata from known file types and the user editing it or a
### Small Items ### Small Items
* Detect if router is dead and show warning or exit
* Wrapper of some kind for in-place upgrades * Wrapper of some kind for in-place upgrades
* Download file sequentially * Download file sequentially
* Unsharing of files * Unsharing of files

View File

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

View File

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

View File

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

View File

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

View File

@@ -16,6 +16,8 @@ import com.muwire.core.download.DownloadManager
import com.muwire.core.download.SourceDiscoveredEvent import com.muwire.core.download.SourceDiscoveredEvent
import com.muwire.core.download.UIDownloadCancelledEvent import com.muwire.core.download.UIDownloadCancelledEvent
import com.muwire.core.download.UIDownloadEvent import com.muwire.core.download.UIDownloadEvent
import com.muwire.core.download.UIDownloadPausedEvent
import com.muwire.core.download.UIDownloadResumedEvent
import com.muwire.core.files.FileDownloadedEvent import com.muwire.core.files.FileDownloadedEvent
import com.muwire.core.files.FileHashedEvent import com.muwire.core.files.FileHashedEvent
import com.muwire.core.files.FileHasher import com.muwire.core.files.FileHasher
@@ -25,6 +27,7 @@ import com.muwire.core.files.FileSharedEvent
import com.muwire.core.files.FileUnsharedEvent import com.muwire.core.files.FileUnsharedEvent
import com.muwire.core.files.HasherService import com.muwire.core.files.HasherService
import com.muwire.core.files.PersisterService import com.muwire.core.files.PersisterService
import com.muwire.core.files.AllFilesLoadedEvent
import com.muwire.core.files.DirectoryWatcher import com.muwire.core.files.DirectoryWatcher
import com.muwire.core.hostcache.CacheClient import com.muwire.core.hostcache.CacheClient
import com.muwire.core.hostcache.HostCache import com.muwire.core.hostcache.HostCache
@@ -48,6 +51,7 @@ import net.i2p.client.I2PSession
import net.i2p.client.streaming.I2PSocketManager import net.i2p.client.streaming.I2PSocketManager
import net.i2p.client.streaming.I2PSocketManagerFactory import net.i2p.client.streaming.I2PSocketManagerFactory
import net.i2p.client.streaming.I2PSocketOptions import net.i2p.client.streaming.I2PSocketOptions
import net.i2p.client.streaming.I2PSocketManager.DisconnectListener
import net.i2p.crypto.DSAEngine import net.i2p.crypto.DSAEngine
import net.i2p.crypto.SigType import net.i2p.crypto.SigType
import net.i2p.data.Destination import net.i2p.data.Destination
@@ -55,6 +59,9 @@ import net.i2p.data.PrivateKey
import net.i2p.data.Signature import net.i2p.data.Signature
import net.i2p.data.SigningPrivateKey import net.i2p.data.SigningPrivateKey
import net.i2p.router.Router
import net.i2p.router.RouterContext
@Log @Log
public class Core { public class Core {
@@ -77,14 +84,31 @@ public class Core {
private final DirectoryWatcher directoryWatcher private final DirectoryWatcher directoryWatcher
final FileManager fileManager final FileManager fileManager
private final Router router
final AtomicBoolean shutdown = new AtomicBoolean() final AtomicBoolean shutdown = new AtomicBoolean()
public Core(MuWireSettings props, File home, String myVersion) { public Core(MuWireSettings props, File home, String myVersion) {
this.home = home this.home = home
this.muOptions = props this.muOptions = props
if (!props.embeddedRouter) {
log.info "Initializing I2P context" log.info "Initializing I2P context"
I2PAppContext.getGlobalContext().logManager() I2PAppContext.getGlobalContext().logManager()
I2PAppContext.getGlobalContext()._logManager = new MuWireLogManager() 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))
router = new Router(routerProps)
I2PAppContext.getGlobalContext().metaClass = new RouterContextMetaClass()
router.runRouter()
while(!router.isRunning())
Thread.sleep(100)
}
log.info("initializing I2P socket manager") log.info("initializing I2P socket manager")
def i2pClient = new I2PClientFactory().createClient() def i2pClient = new I2PClientFactory().createClient()
@@ -124,6 +148,7 @@ public class Core {
} }
socketManager.getDefaultOptions().setReadTimeout(60000) socketManager.getDefaultOptions().setReadTimeout(60000)
socketManager.getDefaultOptions().setConnectTimeout(30000) socketManager.getDefaultOptions().setConnectTimeout(30000)
socketManager.addDisconnectListener({eventBus.publish(new RouterDisconnectedEvent())} as DisconnectListener)
i2pSession = socketManager.getSession() i2pSession = socketManager.getSession()
def destination = new Destination() def destination = new Destination()
@@ -169,7 +194,7 @@ public class Core {
eventBus.register(SearchEvent.class, fileManager) eventBus.register(SearchEvent.class, fileManager)
log.info("initializing mesh manager") log.info("initializing mesh manager")
MeshManager meshManager = new MeshManager(fileManager) MeshManager meshManager = new MeshManager(fileManager, home, props)
eventBus.register(SourceDiscoveredEvent.class, meshManager) eventBus.register(SourceDiscoveredEvent.class, meshManager)
log.info "initializing persistence service" log.info "initializing persistence service"
@@ -215,6 +240,8 @@ public class Core {
eventBus.register(FileDownloadedEvent.class, downloadManager) eventBus.register(FileDownloadedEvent.class, downloadManager)
eventBus.register(UIDownloadCancelledEvent.class, downloadManager) eventBus.register(UIDownloadCancelledEvent.class, downloadManager)
eventBus.register(SourceDiscoveredEvent.class, downloadManager) eventBus.register(SourceDiscoveredEvent.class, downloadManager)
eventBus.register(UIDownloadPausedEvent.class, downloadManager)
eventBus.register(UIDownloadResumedEvent.class, downloadManager)
log.info("initializing upload manager") log.info("initializing upload manager")
UploadManager uploadManager = new UploadManager(eventBus, fileManager, meshManager, downloadManager) UploadManager uploadManager = new UploadManager(eventBus, fileManager, meshManager, downloadManager)
@@ -230,6 +257,7 @@ public class Core {
log.info("initializing directory watcher") log.info("initializing directory watcher")
directoryWatcher = new DirectoryWatcher(eventBus, fileManager) directoryWatcher = new DirectoryWatcher(eventBus, fileManager)
eventBus.register(FileSharedEvent.class, directoryWatcher) eventBus.register(FileSharedEvent.class, directoryWatcher)
eventBus.register(AllFilesLoadedEvent.class, directoryWatcher)
log.info("initializing hasher service") log.info("initializing hasher service")
hasherService = new HasherService(new FileHasher(), eventBus, fileManager) hasherService = new HasherService(new FileHasher(), eventBus, fileManager)
@@ -238,7 +266,6 @@ public class Core {
public void startServices() { public void startServices() {
hasherService.start() hasherService.start()
directoryWatcher.start()
trustService.start() trustService.start()
trustService.waitForLoad() trustService.waitForLoad()
hostCache.start() hostCache.start()
@@ -265,6 +292,23 @@ public class Core {
directoryWatcher.stop() directoryWatcher.stop()
log.info("shutting down connection manager") log.info("shutting down connection manager")
connectionManager.shutdown() 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) { static main(args) {
@@ -291,7 +335,7 @@ public class Core {
} }
} }
Core core = new Core(props, home, "0.3.5") Core core = new Core(props, home, "0.4.0")
core.startServices() core.startServices()
// ... at the end, sleep or execute script // ... at the end, sleep or execute script

View File

@@ -18,6 +18,11 @@ class MuWireSettings {
CrawlerResponse crawlerResponse CrawlerResponse crawlerResponse
boolean shareDownloadedFiles boolean shareDownloadedFiles
Set<String> watchedDirectories Set<String> watchedDirectories
float downloadSequentialRatio
int hostClearInterval
int meshExpiration
boolean embeddedRouter
int inBw, outBw
MuWireSettings() { MuWireSettings() {
this(new Properties()) this(new Properties())
@@ -30,9 +35,15 @@ class MuWireSettings {
nickname = props.getProperty("nickname","MuWireUser") nickname = props.getProperty("nickname","MuWireUser")
downloadLocation = new File((String)props.getProperty("downloadLocation", downloadLocation = new File((String)props.getProperty("downloadLocation",
System.getProperty("user.home"))) System.getProperty("user.home")))
downloadRetryInterval = Integer.parseInt(props.getProperty("downloadRetryInterval","5")) downloadRetryInterval = Integer.parseInt(props.getProperty("downloadRetryInterval","1"))
updateCheckInterval = Integer.parseInt(props.getProperty("updateCheckInterval","36")) updateCheckInterval = Integer.parseInt(props.getProperty("updateCheckInterval","24"))
shareDownloadedFiles = Boolean.parseBoolean(props.getProperty("shareDownloadedFiles","true")) 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<>() watchedDirectories = new HashSet<>()
if (props.containsKey("watchedDirectories")) { if (props.containsKey("watchedDirectories")) {
@@ -52,6 +63,12 @@ class MuWireSettings {
props.setProperty("downloadRetryInterval", String.valueOf(downloadRetryInterval)) props.setProperty("downloadRetryInterval", String.valueOf(downloadRetryInterval))
props.setProperty("updateCheckInterval", String.valueOf(updateCheckInterval)) props.setProperty("updateCheckInterval", String.valueOf(updateCheckInterval))
props.setProperty("shareDownloadedFiles", String.valueOf(shareDownloadedFiles)) 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()) { if (!watchedDirectories.isEmpty()) {
String encoded = watchedDirectories.stream(). String encoded = watchedDirectories.stream().

View File

@@ -0,0 +1,4 @@
package com.muwire.core
class RouterDisconnectedEvent extends Event {
}

View File

@@ -90,6 +90,14 @@ public class DownloadManager {
persistDownloaders() persistDownloaders()
} }
public void onUIDownloadPausedEvent(UIDownloadPausedEvent e) {
persistDownloaders()
}
public void onUIDownloadResumedEvent(UIDownloadResumedEvent e) {
persistDownloaders()
}
void resume(Downloader downloader) { void resume(Downloader downloader) {
executor.execute({downloader.download() as Runnable}) executor.execute({downloader.download() as Runnable})
} }
@@ -119,7 +127,11 @@ public class DownloadManager {
def downloader = new Downloader(eventBus, this, me, file, (long)json.length, def downloader = new Downloader(eventBus, this, me, file, (long)json.length,
infoHash, json.pieceSizePow2, connector, destinations, incompletes, pieces) infoHash, json.pieceSizePow2, connector, destinations, incompletes, pieces)
if (json.paused != null)
downloader.paused = json.paused
downloaders.put(infoHash, downloader) downloaders.put(infoHash, downloader)
downloader.readPieces()
if (!downloader.paused)
downloader.download() downloader.download()
eventBus.publish(new DownloadStartedEvent(downloader : downloader)) eventBus.publish(new DownloadStartedEvent(downloader : downloader))
} }
@@ -174,6 +186,8 @@ public class DownloadManager {
json.hashList = Base64.encode(infoHash.hashList) json.hashList = Base64.encode(infoHash.hashList)
else else
json.hashRoot = Base64.encode(infoHash.getRoot()) json.hashRoot = Base64.encode(infoHash.getRoot())
json.paused = downloader.paused
writer.println(JsonOutput.toJson(json)) writer.println(JsonOutput.toJson(json))
} }
} }

View File

@@ -75,7 +75,7 @@ class DownloadSession {
if (available.isEmpty()) if (available.isEmpty())
piece = pieces.claim() piece = pieces.claim()
else else
piece = pieces.claim(available) piece = pieces.claim(new HashSet<>(available))
if (piece == -1) if (piece == -1)
return false return false
boolean unclaim = true boolean unclaim = true

View File

@@ -18,6 +18,7 @@ import com.muwire.core.DownloadedFile
import com.muwire.core.EventBus import com.muwire.core.EventBus
import com.muwire.core.connection.I2PConnector import com.muwire.core.connection.I2PConnector
import com.muwire.core.files.FileDownloadedEvent import com.muwire.core.files.FileDownloadedEvent
import com.muwire.core.util.DataUtil
import groovy.util.logging.Log import groovy.util.logging.Log
import net.i2p.data.Destination import net.i2p.data.Destination
@@ -25,7 +26,7 @@ import net.i2p.util.ConcurrentHashSet
@Log @Log
public class Downloader { public class Downloader {
public enum DownloadState { CONNECTING, HASHLIST, DOWNLOADING, FAILED, CANCELLED, FINISHED } public enum DownloadState { CONNECTING, HASHLIST, DOWNLOADING, FAILED, CANCELLED, PAUSED, FINISHED }
private enum WorkerState { CONNECTING, HASHLIST, DOWNLOADING, FINISHED} private enum WorkerState { CONNECTING, HASHLIST, DOWNLOADING, FINISHED}
private static final ExecutorService executorService = Executors.newCachedThreadPool({r -> private static final ExecutorService executorService = Executors.newCachedThreadPool({r ->
@@ -53,7 +54,7 @@ public class Downloader {
private final Set<Destination> successfulDestinations = new ConcurrentHashSet<>() private final Set<Destination> successfulDestinations = new ConcurrentHashSet<>()
private volatile boolean cancelled private volatile boolean cancelled, paused
private final AtomicBoolean eventFired = new AtomicBoolean() private final AtomicBoolean eventFired = new AtomicBoolean()
private boolean piecesFileClosed private boolean piecesFileClosed
@@ -136,6 +137,9 @@ public class Downloader {
public DownloadState getCurrentState() { public DownloadState getCurrentState() {
if (cancelled) if (cancelled)
return DownloadState.CANCELLED return DownloadState.CANCELLED
if (paused)
return DownloadState.PAUSED
boolean allFinished = true boolean allFinished = true
activeWorkers.values().each { activeWorkers.values().each {
allFinished &= it.currentState == WorkerState.FINISHED allFinished &= it.currentState == WorkerState.FINISHED
@@ -183,6 +187,11 @@ public class Downloader {
pieces.clearAll() pieces.clearAll()
} }
public void pause() {
paused = true
stop()
}
void stop() { void stop() {
activeWorkers.values().each { activeWorkers.values().each {
it.cancel() it.cancel()
@@ -199,6 +208,8 @@ public class Downloader {
} }
public void resume() { public void resume() {
paused = false
readPieces()
destinations.each { destination -> destinations.each { destination ->
def worker = activeWorkers.get(destination) def worker = activeWorkers.get(destination)
if (worker != null) { if (worker != null) {
@@ -259,7 +270,7 @@ public class Downloader {
writePieces() writePieces()
} }
} catch (Exception bad) { } catch (Exception bad) {
log.log(Level.WARNING,"Exception while downloading",bad) log.log(Level.WARNING,"Exception while downloading",DataUtil.findRoot(bad))
} finally { } finally {
currentState = WorkerState.FINISHED currentState = WorkerState.FINISHED
if (pieces.isComplete() && eventFired.compareAndSet(false, true)) { if (pieces.isComplete() && eventFired.compareAndSet(false, true)) {

View File

@@ -0,0 +1,6 @@
package com.muwire.core.download
import com.muwire.core.Event
class UIDownloadPausedEvent extends Event {
}

View File

@@ -0,0 +1,6 @@
package com.muwire.core.download
import com.muwire.core.Event
class UIDownloadResumedEvent extends Event {
}

View File

@@ -47,7 +47,7 @@ class DirectoryWatcher {
publisherThread.setDaemon(true) publisherThread.setDaemon(true)
} }
void start() { void onAllFilesLoadedEvent(AllFilesLoadedEvent e) {
watchService = FileSystems.getDefault().newWatchService() watchService = FileSystems.getDefault().newWatchService()
watcherThread.start() watcherThread.start()
publisherThread.start() publisherThread.start()
@@ -55,9 +55,9 @@ class DirectoryWatcher {
void stop() { void stop() {
shutdown = true shutdown = true
watcherThread.interrupt() watcherThread?.interrupt()
publisherThread.interrupt() publisherThread?.interrupt()
watchService.close() watchService?.close()
} }
void onFileSharedEvent(FileSharedEvent e) { void onFileSharedEvent(FileSharedEvent e) {

View File

@@ -7,4 +7,10 @@ class FileHashedEvent extends Event {
SharedFile sharedFile SharedFile sharedFile
String error String error
@Override
public String toString() {
super.toString() + " sharedFile " + sharedFile?.file.getAbsolutePath() + " error: $error"
}
} }

View File

@@ -5,4 +5,9 @@ import com.muwire.core.Event
class FileSharedEvent extends Event { class FileSharedEvent extends Event {
File file File file
@Override
public String toString() {
return super.toString() + " file: "+file.getAbsolutePath()
}
} }

View File

@@ -5,14 +5,15 @@ import net.i2p.data.Destination
class Host { class Host {
private static final int MAX_FAILURES = 3 private static final int MAX_FAILURES = 3
private static final int CLEAR_INTERVAL = 60 * 60 * 1000
final Destination destination final Destination destination
private final int clearInterval
int failures,successes int failures,successes
long lastAttempt long lastAttempt
public Host(Destination destination) { public Host(Destination destination, int clearInterval) {
this.destination = destination this.destination = destination
this.clearInterval = clearInterval
} }
synchronized void onConnect() { synchronized void onConnect() {
@@ -40,6 +41,6 @@ class Host {
} }
synchronized void canTryAgain() { synchronized void canTryAgain() {
System.currentTimeMillis() - lastAttempt > CLEAR_INTERVAL System.currentTimeMillis() - lastAttempt > (clearInterval * 60 * 1000)
} }
} }

View File

@@ -52,7 +52,7 @@ class HostCache extends Service {
hosts.get(e.destination).clearFailures() hosts.get(e.destination).clearFailures()
return return
} }
Host host = new Host(e.destination) Host host = new Host(e.destination, settings.hostClearInterval)
if (allowHost(host)) { if (allowHost(host)) {
hosts.put(e.destination, host) hosts.put(e.destination, host)
} }
@@ -64,7 +64,7 @@ class HostCache extends Service {
Destination dest = e.endpoint.destination Destination dest = e.endpoint.destination
Host host = hosts.get(dest) Host host = hosts.get(dest)
if (host == null) { if (host == null) {
host = new Host(dest) host = new Host(dest, settings.hostClearInterval)
hosts.put(dest, host) hosts.put(dest, host)
} }
@@ -106,7 +106,7 @@ class HostCache extends Service {
storage.eachLine { storage.eachLine {
def entry = slurper.parseText(it) def entry = slurper.parseText(it)
Destination dest = new Destination(entry.destination) 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.failures = Integer.valueOf(String.valueOf(entry.failures))
host.successes = Integer.valueOf(String.valueOf(entry.successes)) host.successes = Integer.valueOf(String.valueOf(entry.successes))
if (entry.lastAttempt != null) if (entry.lastAttempt != null)

View File

@@ -1,18 +1,32 @@
package com.muwire.core.mesh package com.muwire.core.mesh
import java.util.stream.Collectors
import com.muwire.core.Constants import com.muwire.core.Constants
import com.muwire.core.InfoHash 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.Pieces
import com.muwire.core.download.SourceDiscoveredEvent import com.muwire.core.download.SourceDiscoveredEvent
import com.muwire.core.files.FileManager import com.muwire.core.files.FileManager
import com.muwire.core.util.DataUtil
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import net.i2p.data.Base64
class MeshManager { class MeshManager {
private final Map<InfoHash, Mesh> meshes = Collections.synchronizedMap(new HashMap<>()) private final Map<InfoHash, Mesh> meshes = Collections.synchronizedMap(new HashMap<>())
private final FileManager fileManager private final FileManager fileManager
private final File home
private final MuWireSettings settings
MeshManager(FileManager fileManager) { MeshManager(FileManager fileManager, File home, MuWireSettings settings) {
this.fileManager = fileManager this.fileManager = fileManager
this.home = home
this.settings = settings
load()
} }
Mesh get(InfoHash infoHash) { Mesh get(InfoHash infoHash) {
@@ -23,7 +37,7 @@ class MeshManager {
synchronized(meshes) { synchronized(meshes) {
if (meshes.containsKey(infoHash)) if (meshes.containsKey(infoHash))
return meshes.get(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)) { if (fileManager.rootToFiles.containsKey(infoHash)) {
for (int i = 0; i < nPieces; i++) for (int i = 0; i < nPieces; i++)
pieces.markDownloaded(i) pieces.markDownloaded(i)
@@ -39,5 +53,50 @@ class MeshManager {
if (mesh == null) if (mesh == null)
return return
mesh.sources.add(e.source) mesh.sources.add(e.source)
save()
}
private void save() {
File meshFile = new File(home, "mesh.json")
synchronized(meshes) {
meshFile.withPrintWriter { writer ->
meshes.values().each { mesh ->
def json = [:]
json.timestamp = System.currentTimeMillis()
json.infoHash = Base64.encode(mesh.infoHash.getRoot())
json.sources = mesh.sources.stream().map({it.toBase64()}).collect(Collectors.toList())
json.nPieces = mesh.pieces.nPieces
json.xHave = DataUtil.encodeXHave(mesh.pieces.downloaded, mesh.pieces.nPieces)
writer.println(JsonOutput.toJson(json))
}
}
}
}
private void load() {
File meshFile = new File(home, "mesh.json")
if (!meshFile.exists())
return
long now = System.currentTimeMillis()
JsonSlurper slurper = new JsonSlurper()
meshFile.eachLine {
def json = slurper.parseText(it)
if (now - json.timestamp > settings.meshExpiration * 60 * 1000)
return
InfoHash infoHash = new InfoHash(Base64.decode(json.infoHash))
Pieces pieces = new Pieces(json.nPieces, settings.downloadSequentialRatio)
Mesh mesh = new Mesh(infoHash, pieces)
json.sources.each { source ->
Persona persona = new Persona(new ByteArrayInputStream(Base64.decode(source)))
mesh.sources.add(persona)
}
if (json.xHave != null)
DataUtil.decodeXHave(json.xHave).each { pieces.markDownloaded(it) }
if (!mesh.sources.isEmpty())
meshes.put(infoHash, mesh)
}
} }
} }

View File

@@ -2,5 +2,5 @@ package com.muwire.core.upload
class ContentRequest extends Request { class ContentRequest extends Request {
Range range Range range
boolean have int have
} }

View File

@@ -105,4 +105,14 @@ class ContentUploader extends Uploader {
request.downloader.getHumanReadableName() request.downloader.getHumanReadableName()
} }
@Override
public int getDonePieces() {
return request.have;
}
@Override
public int getTotalPieces() {
return mesh.pieces.nPieces;
}
} }

View File

@@ -51,5 +51,15 @@ class HashListUploader extends Uploader {
request.downloader.getHumanReadableName() request.downloader.getHumanReadableName()
} }
@Override
public int getDonePieces() {
return 0;
}
@Override
public int getTotalPieces() {
return 1;
}
} }

View File

@@ -50,10 +50,10 @@ class Request {
downloader = new Persona(new ByteArrayInputStream(decoded)) downloader = new Persona(new ByteArrayInputStream(decoded))
} }
boolean have = false int have = 0
if (headers.containsKey("X-Have")) { if (headers.containsKey("X-Have")) {
def encoded = headers["X-Have"].trim() 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), new ContentRequest( infoHash : infoHash, range : new Range(start, end),
headers : headers, downloader : downloader, have : have) headers : headers, downloader : downloader, have : have)

View File

@@ -80,7 +80,7 @@ public class UploadManager {
return return
} }
if (request.have) if (request.have > 0)
eventBus.publish(new SourceDiscoveredEvent(infoHash : request.infoHash, source : request.downloader)) eventBus.publish(new SourceDiscoveredEvent(infoHash : request.infoHash, source : request.downloader))
Mesh mesh Mesh mesh
@@ -205,7 +205,7 @@ public class UploadManager {
return return
} }
if (request.have) if (request.have > 0)
eventBus.publish(new SourceDiscoveredEvent(infoHash : request.infoHash, source : request.downloader)) eventBus.publish(new SourceDiscoveredEvent(infoHash : request.infoHash, source : request.downloader))
Mesh mesh Mesh mesh

View File

@@ -32,4 +32,8 @@ abstract class Uploader {
abstract int getProgress(); abstract int getProgress();
abstract String getDownloader(); abstract String getDownloader();
abstract int getDonePieces();
abstract int getTotalPieces()
} }

View File

@@ -109,4 +109,10 @@ class DataUtil {
} }
available available
} }
public static Exception findRoot(Exception e) {
while(e.getCause() != null)
e = e.getCause()
e
}
} }

View File

@@ -30,7 +30,7 @@ public class SharedFile {
long length = file.length(); long length = file.length();
int rawPieceSize = 0x1 << pieceSize; int rawPieceSize = 0x1 << pieceSize;
int rv = (int) (length / rawPieceSize); int rv = (int) (length / rawPieceSize);
if (length % pieceSize != 0) if (length % rawPieceSize != 0)
rv++; rv++;
return rv; return rv;
} }

View File

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

View File

@@ -17,6 +17,8 @@ import com.muwire.core.Core
import com.muwire.core.download.DownloadStartedEvent import com.muwire.core.download.DownloadStartedEvent
import com.muwire.core.download.UIDownloadCancelledEvent import com.muwire.core.download.UIDownloadCancelledEvent
import com.muwire.core.download.UIDownloadEvent import com.muwire.core.download.UIDownloadEvent
import com.muwire.core.download.UIDownloadPausedEvent
import com.muwire.core.download.UIDownloadResumedEvent
import com.muwire.core.search.QueryEvent import com.muwire.core.search.QueryEvent
import com.muwire.core.search.SearchEvent import com.muwire.core.search.SearchEvent
import com.muwire.core.trust.TrustEvent import com.muwire.core.trust.TrustEvent
@@ -42,6 +44,8 @@ class MainFrameController {
search = search.trim() search = search.trim()
if (search.length() == 0) if (search.length() == 0)
return return
if (search.length() > 128)
search = search.substring(0,128)
def uuid = UUID.randomUUID() def uuid = UUID.randomUUID()
Map<String, Object> params = new HashMap<>() Map<String, Object> params = new HashMap<>()
params["search-terms"] = search params["search-terms"] = search
@@ -164,6 +168,14 @@ class MainFrameController {
void resume() { void resume() {
def downloader = model.downloads[selectedDownload()].downloader def downloader = model.downloads[selectedDownload()].downloader
downloader.resume() downloader.resume()
core.eventBus.publish(new UIDownloadResumedEvent())
}
@ControllerAction
void pause() {
def downloader = model.downloads[selectedDownload()].downloader
downloader.pause()
core.eventBus.publish(new UIDownloadPausedEvent())
} }
private void markTrust(String tableName, TrustLevel level, def list) { private void markTrust(String tableName, TrustLevel level, def list) {

View File

@@ -4,9 +4,15 @@ import griffon.core.artifact.GriffonController
import griffon.core.controller.ControllerAction import griffon.core.controller.ControllerAction
import griffon.inject.MVCMember import griffon.inject.MVCMember
import griffon.metadata.ArtifactProviderFor import griffon.metadata.ArtifactProviderFor
import groovy.util.logging.Log
import java.util.logging.Level
import javax.annotation.Nonnull import javax.annotation.Nonnull
import javax.swing.JFileChooser
import com.muwire.core.Core import com.muwire.core.Core
import com.muwire.core.MuWireSettings
@ArtifactProviderFor(GriffonController) @ArtifactProviderFor(GriffonController)
class OptionsController { class OptionsController {
@@ -46,7 +52,7 @@ class OptionsController {
text = view.retryField.text text = view.retryField.text
model.downloadRetryInterval = text model.downloadRetryInterval = text
def settings = application.context.get("muwire-settings") MuWireSettings settings = application.context.get("muwire-settings")
settings.downloadRetryInterval = Integer.valueOf(text) settings.downloadRetryInterval = Integer.valueOf(text)
text = view.updateField.text text = view.updateField.text
@@ -61,6 +67,18 @@ class OptionsController {
model.shareDownloadedFiles = shareDownloaded model.shareDownloadedFiles = shareDownloaded
settings.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") File settingsFile = new File(core.home, "MuWire.properties")
settingsFile.withOutputStream { settingsFile.withOutputStream {
settings.write(it) settings.write(it)
@@ -77,9 +95,9 @@ class OptionsController {
model.font = text model.font = text
uiSettings.font = text uiSettings.font = text
boolean showMonitor = view.monitorCheckbox.model.isSelected() // boolean showMonitor = view.monitorCheckbox.model.isSelected()
model.showMonitor = showMonitor // model.showMonitor = showMonitor
uiSettings.showMonitor = showMonitor // uiSettings.showMonitor = showMonitor
boolean clearCancelledDownloads = view.clearCancelledDownloadsCheckbox.model.isSelected() boolean clearCancelledDownloads = view.clearCancelledDownloadsCheckbox.model.isSelected()
model.clearCancelledDownloads = clearCancelledDownloads model.clearCancelledDownloads = clearCancelledDownloads
@@ -93,9 +111,9 @@ class OptionsController {
model.excludeLocalResult = excludeLocalResult model.excludeLocalResult = excludeLocalResult
uiSettings.excludeLocalResult = excludeLocalResult uiSettings.excludeLocalResult = excludeLocalResult
boolean showSearchHashes = view.showSearchHashesCheckbox.model.isSelected() // boolean showSearchHashes = view.showSearchHashesCheckbox.model.isSelected()
model.showSearchHashes = showSearchHashes // model.showSearchHashes = showSearchHashes
uiSettings.showSearchHashes = showSearchHashes // uiSettings.showSearchHashes = showSearchHashes
File uiSettingsFile = new File(core.home, "gui.properties") File uiSettingsFile = new File(core.home, "gui.properties")
uiSettingsFile.withOutputStream { uiSettingsFile.withOutputStream {
@@ -110,4 +128,15 @@ class OptionsController {
view.d.setVisible(false) view.d.setVisible(false)
mvcGroup.destroy() 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()
}
} }

View File

@@ -43,6 +43,8 @@ class Initialize extends AbstractLifecycleHandler {
application.context.put("muwire-home", home.getAbsolutePath()) application.context.put("muwire-home", home.getAbsolutePath())
System.getProperties().setProperty("awt.useSystemAAFontSettings", "true")
def guiPropsFile = new File(home, "gui.properties") def guiPropsFile = new File(home, "gui.properties")
UISettings uiSettings UISettings uiSettings
if (guiPropsFile.exists()) { if (guiPropsFile.exists()) {

View File

@@ -74,6 +74,7 @@ class Ready extends AbstractLifecycleHandler {
props.downloadLocation = new File(portableDownloads) props.downloadLocation = new File(portableDownloads)
} else { } else {
def chooser = new JFileChooser() def chooser = new JFileChooser()
chooser.setFileHidingEnabled(false)
chooser.setDialogTitle("Select a directory where downloads will be saved") chooser.setDialogTitle("Select a directory where downloads will be saved")
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY) chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY)
int rv = chooser.showOpenDialog(null) int rv = chooser.showOpenDialog(null)

View File

@@ -11,11 +11,13 @@ import com.muwire.core.Core
import com.muwire.core.InfoHash import com.muwire.core.InfoHash
import com.muwire.core.MuWireSettings import com.muwire.core.MuWireSettings
import com.muwire.core.Persona import com.muwire.core.Persona
import com.muwire.core.RouterDisconnectedEvent
import com.muwire.core.connection.ConnectionAttemptStatus import com.muwire.core.connection.ConnectionAttemptStatus
import com.muwire.core.connection.ConnectionEvent import com.muwire.core.connection.ConnectionEvent
import com.muwire.core.connection.DisconnectionEvent import com.muwire.core.connection.DisconnectionEvent
import com.muwire.core.download.DownloadStartedEvent import com.muwire.core.download.DownloadStartedEvent
import com.muwire.core.download.Downloader import com.muwire.core.download.Downloader
import com.muwire.core.files.AllFilesLoadedEvent
import com.muwire.core.files.FileDownloadedEvent import com.muwire.core.files.FileDownloadedEvent
import com.muwire.core.files.FileHashedEvent import com.muwire.core.files.FileHashedEvent
import com.muwire.core.files.FileLoadedEvent import com.muwire.core.files.FileLoadedEvent
@@ -67,6 +69,8 @@ class MainFrameModel {
@Observable boolean trustButtonsEnabled @Observable boolean trustButtonsEnabled
@Observable boolean cancelButtonEnabled @Observable boolean cancelButtonEnabled
@Observable boolean retryButtonEnabled @Observable boolean retryButtonEnabled
@Observable boolean pauseButtonEnabled
@Observable String resumeButtonText
private final Set<InfoHash> infoHashes = new HashSet<>() private final Set<InfoHash> infoHashes = new HashSet<>()
@@ -135,6 +139,8 @@ class MainFrameModel {
core.eventBus.register(UpdateAvailableEvent.class, this) core.eventBus.register(UpdateAvailableEvent.class, this)
core.eventBus.register(FileDownloadedEvent.class, this) core.eventBus.register(FileDownloadedEvent.class, this)
core.eventBus.register(FileUnsharedEvent.class, this) core.eventBus.register(FileUnsharedEvent.class, this)
core.eventBus.register(RouterDisconnectedEvent.class, this)
core.eventBus.register(AllFilesLoadedEvent.class, this)
timer.schedule({ timer.schedule({
if (core.shutdown.get()) if (core.shutdown.get())
@@ -163,14 +169,20 @@ class MainFrameModel {
trusted.addAll(core.trustService.good.values()) trusted.addAll(core.trustService.good.values())
distrusted.addAll(core.trustService.bad.values()) distrusted.addAll(core.trustService.bad.values())
watched.addAll(core.muOptions.watchedDirectories)
builder.getVariable("watched-directories-table").model.fireTableDataChanged() resumeButtonText = "Retry"
watched.each { core.eventBus.publish(new FileSharedEvent(file : new File(it))) }
} }
}) })
} }
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) { void onUIResultEvent(UIResultEvent e) {
MVCGroup resultsGroup = results.get(e.uuid) MVCGroup resultsGroup = results.get(e.uuid)
resultsGroup?.model.handleResult(e) resultsGroup?.model.handleResult(e)
@@ -339,6 +351,14 @@ class MainFrameModel {
} }
} }
void onRouterDisconnectedEvent(RouterDisconnectedEvent e) {
runInsideUIAsync {
JOptionPane.showMessageDialog(null, "MuWire lost connection to the I2P router and will now exit.",
"Connection to I2P router lost", JOptionPane.WARNING_MESSAGE)
System.exit(0)
}
}
void onFileDownloadedEvent(FileDownloadedEvent e) { void onFileDownloadedEvent(FileDownloadedEvent e) {
if (!core.muOptions.shareDownloadedFiles) if (!core.muOptions.shareDownloadedFiles)
return return

View File

@@ -13,6 +13,7 @@ class OptionsModel {
@Observable String updateCheckInterval @Observable String updateCheckInterval
@Observable boolean onlyTrusted @Observable boolean onlyTrusted
@Observable boolean shareDownloadedFiles @Observable boolean shareDownloadedFiles
@Observable String downloadLocation
// i2p options // i2p options
@Observable String inboundLength @Observable String inboundLength
@@ -29,12 +30,17 @@ class OptionsModel {
@Observable boolean excludeLocalResult @Observable boolean excludeLocalResult
@Observable boolean showSearchHashes @Observable boolean showSearchHashes
// bw options
@Observable String inBw
@Observable String outBw
void mvcGroupInit(Map<String, String> args) { void mvcGroupInit(Map<String, String> args) {
MuWireSettings settings = application.context.get("muwire-settings") MuWireSettings settings = application.context.get("muwire-settings")
downloadRetryInterval = settings.downloadRetryInterval downloadRetryInterval = settings.downloadRetryInterval
updateCheckInterval = settings.updateCheckInterval updateCheckInterval = settings.updateCheckInterval
onlyTrusted = !settings.allowUntrusted() onlyTrusted = !settings.allowUntrusted()
shareDownloadedFiles = settings.shareDownloadedFiles shareDownloadedFiles = settings.shareDownloadedFiles
downloadLocation = settings.downloadLocation.getAbsolutePath()
Core core = application.context.get("core") Core core = application.context.get("core")
inboundLength = core.i2pOptions["inbound.length"] inboundLength = core.i2pOptions["inbound.length"]
@@ -50,5 +56,10 @@ class OptionsModel {
clearFinishedDownloads = uiSettings.clearFinishedDownloads clearFinishedDownloads = uiSettings.clearFinishedDownloads
excludeLocalResult = uiSettings.excludeLocalResult excludeLocalResult = uiSettings.excludeLocalResult
showSearchHashes = uiSettings.showSearchHashes showSearchHashes = uiSettings.showSearchHashes
if (core.router != null) {
inBw = String.valueOf(settings.inBw)
outBw = String.valueOf(settings.outBw)
}
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1003 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

View File

@@ -21,10 +21,10 @@ class EventListView {
application(size: [320, 80], id: 'event-list', application(size: [320, 80], id: 'event-list',
locationRelativeTo : null, locationRelativeTo : null,
title: application.configuration['application.title'], title: application.configuration['application.title'],
iconImage: imageIcon('/griffon-icon-48x48.png').image, iconImage: imageIcon('/MuWire-48x48.png').image,
iconImages: [imageIcon('/griffon-icon-48x48.png').image, iconImages: [imageIcon('/MuWire-48x48.png').image,
imageIcon('/griffon-icon-32x32.png').image, imageIcon('/MuWire-32x32.png').image,
imageIcon('/griffon-icon-16x16.png').image], imageIcon('/MuWire-16x16.png').image],
visible: bind { !model.coreInitialized} ) { visible: bind { !model.coreInitialized} ) {
panel { panel {
vbox { vbox {

View File

@@ -60,10 +60,10 @@ class MainFrameView {
locationRelativeTo : null, locationRelativeTo : null,
title: application.configuration['application.title'] + " " + title: application.configuration['application.title'] + " " +
metadata["application.version"] + " revision " + metadata["build.revision"], metadata["application.version"] + " revision " + metadata["build.revision"],
iconImage: imageIcon('/griffon-icon-48x48.png').image, iconImage: imageIcon('/MuWire-48x48.png').image,
iconImages: [imageIcon('/griffon-icon-48x48.png').image, iconImages: [imageIcon('/MuWire-48x48.png').image,
imageIcon('/griffon-icon-32x32.png').image, imageIcon('/MuWire-32x32.png').image,
imageIcon('/griffon-icon-16x16.png').image], imageIcon('/MuWire-16x16.png').image],
pack : false, pack : false,
visible : bind { model.coreInitialized }) { visible : bind { model.coreInitialized }) {
menuBar { menuBar {
@@ -135,8 +135,9 @@ class MainFrameView {
} }
} }
panel (constraints : BorderLayout.SOUTH) { panel (constraints : BorderLayout.SOUTH) {
button(text: "Pause", enabled : bind {model.pauseButtonEnabled}, pauseAction)
button(text: "Cancel", enabled : bind {model.cancelButtonEnabled }, cancelAction ) button(text: "Cancel", enabled : bind {model.cancelButtonEnabled }, cancelAction )
button(text: "Retry", enabled : bind {model.retryButtonEnabled}, resumeAction) button(text: bind { model.resumeButtonText }, enabled : bind {model.retryButtonEnabled}, resumeAction)
} }
} }
} }
@@ -184,11 +185,14 @@ class MainFrameView {
closureColumn(header : "Name", type : String, read : {row -> row.getName() }) closureColumn(header : "Name", type : String, read : {row -> row.getName() })
closureColumn(header : "Progress", type : String, read : { row -> closureColumn(header : "Progress", type : String, read : { row ->
int percent = row.getProgress() int percent = row.getProgress()
"$percent%" "$percent% of piece".toString()
}) })
closureColumn(header : "Downloader", type : String, read : { row -> closureColumn(header : "Downloader", type : String, read : { row ->
row.getDownloader() row.getDownloader()
}) })
closureColumn(header : "Remote Pieces", type : String, read : { row ->
"${row.getDonePieces()}/${row.getTotalPieces()}".toString()
})
} }
} }
} }
@@ -295,6 +299,7 @@ class MainFrameView {
if (selectedRow < 0) { if (selectedRow < 0) {
model.cancelButtonEnabled = false model.cancelButtonEnabled = false
model.retryButtonEnabled = false model.retryButtonEnabled = false
model.pauseButtonEnabled = false
return return
} }
def downloader = model.downloads[selectedRow]?.downloader def downloader = model.downloads[selectedRow]?.downloader
@@ -305,15 +310,25 @@ class MainFrameView {
case Downloader.DownloadState.DOWNLOADING : case Downloader.DownloadState.DOWNLOADING :
case Downloader.DownloadState.HASHLIST: case Downloader.DownloadState.HASHLIST:
model.cancelButtonEnabled = true model.cancelButtonEnabled = true
model.pauseButtonEnabled = true
model.retryButtonEnabled = false model.retryButtonEnabled = false
break break
case Downloader.DownloadState.FAILED: case Downloader.DownloadState.FAILED:
model.cancelButtonEnabled = true model.cancelButtonEnabled = true
model.retryButtonEnabled = true model.retryButtonEnabled = true
model.resumeButtonText = "Retry"
model.pauseButtonEnabled = false
break
case Downloader.DownloadState.PAUSED:
model.cancelButtonEnabled = true
model.retryButtonEnabled = true
model.resumeButtonText = "Resume"
model.pauseButtonEnabled = false
break break
default: default:
model.cancelButtonEnabled = false model.cancelButtonEnabled = false
model.retryButtonEnabled = false model.retryButtonEnabled = false
model.pauseButtonEnabled = false
} }
}) })
@@ -421,21 +436,32 @@ class MainFrameView {
int selected = selectedDownloaderRow() int selected = selectedDownloaderRow()
if (selected < 0) if (selected < 0)
return return
boolean pauseEnabled = false
boolean cancelEnabled = false boolean cancelEnabled = false
boolean retryEnabled = false boolean retryEnabled = false
String resumeText = "Retry"
Downloader downloader = model.downloads[selected].downloader Downloader downloader = model.downloads[selected].downloader
switch(downloader.currentState) { switch(downloader.currentState) {
case Downloader.DownloadState.DOWNLOADING: case Downloader.DownloadState.DOWNLOADING:
case Downloader.DownloadState.HASHLIST: case Downloader.DownloadState.HASHLIST:
case Downloader.DownloadState.CONNECTING: case Downloader.DownloadState.CONNECTING:
pauseEnabled = true
cancelEnabled = true cancelEnabled = true
retryEnabled = false retryEnabled = false
break break
case Downloader.DownloadState.FAILED: case Downloader.DownloadState.FAILED:
pauseEnabled = false
cancelEnabled = true cancelEnabled = true
retryEnabled = true retryEnabled = true
break break
case Downloader.DownloadState.PAUSED:
pauseEnabled = false
cancelEnabled = true
retryEnabled = true
resumeText = "Resume"
break
default : default :
pauseEnabled = false
cancelEnabled = false cancelEnabled = false
retryEnabled = false retryEnabled = false
} }
@@ -450,6 +476,12 @@ class MainFrameView {
}) })
menu.add(copyHashToClipboard) menu.add(copyHashToClipboard)
if (pauseEnabled) {
JMenuItem pause = new JMenuItem("Pause")
pause.addActionListener({mvcGroup.controller.pause()})
menu.add(pause)
}
if (cancelEnabled) { if (cancelEnabled) {
JMenuItem cancel = new JMenuItem("Cancel") JMenuItem cancel = new JMenuItem("Cancel")
cancel.addActionListener({mvcGroup.controller.cancel()}) cancel.addActionListener({mvcGroup.controller.cancel()})
@@ -457,7 +489,7 @@ class MainFrameView {
} }
if (retryEnabled) { if (retryEnabled) {
JMenuItem retry = new JMenuItem("Retry") JMenuItem retry = new JMenuItem(resumeText)
retry.addActionListener({mvcGroup.controller.resume()}) retry.addActionListener({mvcGroup.controller.resume()})
menu.add(retry) menu.add(retry)
} }
@@ -487,6 +519,7 @@ class MainFrameView {
def shareFiles = { def shareFiles = {
def chooser = new JFileChooser() def chooser = new JFileChooser()
chooser.setFileHidingEnabled(false)
chooser.setDialogTitle("Select file to share") chooser.setDialogTitle("Select file to share")
chooser.setFileSelectionMode(JFileChooser.FILES_ONLY) chooser.setFileSelectionMode(JFileChooser.FILES_ONLY)
int rv = chooser.showOpenDialog(null) int rv = chooser.showOpenDialog(null)
@@ -497,6 +530,7 @@ class MainFrameView {
def watchDirectories = { def watchDirectories = {
def chooser = new JFileChooser() def chooser = new JFileChooser()
chooser.setFileHidingEnabled(false)
chooser.setDialogTitle("Select directory to watch") chooser.setDialogTitle("Select directory to watch")
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY) chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY)
int rv = chooser.showOpenDialog(null) int rv = chooser.showOpenDialog(null)

View File

@@ -9,6 +9,8 @@ import javax.swing.JPanel
import javax.swing.JTabbedPane import javax.swing.JTabbedPane
import javax.swing.SwingConstants import javax.swing.SwingConstants
import com.muwire.core.Core
import java.awt.BorderLayout import java.awt.BorderLayout
import java.awt.event.WindowAdapter import java.awt.event.WindowAdapter
import java.awt.event.WindowEvent import java.awt.event.WindowEvent
@@ -26,6 +28,7 @@ class OptionsView {
def p def p
def i def i
def u def u
def bandwidth
def retryField def retryField
def updateField def updateField
@@ -45,6 +48,10 @@ class OptionsView {
def excludeLocalResultCheckbox def excludeLocalResultCheckbox
def showSearchHashesCheckbox def showSearchHashesCheckbox
def inBwField
def outBwField
def buttonsPanel def buttonsPanel
def mainFrame def mainFrame
@@ -69,6 +76,10 @@ class OptionsView {
label(text : "Share downloaded files", constraints : gbc(gridx : 0, 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)) 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 { i = builder.panel {
gridBagLayout() gridBagLayout()
@@ -89,17 +100,27 @@ class OptionsView {
lnfField = textField(text : bind {model.lnf}, columns : 4, constraints : gbc(gridx : 1, gridy : 1)) lnfField = textField(text : bind {model.lnf}, columns : 4, constraints : gbc(gridx : 1, gridy : 1))
label(text : "Font", constraints : gbc(gridx: 0, gridy : 2)) label(text : "Font", constraints : gbc(gridx: 0, gridy : 2))
fontField = textField(text : bind {model.font}, columns : 4, constraints : gbc(gridx : 1, gridy:2)) fontField = textField(text : bind {model.font}, columns : 4, constraints : gbc(gridx : 1, gridy:2))
label(text : "Show Monitor", constraints : gbc(gridx :0, gridy: 3)) // label(text : "Show Monitor", constraints : gbc(gridx :0, gridy: 3))
monitorCheckbox = checkBox(selected : bind {model.showMonitor}, constraints : gbc(gridx : 1, gridy: 3)) // monitorCheckbox = checkBox(selected : bind {model.showMonitor}, constraints : gbc(gridx : 1, gridy: 3))
label(text : "Clear Cancelled Downloads", constraints: gbc(gridx: 0, gridy:4)) label(text : "Clear Cancelled Downloads", constraints: gbc(gridx: 0, gridy:4))
clearCancelledDownloadsCheckbox = checkBox(selected : bind {model.clearCancelledDownloads}, constraints : gbc(gridx : 1, gridy:4)) clearCancelledDownloadsCheckbox = checkBox(selected : bind {model.clearCancelledDownloads}, constraints : gbc(gridx : 1, gridy:4))
label(text : "Clear Finished Downloads", constraints: gbc(gridx: 0, gridy:5)) label(text : "Clear Finished Downloads", constraints: gbc(gridx: 0, gridy:5))
clearFinishedDownloadsCheckbox = checkBox(selected : bind {model.clearFinishedDownloads}, constraints : gbc(gridx : 1, gridy:5)) clearFinishedDownloadsCheckbox = checkBox(selected : bind {model.clearFinishedDownloads}, constraints : gbc(gridx : 1, gridy:5))
label(text : "Exclude Local Files From Results", constraints: gbc(gridx:0, gridy:6)) label(text : "Exclude Local Files From Results", constraints: gbc(gridx:0, gridy:6))
excludeLocalResultCheckbox = checkBox(selected : bind {model.excludeLocalResult}, constraints : gbc(gridx: 1, gridy : 6)) excludeLocalResultCheckbox = checkBox(selected : bind {model.excludeLocalResult}, constraints : gbc(gridx: 1, gridy : 6))
label(text : "Show Hash Searches In Monitor", constraints: gbc(gridx:0, gridy:7)) // 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)) // 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 { buttonsPanel = builder.panel {
gridBagLayout() gridBagLayout()
button(text : "Save", constraints : gbc(gridx : 1, gridy: 2), saveAction) button(text : "Save", constraints : gbc(gridx : 1, gridy: 2), saveAction)
@@ -112,6 +133,10 @@ class OptionsView {
tabbedPane.addTab("MuWire", p) tabbedPane.addTab("MuWire", p)
tabbedPane.addTab("I2P", i) tabbedPane.addTab("I2P", i)
tabbedPane.addTab("GUI", u) tabbedPane.addTab("GUI", u)
Core core = application.context.get("core")
if (core.router != null) {
tabbedPane.addTab("Bandwidth", bandwidth)
}
JPanel panel = new JPanel() JPanel panel = new JPanel()
panel.setLayout(new BorderLayout()) panel.setLayout(new BorderLayout())

View File

@@ -12,12 +12,12 @@ class UISettings {
UISettings(Properties props) { UISettings(Properties props) {
lnf = props.getProperty("lnf", "system") lnf = props.getProperty("lnf", "system")
showMonitor = Boolean.parseBoolean(props.getProperty("showMonitor", "true")) showMonitor = Boolean.parseBoolean(props.getProperty("showMonitor", "false"))
font = props.getProperty("font",null) font = props.getProperty("font",null)
clearCancelledDownloads = Boolean.parseBoolean(props.getProperty("clearCancelledDownloads","false")) clearCancelledDownloads = Boolean.parseBoolean(props.getProperty("clearCancelledDownloads","true"))
clearFinishedDownloads = Boolean.parseBoolean(props.getProperty("clearFinishedDownloads","false")) clearFinishedDownloads = Boolean.parseBoolean(props.getProperty("clearFinishedDownloads","false"))
excludeLocalResult = Boolean.parseBoolean(props.getProperty("excludeLocalResult","true")) excludeLocalResult = Boolean.parseBoolean(props.getProperty("excludeLocalResult","true"))
showSearchHashes = Boolean.parseBoolean(props.getProperty("showSearchHashes","false")) showSearchHashes = Boolean.parseBoolean(props.getProperty("showSearchHashes","true"))
} }
void write(OutputStream out) throws IOException { void write(OutputStream out) throws IOException {

View File

@@ -26,7 +26,7 @@ handlers= java.util.logging.FileHandler
# can be overriden by a facility specific level # can be overriden by a facility specific level
# Note that the ConsoleHandler also has a separate level # Note that the ConsoleHandler also has a separate level
# setting to limit messages printed to the console. # setting to limit messages printed to the console.
.level= SEVERE .level= INFO
############################################################ ############################################################
# Handler specific properties. # Handler specific properties.
@@ -35,7 +35,7 @@ handlers= java.util.logging.FileHandler
# default file output is in user's home directory. # default file output is in user's home directory.
java.util.logging.FileHandler.pattern = MuWire.log java.util.logging.FileHandler.pattern = MuWire.log
java.util.logging.FileHandler.limit = 50000000 java.util.logging.FileHandler.limit = 150000000
java.util.logging.FileHandler.count = 1 java.util.logging.FileHandler.count = 1
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
@@ -60,3 +60,5 @@ java.util.logging.SimpleFormatter.format=%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$
# messages: # messages:
com.xyz.foo.level = SEVERE com.xyz.foo.level = SEVERE
com.muwire.core.level = FINE com.muwire.core.level = FINE
net.i2p.client.streaming.impl.level = FINE
net.i2p.client.impl.level = FINE