From 7c8d64b462153cddde2a2291bb8d0fa663126bc7 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Mon, 1 Jul 2019 21:40:07 +0100 Subject: [PATCH 01/17] start work on sharing of trust lists --- .../com/muwire/core/MuWireSettings.groovy | 3 ++ .../core/connection/ConnectionAcceptor.groovy | 43 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy b/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy index 43509aa3..c7980b8a 100644 --- a/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy +++ b/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy @@ -11,6 +11,7 @@ class MuWireSettings { final boolean isLeaf boolean allowUntrusted + boolean allowTrustLists int downloadRetryInterval int updateCheckInterval boolean autoDownloadUpdate @@ -33,6 +34,7 @@ class MuWireSettings { MuWireSettings(Properties props) { isLeaf = Boolean.valueOf(props.get("leaf","false")) allowUntrusted = Boolean.valueOf(props.get("allowUntrusted","true")) + allowTrustLists = Boolean.valueOf(props.get("allowTrustLists","true")) crawlerResponse = CrawlerResponse.valueOf(props.get("crawlerResponse","REGISTERED")) nickname = props.getProperty("nickname","MuWireUser") downloadLocation = new File((String)props.getProperty("downloadLocation", @@ -61,6 +63,7 @@ class MuWireSettings { Properties props = new Properties() props.setProperty("leaf", isLeaf.toString()) props.setProperty("allowUntrusted", allowUntrusted.toString()) + props.setProperty("allowTrustLists", String.valueOf(allowTrustLists)) props.setProperty("crawlerResponse", crawlerResponse.toString()) props.setProperty("nickname", nickname) props.setProperty("downloadLocation", downloadLocation.getAbsolutePath()) diff --git a/core/src/main/groovy/com/muwire/core/connection/ConnectionAcceptor.groovy b/core/src/main/groovy/com/muwire/core/connection/ConnectionAcceptor.groovy index 0b686af9..7b2f0746 100644 --- a/core/src/main/groovy/com/muwire/core/connection/ConnectionAcceptor.groovy +++ b/core/src/main/groovy/com/muwire/core/connection/ConnectionAcceptor.groovy @@ -14,6 +14,7 @@ import com.muwire.core.hostcache.HostCache import com.muwire.core.trust.TrustLevel import com.muwire.core.trust.TrustService import com.muwire.core.upload.UploadManager +import com.muwire.core.util.DataUtil import com.muwire.core.search.InvalidSearchResultException import com.muwire.core.search.ResultsParser import com.muwire.core.search.SearchManager @@ -124,6 +125,9 @@ class ConnectionAcceptor { break case (byte)'P': processPOST(e) + break + case (byte)'T': + processTRUST(e) break default: throw new Exception("Invalid read $read") @@ -242,5 +246,44 @@ class ConnectionAcceptor { e.close() } } + + private void processTRUST(Endpoint e) { + byte[] RUST = new byte[6] + DataInputStream dis = new DataInputStream(e.getInputStream()) + dis.readFully(RUST) + if (RUST != "RUST\r\n".getBytes(StandardCharsets.US_ASCII)) + throw new IOException("Invalid TRUST connection") + String header + while ((header = DataUtil.readTillRN(dis)) != ""); // ignore headers for now + + OutputStream os = e.getOutputStream() + if (!settings.allowTrustLists) { + os.write("403 Not Allowed\r\n\r\n".getBytes(StandardCharsets.US_ASCII)) + os.flush() + e.close() + return + } + + os.write("200 OK\r\n\r\n".getBytes(StandardCharsets.US_ASCII)) + List good = new ArrayList<>(trustService.good.values()) + int size = Math.min(Short.MAX_VALUE * 2, good.size()) + good = good.subList(0, size) + DataOutputStream dos = new DataOutputStream(os) + dos.writeShort(size) + good.each { + it.write(dos) + } + + List bad = new ArrayList<>(trustService.bad.values()) + size = Math.min(Short.MAX_VALUE * 2, bad.size()) + bad = bad.subList(0, size) + dos.writeShort(size) + bad.each { + it.write(dos) + } + + dos.flush() + e.close() + } } From 3ec9654d3cbccd0c143eeeb3ceef8d8171fee8f9 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Mon, 1 Jul 2019 22:05:43 +0100 Subject: [PATCH 02/17] start work on sharing of trust lists --- .../com/muwire/core/MuWireSettings.groovy | 21 +++++++++++++++++-- .../muwire/core/trust/TrustSubscriber.groovy | 18 ++++++++++++++++ .../core/trust/TrustSubscriptionEvent.groovy | 9 ++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy create mode 100644 core/src/main/groovy/com/muwire/core/trust/TrustSubscriptionEvent.groovy diff --git a/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy b/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy index c7980b8a..4da24897 100644 --- a/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy +++ b/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy @@ -12,6 +12,8 @@ class MuWireSettings { final boolean isLeaf boolean allowUntrusted boolean allowTrustLists + int trustListInterval + Set trustSubscriptions int downloadRetryInterval int updateCheckInterval boolean autoDownloadUpdate @@ -33,8 +35,9 @@ class MuWireSettings { MuWireSettings(Properties props) { isLeaf = Boolean.valueOf(props.get("leaf","false")) - allowUntrusted = Boolean.valueOf(props.get("allowUntrusted","true")) - allowTrustLists = Boolean.valueOf(props.get("allowTrustLists","true")) + allowUntrusted = Boolean.valueOf(props.getProperty("allowUntrusted","true")) + allowTrustLists = Boolean.valueOf(props.getProperty("allowTrustLists","true")) + trustListInterval = Integer.valueOf(props.getProperty("trustListInterval","1")) crawlerResponse = CrawlerResponse.valueOf(props.get("crawlerResponse","REGISTERED")) nickname = props.getProperty("nickname","MuWireUser") downloadLocation = new File((String)props.getProperty("downloadLocation", @@ -57,6 +60,12 @@ class MuWireSettings { encoded.each { watchedDirectories << DataUtil.readi18nString(Base64.decode(it)) } } + trustSubscriptions = new HashSet<>() + if (props.containsKey("trustSubscriptions")) { + props.getProperty("trustSubscriptions").split(",").each { + trustSubscriptions.add(new Persona(new ByteArrayInputStream(Base64.decode(it)))) + } + } } void write(OutputStream out) throws IOException { @@ -64,6 +73,7 @@ class MuWireSettings { props.setProperty("leaf", isLeaf.toString()) props.setProperty("allowUntrusted", allowUntrusted.toString()) props.setProperty("allowTrustLists", String.valueOf(allowTrustLists)) + props.setProperty("trustListInterval", String.valueOf(trustListInterval)) props.setProperty("crawlerResponse", crawlerResponse.toString()) props.setProperty("nickname", nickname) props.setProperty("downloadLocation", downloadLocation.getAbsolutePath()) @@ -86,6 +96,13 @@ class MuWireSettings { props.setProperty("watchedDirectories", encoded) } + if (!trustSubscriptions.isEmpty()) { + String encoded = trustSubscriptions.stream(). + map(it.toBase64()). + collect(Collectors.joining(",")) + props.setProperty("trustSubscriptions", encoded) + } + props.store(out, "") } diff --git a/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy b/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy new file mode 100644 index 00000000..2ee71361 --- /dev/null +++ b/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy @@ -0,0 +1,18 @@ +package com.muwire.core.trust + +import java.util.concurrent.ConcurrentHashMap + +import com.muwire.core.EventBus +import com.muwire.core.MuWireSettings +import com.muwire.core.connection.I2PConnector + +import net.i2p.data.Destination + +class TrustSubscriber { + private final EventBus eventBus + private final I2PConnector i2pConnector + private final MuWireSettings settings + + private final Map lastRequestTime = new ConcurrentHashMap<>() + +} diff --git a/core/src/main/groovy/com/muwire/core/trust/TrustSubscriptionEvent.groovy b/core/src/main/groovy/com/muwire/core/trust/TrustSubscriptionEvent.groovy new file mode 100644 index 00000000..42bb67c0 --- /dev/null +++ b/core/src/main/groovy/com/muwire/core/trust/TrustSubscriptionEvent.groovy @@ -0,0 +1,9 @@ +package com.muwire.core.trust + +import com.muwire.core.Event +import com.muwire.core.Persona + +class TrustSubscriptionEvent extends Event { + Persona persona + boolean subscribe +} From 5d0fcb70276ae1c93dcf9f88f13ba9d258e1a067 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Mon, 1 Jul 2019 23:15:13 +0100 Subject: [PATCH 03/17] start work on sharing of trust lists --- .../muwire/core/trust/RemoteTrustList.groovy | 19 ++++++ .../muwire/core/trust/TrustSubscriber.groovy | 60 ++++++++++++++++++- .../TrustSubscriptionUpdatedEvent.groovy | 6 ++ 3 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 core/src/main/groovy/com/muwire/core/trust/RemoteTrustList.groovy create mode 100644 core/src/main/groovy/com/muwire/core/trust/TrustSubscriptionUpdatedEvent.groovy diff --git a/core/src/main/groovy/com/muwire/core/trust/RemoteTrustList.groovy b/core/src/main/groovy/com/muwire/core/trust/RemoteTrustList.groovy new file mode 100644 index 00000000..8d4de960 --- /dev/null +++ b/core/src/main/groovy/com/muwire/core/trust/RemoteTrustList.groovy @@ -0,0 +1,19 @@ +package com.muwire.core.trust + +import java.util.concurrent.ConcurrentHashMap + +import com.muwire.core.Persona + +import net.i2p.util.ConcurrentHashSet + +class RemoteTrustList { + private final Persona persona + private final Set good, bad + long timestamp + + RemoteTrustList(Persona persona) { + this.persona = persona + good = new ConcurrentHashSet<>() + bad = new ConcurrentHashSet<>() + } +} diff --git a/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy b/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy index 2ee71361..ae874fee 100644 --- a/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy +++ b/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy @@ -4,6 +4,7 @@ import java.util.concurrent.ConcurrentHashMap import com.muwire.core.EventBus import com.muwire.core.MuWireSettings +import com.muwire.core.UILoadedEvent import com.muwire.core.connection.I2PConnector import net.i2p.data.Destination @@ -13,6 +14,63 @@ class TrustSubscriber { private final I2PConnector i2pConnector private final MuWireSettings settings - private final Map lastRequestTime = new ConcurrentHashMap<>() + private final Map remoteTrustLists = new ConcurrentHashMap<>() + + private final Object waitLock = new Object() + private volatile boolean shutdown + private volatile Thread thread + TrustSubscriber(EventBus eventBus, I2PConnector i2pConnector, MuWireSettings settings) { + this.eventBus = eventBus + this.i2pConnector = i2pConnector + this.settings = settings + } + + void onUILoadedEvent(UILoadedEvent e) { + thread = new Thread({checkLoop()} as Runnable, "trust-subscriber") + thread.setDaemon(true) + thread.start() + } + + void stop() { + shutdown = true + thread?.interrupt() + } + + void onTrustSubscriptionEvent(TrustSubscriptionEvent e) { + if (!e.subscribe) { + settings.trustSubscriptions.remove(e.persona) + remoteTrustLists.remove(e.persona.destination) + } else { + settings.trustSubscriptions.add(e.persona) + RemoteTrustList trustList = remoteTrustLists.putIfAbsent(e.persona.destination, new RemoteTrustList(e.persona)) + trustList.timestamp = 0 + synchronized(waitLock) { + waitLock.notify() + } + } + } + + private void checkLoop() { + try { + while(!shutdown) { + synchronized(waitLock) { + waitLock.wait(60 * 1000) + } + final long now = System.currentTimeMillis() + remoteTrustLists.values().each { trustList -> + if (now - trustList.timestamp < settings.trustListInterval * 60 * 60 * 1000) + return + check(trustList, now) + } + } + } catch (InterruptedException e) { + if (!shutdown) + throw e + } + } + + private void check(RemoteTrustList trustList, long now) { + // TODO: fetch trustlist and update timestamp + } } diff --git a/core/src/main/groovy/com/muwire/core/trust/TrustSubscriptionUpdatedEvent.groovy b/core/src/main/groovy/com/muwire/core/trust/TrustSubscriptionUpdatedEvent.groovy new file mode 100644 index 00000000..cce44d75 --- /dev/null +++ b/core/src/main/groovy/com/muwire/core/trust/TrustSubscriptionUpdatedEvent.groovy @@ -0,0 +1,6 @@ +package com.muwire.core.trust + +import com.muwire.core.Event + +class TrustSubscriptionUpdatedEvent extends Event { +} From 7c54bd89665a9c340593e3b1abed33c86f62f16f Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Mon, 1 Jul 2019 23:33:39 +0100 Subject: [PATCH 04/17] start work on sharing of trust lists --- .../main/groovy/com/muwire/core/Core.groovy | 10 ++++ .../muwire/core/trust/TrustSubscriber.groovy | 57 ++++++++++++++++++- .../TrustSubscriptionUpdatedEvent.groovy | 1 + 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/core/src/main/groovy/com/muwire/core/Core.groovy b/core/src/main/groovy/com/muwire/core/Core.groovy index 4b2560f5..1a68cbe0 100644 --- a/core/src/main/groovy/com/muwire/core/Core.groovy +++ b/core/src/main/groovy/com/muwire/core/Core.groovy @@ -42,6 +42,8 @@ import com.muwire.core.search.SearchManager import com.muwire.core.search.UIResultBatchEvent import com.muwire.core.trust.TrustEvent import com.muwire.core.trust.TrustService +import com.muwire.core.trust.TrustSubscriber +import com.muwire.core.trust.TrustSubscriptionEvent import com.muwire.core.update.UpdateClient import com.muwire.core.upload.UploadManager import com.muwire.core.util.MuWireLogManager @@ -74,6 +76,7 @@ public class Core { final MuWireSettings muOptions private final TrustService trustService + private final TrustSubscriber trustSubscriber private final PersisterService persisterService private final HostCache hostCache private final ConnectionManager connectionManager @@ -280,6 +283,11 @@ public class Core { log.info("initializing hasher service") hasherService = new HasherService(new FileHasher(), eventBus, fileManager) eventBus.register(FileSharedEvent.class, hasherService) + + log.info("initializing trust subscriber") + trustSubscriber = new TrustSubscriber(eventBus, i2pConnector, props) + eventBus.register(UILoadedEvent.class, trustSubscriber) + eventBus.register(TrustSubscriptionEvent.class, trustSubscriber) } public void startServices() { @@ -300,6 +308,8 @@ public class Core { log.info("already shutting down") return } + log.info("shutting down trust subscriber") + trustSubscriber.stop() log.info("shutting down download manageer") downloadManager.shutdown() log.info("shutting down connection acceeptor") diff --git a/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy b/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy index ae874fee..fc8a6895 100644 --- a/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy +++ b/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy @@ -1,14 +1,21 @@ package com.muwire.core.trust +import java.nio.charset.StandardCharsets import java.util.concurrent.ConcurrentHashMap +import java.util.logging.Level import com.muwire.core.EventBus import com.muwire.core.MuWireSettings +import com.muwire.core.Persona import com.muwire.core.UILoadedEvent +import com.muwire.core.connection.Endpoint import com.muwire.core.connection.I2PConnector +import com.muwire.core.util.DataUtil +import groovy.util.logging.Log import net.i2p.data.Destination +@Log class TrustSubscriber { private final EventBus eventBus private final I2PConnector i2pConnector @@ -62,6 +69,7 @@ class TrustSubscriber { if (now - trustList.timestamp < settings.trustListInterval * 60 * 60 * 1000) return check(trustList, now) + eventBus.publish(new TrustSubscriptionUpdatedEvent(trustList : trustList)) } } } catch (InterruptedException e) { @@ -71,6 +79,53 @@ class TrustSubscriber { } private void check(RemoteTrustList trustList, long now) { - // TODO: fetch trustlist and update timestamp + log.info("fetching trust list from ${trustList.persona.getHumanReadableName()}") + Endpoint endpoint = null + try { + endpoint = i2pConnector.connect(trustList.persona.destination) + OutputStream os = endpoint.getOutputStream() + InputStream is = endpoint.getInputStream() + os.write("TRUST\r\n\r\n".getBytes(StandardCharsets.US_ASCII)) + os.flush() + + String codeString = DataUtil.readTillRN(is) + int space = codeString.indexOf(' ') + if (space > 0) + codeString = codeString.substring(0,space) + int code = Integer.parseInt(codeString.trim()) + + if (code != 200) { + log.info("couldn't fetch trust list, code $code") + return + } + + DataInputStream dis = new DataInputStream(is) + + Set good = new HashSet<>() + int nGood = dis.readUnsignedShort() + for (int i = 0; i < nGood; i++) { + Persona p = new Persona(dis) + good.add(p) + } + + Set bad = new HashSet<>() + int nBad = dis.readUnsignedShort() + for (int i = 0; i < nBad; i++) { + Persona p = new Persona(dis) + bad.add(p) + } + + trustList.timestamp = now + trustList.good.clear() + trustList.good.addAll(good) + trustList.bad.clear() + trustList.bad.addAll(bad) + + } catch (Exception e) { + log.log(Level.WARNING,"exception fetching trust list from ${trustList.persona.getHumanReadableName()}",e) + } finally { + endpoint?.close() + } + } } diff --git a/core/src/main/groovy/com/muwire/core/trust/TrustSubscriptionUpdatedEvent.groovy b/core/src/main/groovy/com/muwire/core/trust/TrustSubscriptionUpdatedEvent.groovy index cce44d75..25bbc70e 100644 --- a/core/src/main/groovy/com/muwire/core/trust/TrustSubscriptionUpdatedEvent.groovy +++ b/core/src/main/groovy/com/muwire/core/trust/TrustSubscriptionUpdatedEvent.groovy @@ -3,4 +3,5 @@ package com.muwire.core.trust import com.muwire.core.Event class TrustSubscriptionUpdatedEvent extends Event { + RemoteTrustList trustList } From 8b3d7527271555228546c6809e160943ced7930b Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 2 Jul 2019 08:59:30 +0100 Subject: [PATCH 05/17] add status to the trust list object --- .../com/muwire/core/trust/RemoteTrustList.groovy | 11 +++++++++++ .../com/muwire/core/trust/TrustSubscriber.groovy | 3 +++ 2 files changed, 14 insertions(+) diff --git a/core/src/main/groovy/com/muwire/core/trust/RemoteTrustList.groovy b/core/src/main/groovy/com/muwire/core/trust/RemoteTrustList.groovy index 8d4de960..b9c31611 100644 --- a/core/src/main/groovy/com/muwire/core/trust/RemoteTrustList.groovy +++ b/core/src/main/groovy/com/muwire/core/trust/RemoteTrustList.groovy @@ -7,13 +7,24 @@ import com.muwire.core.Persona import net.i2p.util.ConcurrentHashSet class RemoteTrustList { + public enum Status { NEW, UPDATING, UPDATED } + private final Persona persona private final Set good, bad long timestamp + Status status = Status.NEW RemoteTrustList(Persona persona) { this.persona = persona good = new ConcurrentHashSet<>() bad = new ConcurrentHashSet<>() } + + @Override + public boolean equals(Object o) { + if (!(o instanceof RemoteTrustList)) + return false + RemoteTrustList other = (RemoteTrustList)o + persona == other.persona + } } diff --git a/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy b/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy index fc8a6895..6af51a9f 100644 --- a/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy +++ b/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy @@ -68,7 +68,10 @@ class TrustSubscriber { remoteTrustLists.values().each { trustList -> if (now - trustList.timestamp < settings.trustListInterval * 60 * 60 * 1000) return + trustList.status = RemoteTrustList.Status.UPDATING + eventBus.publish(new TrustSubscriptionUpdatedEvent(trustList : trustList)) check(trustList, now) + trustList.status = RemoteTrustList.Status.UPDATED eventBus.publish(new TrustSubscriptionUpdatedEvent(trustList : trustList)) } } From 8573ab2850e33fe7b484c3f41cd23f24079226f3 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 2 Jul 2019 09:35:21 +0100 Subject: [PATCH 06/17] work on trust list UI --- .../com/muwire/gui/MainFrameModel.groovy | 17 ++++- .../views/com/muwire/gui/MainFrameView.groovy | 65 +++++++++++++------ 2 files changed, 62 insertions(+), 20 deletions(-) diff --git a/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy b/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy index 584d935d..8bb5f7d7 100644 --- a/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy +++ b/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy @@ -28,6 +28,8 @@ import com.muwire.core.search.UIResultBatchEvent import com.muwire.core.search.UIResultEvent import com.muwire.core.trust.TrustEvent import com.muwire.core.trust.TrustService +import com.muwire.core.trust.TrustSubscriptionEvent +import com.muwire.core.trust.TrustSubscriptionUpdatedEvent import com.muwire.core.update.UpdateAvailableEvent import com.muwire.core.update.UpdateDownloadedEvent import com.muwire.core.upload.UploadEvent @@ -64,6 +66,7 @@ class MainFrameModel { def searches = new LinkedList() def trusted = [] def distrusted = [] + def subscriptions = [] @Observable int connections @Observable String me @@ -145,6 +148,7 @@ class MainFrameModel { core.eventBus.register(RouterDisconnectedEvent.class, this) core.eventBus.register(AllFilesLoadedEvent.class, this) core.eventBus.register(UpdateDownloadedEvent.class, this) + core.eventBus.register(TrustSubscriptionUpdatedEvent.class, this) timer.schedule({ if (core.shutdown.get()) @@ -173,7 +177,6 @@ class MainFrameModel { trusted.addAll(core.trustService.good.values()) distrusted.addAll(core.trustService.bad.values()) - resumeButtonText = "Retry" } }) @@ -185,6 +188,10 @@ class MainFrameModel { watched.addAll(core.muOptions.watchedDirectories) builder.getVariable("watched-directories-table").model.fireTableDataChanged() watched.each { core.eventBus.publish(new FileSharedEvent(file : new File(it))) } + + core.muOptions.trustSubscriptions.each { + core.eventBus.publish(new TrustSubscriptionEvent(persona : it, subscribe : true)) + } } } @@ -316,6 +323,14 @@ class MainFrameModel { } } + void onTrustSubscriptionUpdatedEvent(TrustSubscriptionUpdatedEvent e) { + runInsideUIAsync { + if (!subscriptions.contains(e.trustList)) + subscriptions << e.trustList + updateTablePreservingSelection("subscription-table") + } + } + void onQueryEvent(QueryEvent e) { if (e.replyTo == core.me.destination) return diff --git a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy index 32f7e16a..f782b1cc 100644 --- a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy +++ b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy @@ -251,35 +251,62 @@ class MainFrameView { } } panel(constraints : "trust window") { - gridLayout(rows: 1, cols :2) - panel (border : etchedBorder()){ - borderLayout() - scrollPane(constraints : BorderLayout.CENTER) { - table(id : "trusted-table", autoCreateRowSorter : true) { - tableModel(list : model.trusted) { - closureColumn(header : "Trusted Users", type : String, read : { it.getHumanReadableName() } ) + gridLayout(rows : 2, cols : 1) + panel { + gridLayout(rows: 1, cols :2) + panel (border : etchedBorder()){ + borderLayout() + scrollPane(constraints : BorderLayout.CENTER) { + table(id : "trusted-table", autoCreateRowSorter : true) { + tableModel(list : model.trusted) { + closureColumn(header : "Trusted Users", type : String, read : { it.getHumanReadableName() } ) + } } } + panel (constraints : BorderLayout.EAST) { + gridBagLayout() + button(text : "Mark Neutral", constraints : gbc(gridx: 0, gridy: 0), markNeutralFromTrustedAction) + button(text : "Mark Distrusted", constraints : gbc(gridx: 0, gridy:1), markDistrustedAction) + } } - panel (constraints : BorderLayout.EAST) { - gridBagLayout() - button(text : "Mark Neutral", constraints : gbc(gridx: 0, gridy: 0), markNeutralFromTrustedAction) - button(text : "Mark Distrusted", constraints : gbc(gridx: 0, gridy:1), markDistrustedAction) + panel (border : etchedBorder()){ + borderLayout() + scrollPane(constraints : BorderLayout.CENTER) { + table(id : "distrusted-table", autoCreateRowSorter : true) { + tableModel(list : model.distrusted) { + closureColumn(header: "Distrusted Users", type : String, read : { it.getHumanReadableName() } ) + } + } + } + panel(constraints : BorderLayout.WEST) { + gridBagLayout() + button(text: "Mark Neutral", constraints: gbc(gridx: 0, gridy: 0), markNeutralFromDistrustedAction) + button(text: "Mark Trusted", constraints : gbc(gridx: 0, gridy : 1), markTrustedAction) + } } } - panel (border : etchedBorder()){ + panel { borderLayout() + panel (constraints : BorderLayout.NORTH){ + label(text : "Trust List Subscriptions") + } scrollPane(constraints : BorderLayout.CENTER) { - table(id : "distrusted-table", autoCreateRowSorter : true) { - tableModel(list : model.distrusted) { - closureColumn(header: "Distrusted Users", type : String, read : { it.getHumanReadableName() } ) + table(id : "subscription-table", autoCreateRowSorter : true) { + tableModel(list : model.subscriptions) { + closureColumn(header : "Name", type: String, read : {it.persona.getHumanReadableName()}) + closureColumn(header : "Trusted", type: Integer, read : {it.good.size()}) + closureColumn(header : "Distrusted", type: Integer, read : {it.bad.size()}) + closureColumn(header : "Status", type: String, read : {it.status.toString()}) + closureColumn(header : "Last Updated", type : String, read : { + String.valueOf(new Date(it.timestamp)) + }) } } } - panel(constraints : BorderLayout.WEST) { - gridBagLayout() - button(text: "Mark Neutral", constraints: gbc(gridx: 0, gridy: 0), markNeutralFromDistrustedAction) - button(text: "Mark Trusted", constraints : gbc(gridx: 0, gridy : 1), markTrustedAction) + panel(constraints : BorderLayout.SOUTH) { + button(text : "Review") + button(text : "Update") + button(text : "Unsubscribe") } } } From cafc5f582e4c6ac96a18611b59abfa172dd33727 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 2 Jul 2019 14:35:52 +0100 Subject: [PATCH 07/17] subscribe button --- .../com/muwire/gui/MainFrameController.groovy | 13 +++++++- .../views/com/muwire/gui/MainFrameView.groovy | 31 ++++++++++++++++++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy index 8efab2d9..e618de2e 100644 --- a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy +++ b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy @@ -14,6 +14,7 @@ import javax.inject.Inject import com.muwire.core.Constants import com.muwire.core.Core +import com.muwire.core.Persona import com.muwire.core.SharedFile import com.muwire.core.download.DownloadStartedEvent import com.muwire.core.download.UIDownloadCancelledEvent @@ -26,6 +27,7 @@ import com.muwire.core.search.QueryEvent import com.muwire.core.search.SearchEvent import com.muwire.core.trust.TrustEvent import com.muwire.core.trust.TrustLevel +import com.muwire.core.trust.TrustSubscriptionEvent @ArtifactProviderFor(GriffonController) class MainFrameController { @@ -184,7 +186,7 @@ class MainFrameController { } private void markTrust(String tableName, TrustLevel level, def list) { - int row = builder.getVariable(tableName).getSelectedRow() + int row = view.getSelectedTrustTablesRow(tableName) if (row < 0) return core.eventBus.publish(new TrustEvent(persona : list[row], level : level)) @@ -210,6 +212,15 @@ class MainFrameController { markTrust("trusted-table", TrustLevel.NEUTRAL, model.trusted) } + @ControllerAction + void subscribe() { + int row = view.getSelectedTrustTablesRow("trusted-table") + if (row < 0) + return + Persona p = model.trusted[row] + core.eventBus.publish(new TrustSubscriptionEvent(persona : p, subscribe : true)) + } + void unshareSelectedFile() { SharedFile sf = view.selectedSharedFile() if (sf == null) diff --git a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy index f782b1cc..0136a2e0 100644 --- a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy +++ b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy @@ -54,6 +54,7 @@ class MainFrameView { def lastDownloadSortEvent def lastSharedSortEvent def lastWatchedSortEvent + def trustTablesSortEvents = [:] void initUI() { UISettings settings = application.context.get("ui-settings") @@ -267,6 +268,7 @@ class MainFrameView { gridBagLayout() button(text : "Mark Neutral", constraints : gbc(gridx: 0, gridy: 0), markNeutralFromTrustedAction) button(text : "Mark Distrusted", constraints : gbc(gridx: 0, gridy:1), markDistrustedAction) + button(text : "Subscribe", constraints : gbc(gridx: 0, gridy : 2), subscribeAction) } } panel (border : etchedBorder()){ @@ -298,7 +300,10 @@ class MainFrameView { closureColumn(header : "Distrusted", type: Integer, read : {it.bad.size()}) closureColumn(header : "Status", type: String, read : {it.status.toString()}) closureColumn(header : "Last Updated", type : String, read : { - String.valueOf(new Date(it.timestamp)) + if (it.timestamp == 0) + return "Never" + else + return String.valueOf(new Date(it.timestamp)) }) } } @@ -453,6 +458,20 @@ class MainFrameView { } }) + // subscription table + def subscriptionTable = builder.getVariable("subscription-table") + subscriptionTable.rowSorter.addRowSorterListener({evt -> trustTablesSortEvents["subscription-table"] = evt}) + subscriptionTable.rowSorter.setSortsOnUpdates(true) + + // trusted table + def trustedTable = builder.getVariable("trusted-table") + trustedTable.rowSorter.addRowSortListener({evt -> trustTablesSortEvents["trusted-table"] = evt}) + trustedTable.rowSorter.setSortsOnUpdates(true) + + // distrusted table + def distrustedTable = builder.getVariable("distrusted-table") + distrustedTable.rowSorter.addRowSortListener({evt -> trustTablesSortEvents["distrusted-table"] = evt}) + distrustedTable.rowSorter.setSortsOnUpdates(true) } private static void showPopupMenu(JPopupMenu menu, MouseEvent event) { @@ -617,4 +636,14 @@ class MainFrameView { selectedRow = watchedTable.rowSorter.convertRowIndexToModel(selectedRow) model.watched[selectedRow] } + + int getSelectedTrustTablesRow(String tableName) { + def table = builder.getVariable(tableName) + int selectedRow = table.getSelectedRow() + if (selectedRow < 0) + return -1 + if (trustTablesSortEvents.get(tableName) != null) + selectedRow = table.rowSorter.convertRowIndexToModel(selectedRow) + selectedRow + } } \ No newline at end of file From b99bc0ea32effffbac54f6868c2c208775e5fd91 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 2 Jul 2019 20:12:22 +0100 Subject: [PATCH 08/17] fix --- gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy index 0136a2e0..4b49d270 100644 --- a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy +++ b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy @@ -465,12 +465,12 @@ class MainFrameView { // trusted table def trustedTable = builder.getVariable("trusted-table") - trustedTable.rowSorter.addRowSortListener({evt -> trustTablesSortEvents["trusted-table"] = evt}) + trustedTable.rowSorter.addRowSorterListener({evt -> trustTablesSortEvents["trusted-table"] = evt}) trustedTable.rowSorter.setSortsOnUpdates(true) // distrusted table def distrustedTable = builder.getVariable("distrusted-table") - distrustedTable.rowSorter.addRowSortListener({evt -> trustTablesSortEvents["distrusted-table"] = evt}) + distrustedTable.rowSorter.addRowSorterListener({evt -> trustTablesSortEvents["distrusted-table"] = evt}) distrustedTable.rowSorter.setSortsOnUpdates(true) } From 7daf981f1ae802568780b2114434e43cb3b38bc4 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 2 Jul 2019 20:24:51 +0100 Subject: [PATCH 09/17] fix NPE --- .../main/groovy/com/muwire/core/trust/TrustSubscriber.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy b/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy index 6af51a9f..65789af4 100644 --- a/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy +++ b/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy @@ -51,7 +51,7 @@ class TrustSubscriber { } else { settings.trustSubscriptions.add(e.persona) RemoteTrustList trustList = remoteTrustLists.putIfAbsent(e.persona.destination, new RemoteTrustList(e.persona)) - trustList.timestamp = 0 + trustList?.timestamp = 0 synchronized(waitLock) { waitLock.notify() } From 14857cb5ad00d74d41eecb3c64d3eb96b00a2c65 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 2 Jul 2019 20:35:50 +0100 Subject: [PATCH 10/17] swallow headers in trust list response --- .../main/groovy/com/muwire/core/trust/TrustSubscriber.groovy | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy b/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy index 65789af4..f65930f5 100644 --- a/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy +++ b/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy @@ -102,6 +102,10 @@ class TrustSubscriber { return } + // swallow any headers + String header + while (( header = DataUtil.readTillRN(is)) != ""); + DataInputStream dis = new DataInputStream(is) Set good = new HashSet<>() From 44c880d911b2cb47d5b6a0e1e7797d045324aae2 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 2 Jul 2019 20:53:29 +0100 Subject: [PATCH 11/17] store subscriber list upon subscription --- core/src/main/groovy/com/muwire/core/MuWireSettings.groovy | 2 +- .../main/groovy/com/muwire/core/trust/TrustSubscriber.groovy | 2 -- .../controllers/com/muwire/gui/MainFrameController.groovy | 2 ++ 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy b/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy index 4da24897..808ae99c 100644 --- a/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy +++ b/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy @@ -98,7 +98,7 @@ class MuWireSettings { if (!trustSubscriptions.isEmpty()) { String encoded = trustSubscriptions.stream(). - map(it.toBase64()). + map({it.toBase64()}). collect(Collectors.joining(",")) props.setProperty("trustSubscriptions", encoded) } diff --git a/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy b/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy index f65930f5..647ac491 100644 --- a/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy +++ b/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy @@ -46,10 +46,8 @@ class TrustSubscriber { void onTrustSubscriptionEvent(TrustSubscriptionEvent e) { if (!e.subscribe) { - settings.trustSubscriptions.remove(e.persona) remoteTrustLists.remove(e.persona.destination) } else { - settings.trustSubscriptions.add(e.persona) RemoteTrustList trustList = remoteTrustLists.putIfAbsent(e.persona.destination, new RemoteTrustList(e.persona)) trustList?.timestamp = 0 synchronized(waitLock) { diff --git a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy index e618de2e..d49519c9 100644 --- a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy +++ b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy @@ -218,6 +218,8 @@ class MainFrameController { if (row < 0) return Persona p = model.trusted[row] + core.muOptions.trustSubscriptions.add(p) + saveMuWireSettings() core.eventBus.publish(new TrustSubscriptionEvent(persona : p, subscribe : true)) } From 5cd1ca88c1360d7e71afa3c437fea1f33574be55 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 2 Jul 2019 21:34:29 +0100 Subject: [PATCH 12/17] do actual updating on in a threadpool --- .../muwire/core/trust/TrustSubscriber.groovy | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy b/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy index 647ac491..2f948e4f 100644 --- a/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy +++ b/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy @@ -2,6 +2,8 @@ package com.muwire.core.trust import java.nio.charset.StandardCharsets import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors import java.util.logging.Level import com.muwire.core.EventBus @@ -26,6 +28,7 @@ class TrustSubscriber { private final Object waitLock = new Object() private volatile boolean shutdown private volatile Thread thread + private final ExecutorService updateThreads = Executors.newCachedThreadPool() TrustSubscriber(EventBus eventBus, I2PConnector i2pConnector, MuWireSettings settings) { this.eventBus = eventBus @@ -42,6 +45,7 @@ class TrustSubscriber { void stop() { shutdown = true thread?.interrupt() + updateThreads.shutdownNow() } void onTrustSubscriptionEvent(TrustSubscriptionEvent e) { @@ -66,11 +70,7 @@ class TrustSubscriber { remoteTrustLists.values().each { trustList -> if (now - trustList.timestamp < settings.trustListInterval * 60 * 60 * 1000) return - trustList.status = RemoteTrustList.Status.UPDATING - eventBus.publish(new TrustSubscriptionUpdatedEvent(trustList : trustList)) - check(trustList, now) - trustList.status = RemoteTrustList.Status.UPDATED - eventBus.publish(new TrustSubscriptionUpdatedEvent(trustList : trustList)) + updateThreads.submit(new UpdateJob(trustList)) } } } catch (InterruptedException e) { @@ -79,6 +79,23 @@ class TrustSubscriber { } } + private class UpdateJob implements Runnable { + + private final RemoteTrustList trustList + + UpdateJob(RemoteTrustList trustList) { + this.trustList = trustList + } + + public void run() { + trustList.status = RemoteTrustList.Status.UPDATING + eventBus.publish(new TrustSubscriptionUpdatedEvent(trustList : trustList)) + check(trustList, System.currentTimeMillis()) + trustList.status = RemoteTrustList.Status.UPDATED + eventBus.publish(new TrustSubscriptionUpdatedEvent(trustList : trustList)) + } + } + private void check(RemoteTrustList trustList, long now) { log.info("fetching trust list from ${trustList.persona.getHumanReadableName()}") Endpoint endpoint = null From 011a4d57662708ab92a34413a2b772fcf923e1c5 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 2 Jul 2019 22:02:15 +0100 Subject: [PATCH 13/17] prevent duplicate updates and zero timestamps --- .../groovy/com/muwire/core/trust/RemoteTrustList.groovy | 3 ++- .../groovy/com/muwire/core/trust/TrustSubscriber.groovy | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/core/src/main/groovy/com/muwire/core/trust/RemoteTrustList.groovy b/core/src/main/groovy/com/muwire/core/trust/RemoteTrustList.groovy index b9c31611..d2a2c369 100644 --- a/core/src/main/groovy/com/muwire/core/trust/RemoteTrustList.groovy +++ b/core/src/main/groovy/com/muwire/core/trust/RemoteTrustList.groovy @@ -11,7 +11,8 @@ class RemoteTrustList { private final Persona persona private final Set good, bad - long timestamp + volatile long timestamp + volatile boolean forceUpdate Status status = Status.NEW RemoteTrustList(Persona persona) { diff --git a/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy b/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy index 2f948e4f..385bd6e0 100644 --- a/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy +++ b/core/src/main/groovy/com/muwire/core/trust/TrustSubscriber.groovy @@ -53,7 +53,7 @@ class TrustSubscriber { remoteTrustLists.remove(e.persona.destination) } else { RemoteTrustList trustList = remoteTrustLists.putIfAbsent(e.persona.destination, new RemoteTrustList(e.persona)) - trustList?.timestamp = 0 + trustList?.forceUpdate = true synchronized(waitLock) { waitLock.notify() } @@ -68,8 +68,12 @@ class TrustSubscriber { } final long now = System.currentTimeMillis() remoteTrustLists.values().each { trustList -> - if (now - trustList.timestamp < settings.trustListInterval * 60 * 60 * 1000) + if (trustList.status == RemoteTrustList.Status.UPDATING) return + if (!trustList.forceUpdate && + now - trustList.timestamp < settings.trustListInterval * 60 * 60 * 1000) + return + trustList.forceUpdate = false updateThreads.submit(new UpdateJob(trustList)) } } From 82b0fa253ce633eb274909cb025ea378949443d5 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 2 Jul 2019 22:26:29 +0100 Subject: [PATCH 14/17] enable update and unsubscribe buttons --- .../com/muwire/gui/MainFrameController.groovy | 35 +++++++++++++++++++ .../com/muwire/gui/MainFrameModel.groovy | 3 ++ .../views/com/muwire/gui/MainFrameView.groovy | 34 ++++++++++++++++-- 3 files changed, 69 insertions(+), 3 deletions(-) diff --git a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy index d49519c9..69eeb348 100644 --- a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy +++ b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy @@ -11,6 +11,7 @@ import net.i2p.data.Base64 import javax.annotation.Nonnull import javax.inject.Inject +import javax.swing.JTable import com.muwire.core.Constants import com.muwire.core.Core @@ -25,6 +26,7 @@ import com.muwire.core.files.DirectoryUnsharedEvent import com.muwire.core.files.FileUnsharedEvent import com.muwire.core.search.QueryEvent import com.muwire.core.search.SearchEvent +import com.muwire.core.trust.RemoteTrustList import com.muwire.core.trust.TrustEvent import com.muwire.core.trust.TrustLevel import com.muwire.core.trust.TrustSubscriptionEvent @@ -223,6 +225,39 @@ class MainFrameController { core.eventBus.publish(new TrustSubscriptionEvent(persona : p, subscribe : true)) } + @ControllerAction + void review() { + println "review action" + } + + @ControllerAction + void update() { + RemoteTrustList list = getSelectedTrustList() + if (list == null) + return + core.eventBus.publish(new TrustSubscriptionEvent(persona : list.persona, subscribe : true)) + } + + @ControllerAction + void unsubscribe() { + RemoteTrustList list = getSelectedTrustList() + if (list == null) + return + core.muOptions.trustSubscriptions.remove(list.persona) + saveMuWireSettings() + model.subscriptions.remove(list) + JTable table = builder.getVariable("subscription-table") + table.model.fireTableDataChanged() + core.eventBus.publish(new TrustSubscriptionEvent(persona : list.persona, subscribe : false)) + } + + private RemoteTrustList getSelectedTrustList() { + int row = view.getSelectedTrustTablesRow("subscription-table") + if (row < 0) + return null + model.subscriptions[row] + } + void unshareSelectedFile() { SharedFile sf = view.selectedSharedFile() if (sf == null) diff --git a/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy b/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy index 8bb5f7d7..f1e3ee32 100644 --- a/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy +++ b/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy @@ -76,6 +76,9 @@ class MainFrameModel { @Observable boolean retryButtonEnabled @Observable boolean pauseButtonEnabled @Observable String resumeButtonText + @Observable boolean reviewButtonEnabled + @Observable boolean updateButtonEnabled + @Observable boolean unsubscribeButtonEnabled private final Set infoHashes = new HashSet<>() diff --git a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy index 4b49d270..cebfe70f 100644 --- a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy +++ b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy @@ -25,6 +25,7 @@ import com.muwire.core.Constants import com.muwire.core.MuWireSettings import com.muwire.core.download.Downloader import com.muwire.core.files.FileSharedEvent +import com.muwire.core.trust.RemoteTrustList import java.awt.BorderLayout import java.awt.CardLayout @@ -309,9 +310,9 @@ class MainFrameView { } } panel(constraints : BorderLayout.SOUTH) { - button(text : "Review") - button(text : "Update") - button(text : "Unsubscribe") + button(text : "Review", enabled : bind {model.reviewButtonEnabled}, reviewAction) + button(text : "Update", enabled : bind {model.updateButtonEnabled}, updateAction) + button(text : "Unsubscribe", enabled : bind {model.unsubscribeButtonEnabled}, unsubscribeAction) } } } @@ -462,6 +463,33 @@ class MainFrameView { def subscriptionTable = builder.getVariable("subscription-table") subscriptionTable.rowSorter.addRowSorterListener({evt -> trustTablesSortEvents["subscription-table"] = evt}) subscriptionTable.rowSorter.setSortsOnUpdates(true) + selectionModel = subscriptionTable.getSelectionModel() + selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION) + selectionModel.addListSelectionListener({ + int selectedRow = getSelectedTrustTablesRow("subscription-table") + if (selectedRow < 0) { + model.reviewButtonEnabled = false + model.updateButtonEnabled = false + model.unsubscribeButtonEnabled = false + return + } + def trustList = model.subscriptions[selectedRow] + if (trustList == null) + return + switch(trustList.status) { + case RemoteTrustList.Status.NEW: + case RemoteTrustList.Status.UPDATING: + model.reviewButtonEnabled = false + model.updateButtonEnabled = false + model.unsubscribeButtonEnabled = false + break + case RemoteTrustList.Status.UPDATED: + model.reviewButtonEnabled = true + model.updateButtonEnabled = true + model.unsubscribeButtonEnabled = true + break + } + }) // trusted table def trustedTable = builder.getVariable("trusted-table") From 5be97d040494e7213ff5692af9ffd14a0d7c91f6 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 2 Jul 2019 22:51:04 +0100 Subject: [PATCH 15/17] show something when review button is pressed --- gui/griffon-app/conf/Config.groovy | 5 ++ .../com/muwire/gui/MainFrameController.groovy | 7 ++- .../com/muwire/gui/TrustListController.groovy | 13 +++++ .../com/muwire/gui/TrustListModel.groovy | 12 +++++ .../views/com/muwire/gui/TrustListView.groovy | 48 +++++++++++++++++++ .../gui/TrustListIntegrationTest.groovy | 25 ++++++++++ .../muwire/gui/TrustListControllerTest.groovy | 21 ++++++++ 7 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 gui/griffon-app/controllers/com/muwire/gui/TrustListController.groovy create mode 100644 gui/griffon-app/models/com/muwire/gui/TrustListModel.groovy create mode 100644 gui/griffon-app/views/com/muwire/gui/TrustListView.groovy create mode 100644 gui/src/integration-test/groovy/com/muwire/gui/TrustListIntegrationTest.groovy create mode 100644 gui/src/test/groovy/com/muwire/gui/TrustListControllerTest.groovy diff --git a/gui/griffon-app/conf/Config.groovy b/gui/griffon-app/conf/Config.groovy index 16ae3915..3e1a209d 100644 --- a/gui/griffon-app/conf/Config.groovy +++ b/gui/griffon-app/conf/Config.groovy @@ -36,4 +36,9 @@ mvcGroups { view = 'com.muwire.gui.I2PStatusView' controller = 'com.muwire.gui.I2PStatusController' } + 'trust-list' { + model = 'com.muwire.gui.TrustListModel' + view = 'com.muwire.gui.TrustListView' + controller = 'com.muwire.gui.TrustListController' + } } diff --git a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy index 69eeb348..2898c55d 100644 --- a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy +++ b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy @@ -227,7 +227,12 @@ class MainFrameController { @ControllerAction void review() { - println "review action" + RemoteTrustList list = getSelectedTrustList() + if (list == null) + return + Map env = new HashMap<>() + env["trustList"] = list + mvcGroup.createMVCGroup("trust-list", env) } @ControllerAction diff --git a/gui/griffon-app/controllers/com/muwire/gui/TrustListController.groovy b/gui/griffon-app/controllers/com/muwire/gui/TrustListController.groovy new file mode 100644 index 00000000..1dbc19b2 --- /dev/null +++ b/gui/griffon-app/controllers/com/muwire/gui/TrustListController.groovy @@ -0,0 +1,13 @@ +package com.muwire.gui + +import griffon.core.artifact.GriffonController +import griffon.core.controller.ControllerAction +import griffon.inject.MVCMember +import griffon.metadata.ArtifactProviderFor +import javax.annotation.Nonnull + +@ArtifactProviderFor(GriffonController) +class TrustListController { + @MVCMember @Nonnull + TrustListModel model +} \ No newline at end of file diff --git a/gui/griffon-app/models/com/muwire/gui/TrustListModel.groovy b/gui/griffon-app/models/com/muwire/gui/TrustListModel.groovy new file mode 100644 index 00000000..f4f1f302 --- /dev/null +++ b/gui/griffon-app/models/com/muwire/gui/TrustListModel.groovy @@ -0,0 +1,12 @@ +package com.muwire.gui + +import com.muwire.core.trust.RemoteTrustList + +import griffon.core.artifact.GriffonModel +import griffon.transform.Observable +import griffon.metadata.ArtifactProviderFor + +@ArtifactProviderFor(GriffonModel) +class TrustListModel { + RemoteTrustList trustList +} \ No newline at end of file diff --git a/gui/griffon-app/views/com/muwire/gui/TrustListView.groovy b/gui/griffon-app/views/com/muwire/gui/TrustListView.groovy new file mode 100644 index 00000000..00a23423 --- /dev/null +++ b/gui/griffon-app/views/com/muwire/gui/TrustListView.groovy @@ -0,0 +1,48 @@ +package com.muwire.gui + +import griffon.core.artifact.GriffonView +import griffon.inject.MVCMember +import griffon.metadata.ArtifactProviderFor + +import javax.swing.JDialog +import javax.swing.SwingConstants + +import java.awt.BorderLayout +import java.awt.event.WindowAdapter +import java.awt.event.WindowEvent + +import javax.annotation.Nonnull + +@ArtifactProviderFor(GriffonView) +class TrustListView { + @MVCMember @Nonnull + FactoryBuilderSupport builder + @MVCMember @Nonnull + TrustListModel model + + def dialog + def mainFrame + def panel + + void initUI() { + mainFrame = application.windowManager.findWindow("main-frame") + dialog = new JDialog(mainFrame, model.trustList.persona.getHumanReadableName(), true) + panel = builder.panel { + borderLayout() + label(text : "Last updated "+ model.trustList.timestamp, constraints : BorderLayout.CENTER) + } + } + + void mvcGroupInit(Map args) { + dialog.getContentPane().add(panel) + dialog.pack() + dialog.setLocationRelativeTo(mainFrame) + dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE) + dialog.addWindowListener(new WindowAdapter() { + public void windowClosed(WindowEvent e) { + mvcGroup.destroy() + } + }) + dialog.show() + } +} \ No newline at end of file diff --git a/gui/src/integration-test/groovy/com/muwire/gui/TrustListIntegrationTest.groovy b/gui/src/integration-test/groovy/com/muwire/gui/TrustListIntegrationTest.groovy new file mode 100644 index 00000000..5900d050 --- /dev/null +++ b/gui/src/integration-test/groovy/com/muwire/gui/TrustListIntegrationTest.groovy @@ -0,0 +1,25 @@ +package com.muwire.gui + +import griffon.core.test.GriffonFestRule +import org.fest.swing.fixture.FrameFixture +import org.junit.Rule +import org.junit.Test + +import static org.junit.Assert.fail + +class TrustListIntegrationTest { + static { + System.setProperty('griffon.swing.edt.violations.check', 'true') + System.setProperty('griffon.swing.edt.hang.monitor', 'true') + } + + @Rule + public final GriffonFestRule fest = new GriffonFestRule() + + private FrameFixture window + + @Test + void smokeTest() { + fail('Not implemented yet!') + } +} diff --git a/gui/src/test/groovy/com/muwire/gui/TrustListControllerTest.groovy b/gui/src/test/groovy/com/muwire/gui/TrustListControllerTest.groovy new file mode 100644 index 00000000..cf7ae9f9 --- /dev/null +++ b/gui/src/test/groovy/com/muwire/gui/TrustListControllerTest.groovy @@ -0,0 +1,21 @@ +package com.muwire.gui + +import griffon.core.test.GriffonUnitRule +import griffon.core.test.TestFor +import org.junit.Rule +import org.junit.Test + +import static org.junit.Assert.fail + +@TestFor(TrustListController) +class TrustListControllerTest { + private TrustListController controller + + @Rule + public final GriffonUnitRule griffon = new GriffonUnitRule() + + @Test + void smokeTest() { + fail('Not yet implemented!') + } +} \ No newline at end of file From 35cabc47add3bf5a837e6084634e6e321418ec52 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 2 Jul 2019 23:44:43 +0100 Subject: [PATCH 16/17] hook up trust and distrust buttons --- .../com/muwire/gui/MainFrameController.groovy | 3 + .../com/muwire/gui/TrustListController.groovy | 45 ++++++++++++ .../com/muwire/gui/TrustListModel.groovy | 10 +++ .../views/com/muwire/gui/TrustListView.groovy | 73 ++++++++++++++++++- 4 files changed, 127 insertions(+), 4 deletions(-) diff --git a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy index 2898c55d..d9c179fb 100644 --- a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy +++ b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy @@ -232,7 +232,10 @@ class MainFrameController { return Map env = new HashMap<>() env["trustList"] = list + env["trustService"] = core.trustService + env["eventBus"] = core.eventBus mvcGroup.createMVCGroup("trust-list", env) + } @ControllerAction diff --git a/gui/griffon-app/controllers/com/muwire/gui/TrustListController.groovy b/gui/griffon-app/controllers/com/muwire/gui/TrustListController.groovy index 1dbc19b2..db9fb794 100644 --- a/gui/griffon-app/controllers/com/muwire/gui/TrustListController.groovy +++ b/gui/griffon-app/controllers/com/muwire/gui/TrustListController.groovy @@ -6,8 +6,53 @@ import griffon.inject.MVCMember import griffon.metadata.ArtifactProviderFor import javax.annotation.Nonnull +import com.muwire.core.EventBus +import com.muwire.core.Persona +import com.muwire.core.trust.TrustEvent +import com.muwire.core.trust.TrustLevel + @ArtifactProviderFor(GriffonController) class TrustListController { @MVCMember @Nonnull TrustListModel model + @MVCMember @Nonnull + TrustListView view + + EventBus eventBus + + @ControllerAction + void trustFromTrusted() { + int selectedRow = view.getSelectedRow("trusted-table") + if (selectedRow < 0) + return + Persona p = model.trusted[selectedRow] + eventBus.publish(new TrustEvent(persona : p, level : TrustLevel.TRUSTED)) + } + + @ControllerAction + void trustFromDistrusted() { + int selectedRow = view.getSelectedRow("distrusted-table") + if (selectedRow < 0) + return + Persona p = model.distrusted[selectedRow] + eventBus.publish(new TrustEvent(persona : p, level : TrustLevel.TRUSTED)) + } + + @ControllerAction + void distrustFromTrusted() { + int selectedRow = view.getSelectedRow("trusted-table") + if (selectedRow < 0) + return + Persona p = model.trusted[selectedRow] + eventBus.publish(new TrustEvent(persona : p, level : TrustLevel.DISTRUSTED)) + } + + @ControllerAction + void distrustFromDistrusted() { + int selectedRow = view.getSelectedRow("distrusted-table") + if (selectedRow < 0) + return + Persona p = model.distrusted[selectedRow] + eventBus.publish(new TrustEvent(persona : p, level : TrustLevel.DISTRUSTED)) + } } \ No newline at end of file diff --git a/gui/griffon-app/models/com/muwire/gui/TrustListModel.groovy b/gui/griffon-app/models/com/muwire/gui/TrustListModel.groovy index f4f1f302..bbd6bb40 100644 --- a/gui/griffon-app/models/com/muwire/gui/TrustListModel.groovy +++ b/gui/griffon-app/models/com/muwire/gui/TrustListModel.groovy @@ -1,6 +1,7 @@ package com.muwire.gui import com.muwire.core.trust.RemoteTrustList +import com.muwire.core.trust.TrustService import griffon.core.artifact.GriffonModel import griffon.transform.Observable @@ -9,4 +10,13 @@ import griffon.metadata.ArtifactProviderFor @ArtifactProviderFor(GriffonModel) class TrustListModel { RemoteTrustList trustList + TrustService trustService + + def trusted + def distrusted + + void mvcGroupInit(Map args) { + trusted = new ArrayList<>(trustList.good) + distrusted = new ArrayList<>(trustList.bad) + } } \ No newline at end of file diff --git a/gui/griffon-app/views/com/muwire/gui/TrustListView.groovy b/gui/griffon-app/views/com/muwire/gui/TrustListView.groovy index 00a23423..53cb01c6 100644 --- a/gui/griffon-app/views/com/muwire/gui/TrustListView.groovy +++ b/gui/griffon-app/views/com/muwire/gui/TrustListView.groovy @@ -5,6 +5,7 @@ import griffon.inject.MVCMember import griffon.metadata.ArtifactProviderFor import javax.swing.JDialog +import javax.swing.ListSelectionModel import javax.swing.SwingConstants import java.awt.BorderLayout @@ -22,19 +23,73 @@ class TrustListView { def dialog def mainFrame - def panel + def mainPanel + + def sortEvents = [:] void initUI() { mainFrame = application.windowManager.findWindow("main-frame") dialog = new JDialog(mainFrame, model.trustList.persona.getHumanReadableName(), true) - panel = builder.panel { + mainPanel = builder.panel { borderLayout() - label(text : "Last updated "+ model.trustList.timestamp, constraints : BorderLayout.CENTER) + panel(constraints : BorderLayout.NORTH) { + borderLayout() + panel (constraints : BorderLayout.NORTH) { + label(text: "Trust List of "+model.trustList.persona.getHumanReadableName()) + } + panel (constraints: BorderLayout.SOUTH) { + label(text : "Last updated "+ new Date(model.trustList.timestamp)) + } + } + panel(constraints : BorderLayout.CENTER) { + gridLayout(rows : 1, cols : 2) + panel { + borderLayout() + scrollPane (constraints : BorderLayout.CENTER){ + table(id : "trusted-table", autoCreateRowSorter : true) { + tableModel(list : model.trusted) { + closureColumn(header: "Trusted Users", type : String, read : {it.getHumanReadableName()}) + closureColumn(header: "Your Trust", type : String, read : {model.trustService.getLevel(it.destination).toString()}) + } + } + } + panel (constraints : BorderLayout.SOUTH) { + gridBagLayout() + button(text : "Trust", constraints : gbc(gridx : 0, gridy : 0), trustFromTrustedAction) + button(text : "Distrust", constraints : gbc(gridx : 1, gridy : 0), distrustFromTrustedAction) + } + } + panel { + borderLayout() + scrollPane (constraints : BorderLayout.CENTER ){ + table(id : "distrusted-table", autoCreateRowSorter : true) { + tableModel(list : model.distrusted) { + closureColumn(header: "Distrusted Users", type : String, read : {it.getHumanReadableName()}) + closureColumn(header: "Your Trust", type : String, read : {model.trustService.getLevel(it.destination).toString()}) + } + } + } + panel(constraints : BorderLayout.SOUTH) { + gridBagLayout() + button(text : "Trust", constraints : gbc(gridx : 0, gridy : 0), trustFromDistrustedAction) + button(text : "Distrust", constraints : gbc(gridx : 1, gridy : 0), distrustFromDistrustedAction) + } + } + } } } void mvcGroupInit(Map args) { - dialog.getContentPane().add(panel) + + def trustedTable = builder.getVariable("trusted-table") + trustedTable.rowSorter.addRowSorterListener({evt -> sortEvents["trusted-table"] = evt}) + trustedTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION) + + def distrustedTable = builder.getVariable("distrusted-table") + distrustedTable.rowSorter.addRowSorterListener({evt -> sortEvents["distrusted-table"] = evt}) + distrustedTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION) + + dialog.getContentPane().add(mainPanel) dialog.pack() dialog.setLocationRelativeTo(mainFrame) dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE) @@ -45,4 +100,14 @@ class TrustListView { }) dialog.show() } + + int getSelectedRow(String tableName) { + def table = builder.getVariable(tableName) + int selectedRow = table.getSelectedRow() + if (selectedRow < 0) + return -1 + if (sortEvents.get(tableName) != null) + selectedRow = table.rowSorter.convertRowIndexToModel(selectedRow) + selectedRow + } } \ No newline at end of file From fb42fc0e35a3a30f258975681ed825aa8e3baedd Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Wed, 3 Jul 2019 00:04:08 +0100 Subject: [PATCH 17/17] add trust panel in options --- .../com/muwire/gui/OptionsController.groovy | 18 +++++++--- .../models/com/muwire/gui/OptionsModel.groovy | 12 +++++-- .../views/com/muwire/gui/OptionsView.groovy | 33 ++++++++++++------- 3 files changed, 46 insertions(+), 17 deletions(-) diff --git a/gui/griffon-app/controllers/com/muwire/gui/OptionsController.groovy b/gui/griffon-app/controllers/com/muwire/gui/OptionsController.groovy index 16cebf09..f78d9bb9 100644 --- a/gui/griffon-app/controllers/com/muwire/gui/OptionsController.groovy +++ b/gui/griffon-app/controllers/com/muwire/gui/OptionsController.groovy @@ -74,9 +74,6 @@ class OptionsController { model.autoDownloadUpdate = autoDownloadUpdate settings.autoDownloadUpdate = autoDownloadUpdate - boolean onlyTrusted = view.allowUntrustedCheckbox.model.isSelected() - model.onlyTrusted = onlyTrusted - settings.setAllowUntrusted(!onlyTrusted) boolean shareDownloaded = view.shareDownloadedCheckbox.model.isSelected() model.shareDownloadedFiles = shareDownloaded @@ -93,7 +90,20 @@ class OptionsController { model.outBw = text settings.outBw = Integer.valueOf(text) } - + + + boolean onlyTrusted = view.allowUntrustedCheckbox.model.isSelected() + model.onlyTrusted = onlyTrusted + settings.setAllowUntrusted(!onlyTrusted) + + boolean trustLists = view.allowTrustListsCheckbox.model.isSelected() + model.trustLists = trustLists + settings.allowTrustLists = trustLists + + String trustListInterval = view.trustListIntervalField.text + model.trustListInterval = trustListInterval + settings.trustListInterval = Integer.parseInt(trustListInterval) + File settingsFile = new File(core.home, "MuWire.properties") settingsFile.withOutputStream { settings.write(it) diff --git a/gui/griffon-app/models/com/muwire/gui/OptionsModel.groovy b/gui/griffon-app/models/com/muwire/gui/OptionsModel.groovy index fed0b572..2803c830 100644 --- a/gui/griffon-app/models/com/muwire/gui/OptionsModel.groovy +++ b/gui/griffon-app/models/com/muwire/gui/OptionsModel.groovy @@ -12,7 +12,6 @@ class OptionsModel { @Observable String downloadRetryInterval @Observable String updateCheckInterval @Observable boolean autoDownloadUpdate - @Observable boolean onlyTrusted @Observable boolean shareDownloadedFiles @Observable String downloadLocation @@ -37,12 +36,17 @@ class OptionsModel { @Observable String inBw @Observable String outBw + // trust options + @Observable boolean onlyTrusted + @Observable boolean trustLists + @Observable String trustListInterval + + void mvcGroupInit(Map args) { MuWireSettings settings = application.context.get("muwire-settings") downloadRetryInterval = settings.downloadRetryInterval updateCheckInterval = settings.updateCheckInterval autoDownloadUpdate = settings.autoDownloadUpdate - onlyTrusted = !settings.allowUntrusted() shareDownloadedFiles = settings.shareDownloadedFiles downloadLocation = settings.downloadLocation.getAbsolutePath() @@ -67,5 +71,9 @@ class OptionsModel { inBw = String.valueOf(settings.inBw) outBw = String.valueOf(settings.outBw) } + + onlyTrusted = !settings.allowUntrusted() + trustLists = settings.allowTrustLists + trustListInterval = String.valueOf(settings.trustListInterval) } } \ No newline at end of file diff --git a/gui/griffon-app/views/com/muwire/gui/OptionsView.groovy b/gui/griffon-app/views/com/muwire/gui/OptionsView.groovy index 8222f5b4..46c3942b 100644 --- a/gui/griffon-app/views/com/muwire/gui/OptionsView.groovy +++ b/gui/griffon-app/views/com/muwire/gui/OptionsView.groovy @@ -29,11 +29,11 @@ class OptionsView { def i def u def bandwidth + def trust def retryField def updateField def autoDownloadUpdateCheckbox - def allowUntrustedCheckbox def shareDownloadedCheckbox def inboundLengthField @@ -50,11 +50,14 @@ class OptionsView { def clearFinishedDownloadsCheckbox def excludeLocalResultCheckbox def showSearchHashesCheckbox - - + def inBwField def outBwField + def allowUntrustedCheckbox + def allowTrustListsCheckbox + def trustListIntervalField + def buttonsPanel def mainFrame @@ -76,15 +79,12 @@ class OptionsView { 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 : 3)) - allowUntrustedCheckbox = checkBox(selected : bind {model.onlyTrusted}, constraints : gbc(gridx: 1, gridy : 3)) + label(text : "Share downloaded files", constraints : gbc(gridx : 0, gridy:3)) + shareDownloadedCheckbox = checkBox(selected : bind {model.shareDownloadedFiles}, constraints : gbc(gridx :1, gridy:3)) - label(text : "Share downloaded files", constraints : gbc(gridx : 0, gridy:4)) - shareDownloadedCheckbox = checkBox(selected : bind {model.shareDownloadedFiles}, constraints : gbc(gridx :1, gridy:4)) - - label(text : "Save downloaded files to:", constraints: gbc(gridx:0, gridy:5)) - button(text : "Choose", constraints : gbc(gridx : 1, gridy:5), downloadLocationAction) - label(text : bind {model.downloadLocation}, constraints: gbc(gridx:0, gridy:6, gridwidth:2)) + 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 { @@ -134,6 +134,16 @@ class OptionsView { 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)) } + trust = builder.panel { + gridBagLayout() + label(text : "Allow only trusted connections", constraints : gbc(gridx: 0, gridy : 0)) + allowUntrustedCheckbox = checkBox(selected : bind {model.onlyTrusted}, constraints : gbc(gridx: 1, gridy : 0)) + label(text : "Allow others to view my trust list", constraints : gbc(gridx: 0, gridy : 1)) + allowTrustListsCheckbox = checkBox(selected : bind {model.trustLists}, constraints : gbc(gridx: 1, gridy : 1)) + label(text : "Update trust lists every ", constraints : gbc(gridx:0, gridy:2)) + trustListIntervalField = textField(text : bind {model.trustListInterval}, constraints:gbc(gridx:1, gridy:2)) + label(text : "hours", constraints : gbc(gridx: 2, gridy:2)) + } buttonsPanel = builder.panel { @@ -152,6 +162,7 @@ class OptionsView { if (core.router != null) { tabbedPane.addTab("Bandwidth", bandwidth) } + tabbedPane.addTab("Trust", trust) JPanel panel = new JPanel() panel.setLayout(new BorderLayout())