Compare commits

..

15 Commits

Author SHA1 Message Date
Zlatin Balevsky
f5e1833a48 Release 0.4.4 2019-06-30 15:55:23 +01:00
Zlatin Balevsky
9feb2a3c8f fix NPE on update search 2019-06-30 15:11:13 +01:00
Zlatin Balevsky
b27665f5dd Merge pull request #5 from 0rC0/patch-1
code markdown for commands and paths in README.md
2019-06-30 13:45:36 +01:00
orco
4465aa4134 code markdown for commands and paths in README.md
... instead of quotes
2019-06-30 14:27:33 +02:00
Zlatin Balevsky
ad766ac748 try to unmap files when done 2019-06-30 13:20:26 +01:00
Zlatin Balevsky
d9e7d67d86 javadoc 2019-06-30 12:51:34 +01:00
Zlatin Balevsky
3fefbc94b3 utility to decode personas 2019-06-30 10:41:42 +01:00
Zlatin Balevsky
21034209a5 add ? to split pattern 2019-06-30 06:29:46 +01:00
Zlatin Balevsky
7c04c0f83c unshare individual file 2019-06-30 05:44:08 +01:00
Zlatin Balevsky
f5293d65dd update todo 2019-06-29 16:00:49 +01:00
Zlatin Balevsky
8191bf6066 Release 0.4.3 2019-06-29 10:44:15 +01:00
Zlatin Balevsky
29b6bfd463 support different update types 2019-06-29 10:31:27 +01:00
Zlatin Balevsky
2f3d23bc34 fixes 2019-06-29 10:12:50 +01:00
Zlatin Balevsky
98dd80c4b8 fix 2019-06-29 10:03:58 +01:00
Zlatin Balevsky
d9edb2e128 ability to download updates automatically 2019-06-29 09:23:27 +01:00
22 changed files with 198 additions and 33 deletions

View File

@@ -23,7 +23,7 @@ Some of the UI tests will fail because they haven't been written yet :-/
### Running ### Running
You need to have an I2P router up and running on the same machine. After you build the application, look inside "gui/build/distributions". Untar/unzip one of the "shadow" files and then run the jar contained inside by typing "java -jar MuWire-x.y.z.jar" in a terminal or command prompt. If you use a custom I2CP host and port, create a file $HOME/.MuWire/i2p.properties and put "i2cp.tcp.host=<host>" and "i2cp.tcp.port=<port>" in there. You need to have an I2P router up and running on the same machine. After you build the application, look inside `gui/build/distributions`. Untar/unzip one of the `shadow` files and then run the jar contained inside by typing `java -jar MuWire-x.y.z.jar` in a terminal or command prompt. If you use a custom I2CP host and port, create a file `$HOME/.MuWire/i2p.properties` and put `i2cp.tcp.host=<host>` and `i2cp.tcp.port=<port>` in there.
The first time you run MuWire it will ask you to select a nickname. This nickname will be displayed with search results, so that others can verify the file was shared by you. It is best to leave MuWire running all the time, just like I2P. The first time you run MuWire it will ask you to select a nickname. This nickname will be displayed with search results, so that others can verify the file was shared by you. It is best to leave MuWire running all the time, just like I2P.

View File

@@ -20,10 +20,6 @@ For helping users make better decisions whom to trust
To allow every user to not route queries for content they do not like. This is mostly GUI work, the backend part is simple To allow every user to not route queries for content they do not like. This is mostly GUI work, the backend part is simple
##### Packaging With JRE, Embedded Router
For ease of deployment for new users, and so that users do not need to run a separate I2P router
##### Web UI, REST Interface, etc. ##### Web UI, REST Interface, etc.
Basically any non-gui non-cli user interface Basically any non-gui non-cli user interface
@@ -36,5 +32,5 @@ To enable parsing of metadata from known file types and the user editing it or a
* 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 (half done)
* Multiple-selection download, Ctrl-A * Multiple-selection download, Ctrl-A

View File

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

View File

@@ -53,7 +53,7 @@ class CliDownloader {
Core core Core core
try { try {
core = new Core(props, home, "0.4.2") core = new Core(props, home, "0.4.4")
} 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

@@ -9,5 +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 String SPLIT_PATTERN = "[\\*\\+\\-,\\.:;\\(\\)=_/\\\\\\!\\\"\\\'\\\$%\\|\\[\\]\\{\\}]" public static final String SPLIT_PATTERN = "[\\*\\+\\-,\\.:;\\(\\)=_/\\\\\\!\\\"\\\'\\\$%\\|\\[\\]\\{\\}\\?]"
} }

View File

@@ -39,6 +39,7 @@ import com.muwire.core.search.ResultsEvent
import com.muwire.core.search.ResultsSender import com.muwire.core.search.ResultsSender
import com.muwire.core.search.SearchEvent import com.muwire.core.search.SearchEvent
import com.muwire.core.search.SearchManager import com.muwire.core.search.SearchManager
import com.muwire.core.search.UIResultBatchEvent
import com.muwire.core.trust.TrustEvent import com.muwire.core.trust.TrustEvent
import com.muwire.core.trust.TrustService import com.muwire.core.trust.TrustService
import com.muwire.core.update.UpdateClient import com.muwire.core.update.UpdateClient
@@ -233,7 +234,9 @@ public class Core {
cacheClient = new CacheClient(eventBus,hostCache, connectionManager, i2pSession, props, 10000) cacheClient = new CacheClient(eventBus,hostCache, connectionManager, i2pSession, props, 10000)
log.info("initializing update client") log.info("initializing update client")
updateClient = new UpdateClient(eventBus, i2pSession, myVersion, props) updateClient = new UpdateClient(eventBus, i2pSession, myVersion, props, fileManager, me)
eventBus.register(FileDownloadedEvent.class, updateClient)
eventBus.register(UIResultBatchEvent.class, updateClient)
log.info("initializing connector") log.info("initializing connector")
I2PConnector i2pConnector = new I2PConnector(socketManager) I2PConnector i2pConnector = new I2PConnector(socketManager)
@@ -349,7 +352,7 @@ public class Core {
} }
} }
Core core = new Core(props, home, "0.4.2") Core core = new Core(props, home, "0.4.4")
core.startServices() core.startServices()
// ... at the end, sleep or execute script // ... at the end, sleep or execute script

View File

@@ -13,6 +13,8 @@ class MuWireSettings {
boolean allowUntrusted boolean allowUntrusted
int downloadRetryInterval int downloadRetryInterval
int updateCheckInterval int updateCheckInterval
boolean autoDownloadUpdate
String updateType
String nickname String nickname
File downloadLocation File downloadLocation
CrawlerResponse crawlerResponse CrawlerResponse crawlerResponse
@@ -37,6 +39,8 @@ class MuWireSettings {
System.getProperty("user.home"))) System.getProperty("user.home")))
downloadRetryInterval = Integer.parseInt(props.getProperty("downloadRetryInterval","1")) downloadRetryInterval = Integer.parseInt(props.getProperty("downloadRetryInterval","1"))
updateCheckInterval = Integer.parseInt(props.getProperty("updateCheckInterval","24")) updateCheckInterval = Integer.parseInt(props.getProperty("updateCheckInterval","24"))
autoDownloadUpdate = Boolean.parseBoolean(props.getProperty("autoDownloadUpdate","true"))
updateType = props.getProperty("updateType","jar")
shareDownloadedFiles = Boolean.parseBoolean(props.getProperty("shareDownloadedFiles","true")) shareDownloadedFiles = Boolean.parseBoolean(props.getProperty("shareDownloadedFiles","true"))
downloadSequentialRatio = Float.valueOf(props.getProperty("downloadSequentialRatio","0.8")) downloadSequentialRatio = Float.valueOf(props.getProperty("downloadSequentialRatio","0.8"))
hostClearInterval = Integer.valueOf(props.getProperty("hostClearInterval","60")) hostClearInterval = Integer.valueOf(props.getProperty("hostClearInterval","60"))
@@ -62,6 +66,8 @@ class MuWireSettings {
props.setProperty("downloadLocation", downloadLocation.getAbsolutePath()) props.setProperty("downloadLocation", downloadLocation.getAbsolutePath())
props.setProperty("downloadRetryInterval", String.valueOf(downloadRetryInterval)) props.setProperty("downloadRetryInterval", String.valueOf(downloadRetryInterval))
props.setProperty("updateCheckInterval", String.valueOf(updateCheckInterval)) props.setProperty("updateCheckInterval", String.valueOf(updateCheckInterval))
props.setProperty("autoDownloadUpdate", String.valueOf(autoDownloadUpdate))
props.setProperty("updateType",updateType)
props.setProperty("shareDownloadedFiles", String.valueOf(shareDownloadedFiles)) props.setProperty("shareDownloadedFiles", String.valueOf(shareDownloadedFiles))
props.setProperty("downloadSequentialRatio", String.valueOf(downloadSequentialRatio)) props.setProperty("downloadSequentialRatio", String.valueOf(downloadSequentialRatio))
props.setProperty("hostClearInterval", String.valueOf(hostClearInterval)) props.setProperty("hostClearInterval", String.valueOf(hostClearInterval))

View File

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

View File

@@ -198,6 +198,7 @@ class DownloadSession {
mapped.clear() mapped.clear()
digest.update(mapped) digest.update(mapped)
DataUtil.tryUnmap(mapped)
byte [] hash = digest.digest() byte [] hash = digest.digest()
byte [] expected = new byte[32] byte [] expected = new byte[32]
System.arraycopy(infoHash.getHashList(), piece * 32, expected, 0, 32) System.arraycopy(infoHash.getHashList(), piece * 32, expected, 0, 32)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
group = com.muwire group = com.muwire
version = 0.4.2 version = 0.4.4
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

@@ -14,12 +14,14 @@ import javax.inject.Inject
import com.muwire.core.Constants import com.muwire.core.Constants
import com.muwire.core.Core import com.muwire.core.Core
import com.muwire.core.SharedFile
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.UIDownloadPausedEvent
import com.muwire.core.download.UIDownloadResumedEvent import com.muwire.core.download.UIDownloadResumedEvent
import com.muwire.core.files.DirectoryUnsharedEvent import com.muwire.core.files.DirectoryUnsharedEvent
import com.muwire.core.files.FileUnsharedEvent
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
@@ -33,6 +35,8 @@ class MainFrameController {
@MVCMember @Nonnull @MVCMember @Nonnull
MainFrameModel model MainFrameModel model
@MVCMember @Nonnull
MainFrameView view
private volatile Core core private volatile Core core
@@ -206,8 +210,11 @@ class MainFrameController {
markTrust("trusted-table", TrustLevel.NEUTRAL, model.trusted) markTrust("trusted-table", TrustLevel.NEUTRAL, model.trusted)
} }
void unshareSelectedFiles() { void unshareSelectedFile() {
println "unsharing selected files" SharedFile sf = view.selectedSharedFile()
if (sf == null)
return
core.eventBus.publish(new FileUnsharedEvent(unsharedFile : sf))
} }
void stopWatchingDirectory() { void stopWatchingDirectory() {

View File

@@ -70,6 +70,10 @@ class OptionsController {
model.updateCheckInterval = text model.updateCheckInterval = text
settings.updateCheckInterval = Integer.valueOf(text) settings.updateCheckInterval = Integer.valueOf(text)
boolean autoDownloadUpdate = view.autoDownloadUpdateCheckbox.model.isSelected()
model.autoDownloadUpdate = autoDownloadUpdate
settings.autoDownloadUpdate = autoDownloadUpdate
boolean onlyTrusted = view.allowUntrustedCheckbox.model.isSelected() boolean onlyTrusted = view.allowUntrustedCheckbox.model.isSelected()
model.onlyTrusted = onlyTrusted model.onlyTrusted = onlyTrusted
settings.setAllowUntrusted(!onlyTrusted) settings.setAllowUntrusted(!onlyTrusted)

View File

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

View File

@@ -29,6 +29,7 @@ import com.muwire.core.search.UIResultEvent
import com.muwire.core.trust.TrustEvent import com.muwire.core.trust.TrustEvent
import com.muwire.core.trust.TrustService import com.muwire.core.trust.TrustService
import com.muwire.core.update.UpdateAvailableEvent import com.muwire.core.update.UpdateAvailableEvent
import com.muwire.core.update.UpdateDownloadedEvent
import com.muwire.core.upload.UploadEvent import com.muwire.core.upload.UploadEvent
import com.muwire.core.upload.UploadFinishedEvent import com.muwire.core.upload.UploadFinishedEvent
@@ -143,6 +144,7 @@ class MainFrameModel {
core.eventBus.register(FileUnsharedEvent.class, this) core.eventBus.register(FileUnsharedEvent.class, this)
core.eventBus.register(RouterDisconnectedEvent.class, this) core.eventBus.register(RouterDisconnectedEvent.class, this)
core.eventBus.register(AllFilesLoadedEvent.class, this) core.eventBus.register(AllFilesLoadedEvent.class, this)
core.eventBus.register(UpdateDownloadedEvent.class, this)
timer.schedule({ timer.schedule({
if (core.shutdown.get()) if (core.shutdown.get())
@@ -185,6 +187,14 @@ class MainFrameModel {
watched.each { core.eventBus.publish(new FileSharedEvent(file : new File(it))) } watched.each { core.eventBus.publish(new FileSharedEvent(file : new File(it))) }
} }
} }
void onUpdateDownloadedEvent(UpdateDownloadedEvent e) {
runInsideUIAsync {
JOptionPane.showMessageDialog(null, "MuWire $e.version has been downloaded. You can update now",
"Update Downloaded", JOptionPane.INFORMATION_MESSAGE)
}
}
void onUIResultEvent(UIResultEvent e) { void onUIResultEvent(UIResultEvent e) {
MVCGroup resultsGroup = results.get(e.uuid) MVCGroup resultsGroup = results.get(e.uuid)
resultsGroup?.model.handleResult(e) resultsGroup?.model.handleResult(e)
@@ -192,7 +202,7 @@ class MainFrameModel {
void onUIResultBatchEvent(UIResultBatchEvent e) { void onUIResultBatchEvent(UIResultBatchEvent e) {
MVCGroup resultsGroup = results.get(e.uuid) MVCGroup resultsGroup = results.get(e.uuid)
resultsGroup?.model.handleResultBatch(e.results) resultsGroup?.model?.handleResultBatch(e.results)
} }
void onDownloadStartedEvent(DownloadStartedEvent e) { void onDownloadStartedEvent(DownloadStartedEvent e) {

View File

@@ -11,6 +11,7 @@ import griffon.metadata.ArtifactProviderFor
class OptionsModel { class OptionsModel {
@Observable String downloadRetryInterval @Observable String downloadRetryInterval
@Observable String updateCheckInterval @Observable String updateCheckInterval
@Observable boolean autoDownloadUpdate
@Observable boolean onlyTrusted @Observable boolean onlyTrusted
@Observable boolean shareDownloadedFiles @Observable boolean shareDownloadedFiles
@Observable String downloadLocation @Observable String downloadLocation
@@ -40,6 +41,7 @@ class OptionsModel {
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
autoDownloadUpdate = settings.autoDownloadUpdate
onlyTrusted = !settings.allowUntrusted() onlyTrusted = !settings.allowUntrusted()
shareDownloadedFiles = settings.shareDownloadedFiles shareDownloadedFiles = settings.shareDownloadedFiles
downloadLocation = settings.downloadLocation.getAbsolutePath() downloadLocation = settings.downloadLocation.getAbsolutePath()

View File

@@ -367,11 +367,12 @@ class MainFrameView {
sharedFilesTable.rowSorter.setSortsOnUpdates(true) sharedFilesTable.rowSorter.setSortsOnUpdates(true)
JPopupMenu sharedFilesMenu = new JPopupMenu() JPopupMenu sharedFilesMenu = new JPopupMenu()
// JMenuItem unshareSelectedFiles = new JMenuItem("Unshare selected files")
// unshareSelectedFiles.addActionListener({mvcGroup.controller.unshareSelectedFiles()})
JMenuItem copyHashToClipboard = new JMenuItem("Copy hash to clipboard") JMenuItem copyHashToClipboard = new JMenuItem("Copy hash to clipboard")
copyHashToClipboard.addActionListener({mvcGroup.view.copyHashToClipboard(sharedFilesTable)}) copyHashToClipboard.addActionListener({mvcGroup.view.copyHashToClipboard()})
sharedFilesMenu.add(copyHashToClipboard) sharedFilesMenu.add(copyHashToClipboard)
JMenuItem unshareSelectedFiles = new JMenuItem("Unshare selected files")
unshareSelectedFiles.addActionListener({mvcGroup.controller.unshareSelectedFile()})
sharedFilesMenu.add(unshareSelectedFiles)
sharedFilesTable.addMouseListener(new MouseAdapter() { sharedFilesTable.addMouseListener(new MouseAdapter() {
@Override @Override
public void mouseReleased(MouseEvent e) { public void mouseReleased(MouseEvent e) {
@@ -431,13 +432,21 @@ class MainFrameView {
menu.show(event.getComponent(), event.getX(), event.getY()) menu.show(event.getComponent(), event.getX(), event.getY())
} }
def copyHashToClipboard(JTable sharedFilesTable) { def selectedSharedFile() {
def sharedFilesTable = builder.getVariable("shared-files-table")
int selected = sharedFilesTable.getSelectedRow() int selected = sharedFilesTable.getSelectedRow()
if (selected < 0) if (selected < 0)
return return null
if (lastSharedSortEvent != null) if (lastSharedSortEvent != null)
selected = sharedFilesTable.rowSorter.convertRowIndexToModel(selected) selected = sharedFilesTable.rowSorter.convertRowIndexToModel(selected)
String root = Base64.encode(model.shared[selected].infoHash.getRoot()) model.shared[selected]
}
def copyHashToClipboard() {
def selected = selectedSharedFile()
if (selected == null)
return
String root = Base64.encode(selected.infoHash.getRoot())
StringSelection selection = new StringSelection(root) StringSelection selection = new StringSelection(root)
def clipboard = Toolkit.getDefaultToolkit().getSystemClipboard() def clipboard = Toolkit.getDefaultToolkit().getSystemClipboard()
clipboard.setContents(selection, null) clipboard.setContents(selection, null)

View File

@@ -32,6 +32,7 @@ class OptionsView {
def retryField def retryField
def updateField def updateField
def autoDownloadUpdateCheckbox
def allowUntrustedCheckbox def allowUntrustedCheckbox
def shareDownloadedCheckbox def shareDownloadedCheckbox
@@ -71,16 +72,19 @@ class OptionsView {
label(text : "Check for updates every", constraints : gbc(gridx : 0, gridy: 1)) label(text : "Check for updates every", constraints : gbc(gridx : 0, gridy: 1))
updateField = textField(text : bind {model.updateCheckInterval }, columns : 2, constraints : gbc(gridx : 1, gridy: 1)) updateField = textField(text : bind {model.updateCheckInterval }, columns : 2, constraints : gbc(gridx : 1, gridy: 1))
label(text : "hours", constraints : gbc(gridx: 2, gridy : 1)) label(text : "hours", constraints : gbc(gridx: 2, gridy : 1))
label(text : "Download updates automatically", constraints: gbc(gridx :0, gridy : 2))
autoDownloadUpdateCheckbox = checkBox(selected : bind {model.autoDownloadUpdate}, constraints : gbc(gridx:1, gridy : 2))
label(text : "Allow only trusted connections", constraints : gbc(gridx: 0, gridy : 2)) label(text : "Allow only trusted connections", constraints : gbc(gridx: 0, gridy : 3))
allowUntrustedCheckbox = checkBox(selected : bind {model.onlyTrusted}, constraints : gbc(gridx: 1, gridy : 2)) allowUntrustedCheckbox = checkBox(selected : bind {model.onlyTrusted}, constraints : gbc(gridx: 1, gridy : 3))
label(text : "Share downloaded files", constraints : gbc(gridx : 0, gridy:3)) label(text : "Share downloaded files", constraints : gbc(gridx : 0, gridy:4))
shareDownloadedCheckbox = checkBox(selected : bind {model.shareDownloadedFiles}, constraints : gbc(gridx :1, gridy:3)) shareDownloadedCheckbox = checkBox(selected : bind {model.shareDownloadedFiles}, constraints : gbc(gridx :1, gridy:4))
label(text : "Save downloaded files to:", constraints: gbc(gridx:0, gridy:4)) label(text : "Save downloaded files to:", constraints: gbc(gridx:0, gridy:5))
button(text : "Choose", constraints : gbc(gridx : 1, gridy:4), downloadLocationAction) button(text : "Choose", constraints : gbc(gridx : 1, gridy:5), downloadLocationAction)
label(text : bind {model.downloadLocation}, constraints: gbc(gridx:0, gridy:5, gridwidth:2)) label(text : bind {model.downloadLocation}, constraints: gbc(gridx:0, gridy:6, gridwidth:2))
} }
i = builder.panel { i = builder.panel {