Compare commits

...

31 Commits

Author SHA1 Message Date
Zlatin Balevsky
e829e09b13 fix piece count calculation 2019-06-24 18:22:46 +01:00
Zlatin Balevsky
120980100b log mesh creation 2019-06-24 18:11:55 +01:00
Zlatin Balevsky
e64ed2c89d fix method name 2019-06-24 18:03:22 +01:00
Zlatin Balevsky
81436aaa66 fix name 2019-06-24 17:59:39 +01:00
Zlatin Balevsky
3bb530e34f more logging 2019-06-24 17:50:07 +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
24 changed files with 218 additions and 40 deletions

View File

@@ -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.3.8")
} 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.3.8")
} 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

@@ -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
@@ -48,6 +50,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
@@ -124,6 +127,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 +173,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)
eventBus.register(SourceDiscoveredEvent.class, meshManager) eventBus.register(SourceDiscoveredEvent.class, meshManager)
log.info "initializing persistence service" log.info "initializing persistence service"
@@ -215,7 +219,9 @@ 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)
@@ -291,7 +297,7 @@ public class Core {
} }
} }
Core core = new Core(props, home, "0.3.5") Core core = new Core(props, home, "0.3.8")
core.startServices() core.startServices()
// ... at the end, sleep or execute script // ... at the end, sleep or execute script

View File

@@ -30,8 +30,8 @@ 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"))
watchedDirectories = new HashSet<>() watchedDirectories = new HashSet<>()

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,8 +127,12 @@ 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.download() downloader.readPieces()
if (!downloader.paused)
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

@@ -1,18 +1,34 @@
package com.muwire.core.mesh package com.muwire.core.mesh
import java.util.logging.Level
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.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 groovy.util.logging.Log
import net.i2p.data.Base64
@Log
class MeshManager { class MeshManager {
private static final int EXPIRATION = 60 * 60 * 1000
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
MeshManager(FileManager fileManager) { MeshManager(FileManager fileManager, File home) {
this.fileManager = fileManager this.fileManager = fileManager
this.home = home
load()
} }
Mesh get(InfoHash infoHash) { Mesh get(InfoHash infoHash) {
@@ -23,6 +39,7 @@ class MeshManager {
synchronized(meshes) { synchronized(meshes) {
if (meshes.containsKey(infoHash)) if (meshes.containsKey(infoHash))
return meshes.get(infoHash) return meshes.get(infoHash)
log.log(Level.INFO,"creating mesh with pieces:$nPieces", new Exception())
Pieces pieces = new Pieces(nPieces, Constants.DOWNLOAD_SEQUENTIAL_RATIO) Pieces pieces = new Pieces(nPieces, Constants.DOWNLOAD_SEQUENTIAL_RATIO)
if (fileManager.rootToFiles.containsKey(infoHash)) { if (fileManager.rootToFiles.containsKey(infoHash)) {
for (int i = 0; i < nPieces; i++) for (int i = 0; i < nPieces; i++)
@@ -38,6 +55,51 @@ class MeshManager {
Mesh mesh = meshes.get(e.infoHash) Mesh mesh = meshes.get(e.infoHash)
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 > EXPIRATION)
return
InfoHash infoHash = new InfoHash(Base64.decode(json.infoHash))
Pieces pieces = new Pieces(json.nPieces, Constants.DOWNLOAD_SEQUENTIAL_RATIO)
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

@@ -12,8 +12,10 @@ import com.muwire.core.connection.Endpoint
import com.muwire.core.mesh.Mesh import com.muwire.core.mesh.Mesh
import com.muwire.core.util.DataUtil import com.muwire.core.util.DataUtil
import groovy.util.logging.Log
import net.i2p.data.Destination import net.i2p.data.Destination
@Log
class ContentUploader extends Uploader { class ContentUploader extends Uploader {
private final File file private final File file
@@ -42,8 +44,10 @@ class ContentUploader extends Uploader {
int endPiece = range.end / (0x1 << pieceSize) int endPiece = range.end / (0x1 << pieceSize)
for (int i = startPiece; i <= endPiece; i++) for (int i = startPiece; i <= endPiece; i++)
satisfiable &= mesh.pieces.isDownloaded(i) satisfiable &= mesh.pieces.isDownloaded(i)
log.info("requested range $range.start-$range.end startPiece:$startPiece endPiece:$endPiece satisfiable:$satisfiable my pieces: ${mesh.pieces.getDownloaded()}")
} }
if (!satisfiable) { if (!satisfiable) {
log.info("416 range not satisfiable")
os.write("416 Range Not Satisfiable\r\n".getBytes(StandardCharsets.US_ASCII)) os.write("416 Range Not Satisfiable\r\n".getBytes(StandardCharsets.US_ASCII))
writeMesh(request.downloader) writeMesh(request.downloader)
os.write("\r\n".getBytes(StandardCharsets.US_ASCII)) os.write("\r\n".getBytes(StandardCharsets.US_ASCII))
@@ -51,6 +55,8 @@ class ContentUploader extends Uploader {
return return
} }
log.info("200 ok")
os.write("200 OK\r\n".getBytes(StandardCharsets.US_ASCII)) os.write("200 OK\r\n".getBytes(StandardCharsets.US_ASCII))
os.write("Content-Range: $range.start-$range.end\r\n".getBytes(StandardCharsets.US_ASCII)) os.write("Content-Range: $range.start-$range.end\r\n".getBytes(StandardCharsets.US_ASCII))
writeMesh(request.downloader) writeMesh(request.downloader)

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.3.8
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
@@ -164,6 +166,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

@@ -77,9 +77,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 +93,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 {

View File

@@ -11,6 +11,7 @@ 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
@@ -67,6 +68,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 +138,7 @@ 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)
timer.schedule({ timer.schedule({
if (core.shutdown.get()) if (core.shutdown.get())
@@ -166,6 +170,8 @@ class MainFrameModel {
watched.addAll(core.muOptions.watchedDirectories) watched.addAll(core.muOptions.watchedDirectories)
builder.getVariable("watched-directories-table").model.fireTableDataChanged() builder.getVariable("watched-directories-table").model.fireTableDataChanged()
watched.each { core.eventBus.publish(new FileSharedEvent(file : new File(it))) } watched.each { core.eventBus.publish(new FileSharedEvent(file : new File(it))) }
resumeButtonText = "Retry"
} }
}) })
@@ -339,6 +345,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

@@ -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)
} }
} }
} }
@@ -295,6 +296,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 +307,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 +433,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 +473,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 +486,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)
} }

View File

@@ -89,16 +89,16 @@ 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))
} }
buttonsPanel = builder.panel { buttonsPanel = builder.panel {
gridBagLayout() gridBagLayout()

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

@@ -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