Compare commits

...

30 Commits

Author SHA1 Message Date
Zlatin Balevsky
4f948c1b9e Release 0.4.6 2019-07-03 07:11:59 +01:00
Zlatin Balevsky
2b68c24f9c use switch 2019-07-03 07:01:27 +01:00
Zlatin Balevsky
bcdf0422db update for embedded router 2019-07-03 07:00:04 +01:00
Zlatin Balevsky
f6434b478d remove FAQ 2019-07-03 06:56:20 +01:00
Zlatin Balevsky
e979fdd26f update list view tables 2019-07-03 06:51:21 +01:00
Zlatin Balevsky
e6bfcaaab9 size columns, center integers 2019-07-03 06:11:02 +01:00
Zlatin Balevsky
9780108e8a disable trust buttons on action 2019-07-03 06:00:09 +01:00
Zlatin Balevsky
697c7d2d6d enable/disable trust panel buttons 2019-07-03 05:41:17 +01:00
Zlatin Balevsky
887d10c8bf move buttons around 2019-07-03 05:30:39 +01:00
Zlatin Balevsky
ef6b8fe458 add a state for failed updates 2019-07-03 05:12:00 +01:00
Zlatin Balevsky
20ab55d763 update todo 2019-07-03 00:23:21 +01:00
Zlatin Balevsky
eda58c9e0d Merge branch 'trust-lists' 2019-07-03 00:04:50 +01:00
Zlatin Balevsky
fb42fc0e35 add trust panel in options 2019-07-03 00:04:08 +01:00
Zlatin Balevsky
35cabc47ad hook up trust and distrust buttons 2019-07-02 23:44:43 +01:00
Zlatin Balevsky
5be97d0404 show something when review button is pressed 2019-07-02 22:51:04 +01:00
Zlatin Balevsky
82b0fa253c enable update and unsubscribe buttons 2019-07-02 22:26:29 +01:00
Zlatin Balevsky
011a4d5766 prevent duplicate updates and zero timestamps 2019-07-02 22:02:15 +01:00
Zlatin Balevsky
5cd1ca88c1 do actual updating on in a threadpool 2019-07-02 21:34:29 +01:00
Zlatin Balevsky
44c880d911 store subscriber list upon subscription 2019-07-02 20:53:29 +01:00
Zlatin Balevsky
14857cb5ad swallow headers in trust list response 2019-07-02 20:35:50 +01:00
Zlatin Balevsky
7daf981f1a fix NPE 2019-07-02 20:24:51 +01:00
Zlatin Balevsky
b99bc0ea32 fix 2019-07-02 20:12:22 +01:00
Zlatin Balevsky
1ccf6fbdfa participating bandwidth grid cell 2019-07-02 15:35:42 +01:00
Zlatin Balevsky
cafc5f582e subscribe button 2019-07-02 14:35:52 +01:00
Zlatin Balevsky
8573ab2850 work on trust list UI 2019-07-02 09:35:21 +01:00
Zlatin Balevsky
8b3d752727 add status to the trust list object 2019-07-02 08:59:30 +01:00
Zlatin Balevsky
7c54bd8966 start work on sharing of trust lists 2019-07-01 23:33:39 +01:00
Zlatin Balevsky
5d0fcb7027 start work on sharing of trust lists 2019-07-01 23:15:13 +01:00
Zlatin Balevsky
3ec9654d3c start work on sharing of trust lists 2019-07-01 22:05:43 +01:00
Zlatin Balevsky
7c8d64b462 start work on sharing of trust lists 2019-07-01 21:40:07 +01:00
25 changed files with 822 additions and 77 deletions

View File

@@ -23,34 +23,9 @@ Some of the UI tests will fail because they haven't been written yet :-/
### 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.
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.
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.
If you have an I2P router running on the same machine that is all you need to do. 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.
If you do not have an I2P router, pass the following switch to the Java process: `-DembeddedRouter=true`. This will launch MuWire's embedded router. Be aware that this causes startup to take a lot longer.
### Known bugs and limitations
* Many UI features you would expect are not there yet
### Quick FAQ
* why is MuWire slow ?
- too few sources you're downloading from
- you can increase the number of tunnels by using more tunnels via Options->I2P Inbound/Outbound Quantity
the default is 4 and you could raise up to as high as 16 ( Caution !!!!)
* my search is not returning (enough) results !
- search is keyword or hash based
- keywords and hash(es) are NOT regexed or wildcarded so they have to be complete
so searching for 'musi' will not return results with 'music' - you have to search for 'music'
- ALL keywords have to match
- only use space for keyword separation
- if you already have the file in question it is not displayed ( can be changed via Options )
* what's this right click -> 'Copy hash to clipboard' for ?
- if you have a specific file you wish to share or download you can use the hash as a unique identifier
to make sure you have exactly the right file.
- you can share this hash with others to ensure they are getting the right file

View File

@@ -12,10 +12,6 @@ This reduces query traffic by not sending last hop queries to peers that definit
This helps with scalability
##### Trust List Sharing
For helping users make better decisions whom to trust
##### Content Control Panel
To allow every user to not route queries for content they do not like. This is mostly GUI work, the backend part is simple

View File

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

View File

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

View File

@@ -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")
@@ -353,7 +363,7 @@ public class Core {
}
}
Core core = new Core(props, home, "0.4.5")
Core core = new Core(props, home, "0.4.6")
core.startServices()
// ... at the end, sleep or execute script

View File

@@ -11,6 +11,9 @@ class MuWireSettings {
final boolean isLeaf
boolean allowUntrusted
boolean allowTrustLists
int trustListInterval
Set<Persona> trustSubscriptions
int downloadRetryInterval
int updateCheckInterval
boolean autoDownloadUpdate
@@ -32,7 +35,9 @@ class MuWireSettings {
MuWireSettings(Properties props) {
isLeaf = Boolean.valueOf(props.get("leaf","false"))
allowUntrusted = Boolean.valueOf(props.get("allowUntrusted","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",
@@ -55,12 +60,20 @@ 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 {
Properties props = new Properties()
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())
@@ -83,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, "")
}

View File

@@ -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<Persona> 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<Persona> 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()
}
}

View File

@@ -0,0 +1,31 @@
package com.muwire.core.trust
import java.util.concurrent.ConcurrentHashMap
import com.muwire.core.Persona
import net.i2p.util.ConcurrentHashSet
class RemoteTrustList {
public enum Status { NEW, UPDATING, UPDATED, UPDATE_FAILED }
private final Persona persona
private final Set<Persona> good, bad
volatile long timestamp
volatile boolean forceUpdate
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
}
}

View File

@@ -0,0 +1,161 @@
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
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
private final MuWireSettings settings
private final Map<Destination, RemoteTrustList> remoteTrustLists = new ConcurrentHashMap<>()
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
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()
updateThreads.shutdownNow()
}
void onTrustSubscriptionEvent(TrustSubscriptionEvent e) {
if (!e.subscribe) {
remoteTrustLists.remove(e.persona.destination)
} else {
RemoteTrustList trustList = remoteTrustLists.putIfAbsent(e.persona.destination, new RemoteTrustList(e.persona))
trustList?.forceUpdate = true
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 (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))
}
}
} catch (InterruptedException e) {
if (!shutdown)
throw e
}
}
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))
if (check(trustList, System.currentTimeMillis()))
trustList.status = RemoteTrustList.Status.UPDATED
else
trustList.status = RemoteTrustList.Status.UPDATE_FAILED
eventBus.publish(new TrustSubscriptionUpdatedEvent(trustList : trustList))
}
}
private boolean check(RemoteTrustList trustList, long now) {
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 false
}
// swallow any headers
String header
while (( header = DataUtil.readTillRN(is)) != "");
DataInputStream dis = new DataInputStream(is)
Set<Persona> good = new HashSet<>()
int nGood = dis.readUnsignedShort()
for (int i = 0; i < nGood; i++) {
Persona p = new Persona(dis)
good.add(p)
}
Set<Persona> 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)
return true
} catch (Exception e) {
log.log(Level.WARNING,"exception fetching trust list from ${trustList.persona.getHumanReadableName()}",e)
return false
} finally {
endpoint?.close()
}
}
}

View File

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

View File

@@ -0,0 +1,7 @@
package com.muwire.core.trust
import com.muwire.core.Event
class TrustSubscriptionUpdatedEvent extends Event {
RemoteTrustList trustList
}

View File

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

View File

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

View File

@@ -11,9 +11,11 @@ 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
import com.muwire.core.Persona
import com.muwire.core.SharedFile
import com.muwire.core.download.DownloadStartedEvent
import com.muwire.core.download.UIDownloadCancelledEvent
@@ -24,8 +26,10 @@ 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
@ArtifactProviderFor(GriffonController)
class MainFrameController {
@@ -184,30 +188,96 @@ 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
builder.getVariable(tableName).model.fireTableDataChanged()
core.eventBus.publish(new TrustEvent(persona : list[row], level : level))
}
@ControllerAction
void markTrusted() {
markTrust("distrusted-table", TrustLevel.TRUSTED, model.distrusted)
model.markTrustedButtonEnabled = false
model.markNeutralFromDistrustedButtonEnabled = false
}
@ControllerAction
void markNeutralFromDistrusted() {
markTrust("distrusted-table", TrustLevel.NEUTRAL, model.distrusted)
model.markTrustedButtonEnabled = false
model.markNeutralFromDistrustedButtonEnabled = false
}
@ControllerAction
void markDistrusted() {
markTrust("trusted-table", TrustLevel.DISTRUSTED, model.trusted)
model.subscribeButtonEnabled = false
model.markDistrustedButtonEnabled = false
model.markNeutralFromTrustedButtonEnabled = false
}
@ControllerAction
void markNeutralFromTrusted() {
markTrust("trusted-table", TrustLevel.NEUTRAL, model.trusted)
model.subscribeButtonEnabled = false
model.markDistrustedButtonEnabled = false
model.markNeutralFromTrustedButtonEnabled = false
}
@ControllerAction
void subscribe() {
int row = view.getSelectedTrustTablesRow("trusted-table")
if (row < 0)
return
Persona p = model.trusted[row]
core.muOptions.trustSubscriptions.add(p)
saveMuWireSettings()
core.eventBus.publish(new TrustSubscriptionEvent(persona : p, subscribe : true))
model.subscribeButtonEnabled = false
model.markDistrustedButtonEnabled = false
model.markNeutralFromTrustedButtonEnabled = false
}
@ControllerAction
void review() {
RemoteTrustList list = getSelectedTrustList()
if (list == null)
return
Map<String,Object> env = new HashMap<>()
env["trustList"] = list
env["trustService"] = core.trustService
env["eventBus"] = core.eventBus
mvcGroup.createMVCGroup("trust-list", env)
}
@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() {

View File

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

View File

@@ -0,0 +1,62 @@
package com.muwire.gui
import griffon.core.artifact.GriffonController
import griffon.core.controller.ControllerAction
import griffon.inject.MVCMember
import griffon.metadata.ArtifactProviderFor
import javax.annotation.Nonnull
import com.muwire.core.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))
view.fireUpdate("trusted-table")
}
@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))
view.fireUpdate("distrusted-table")
}
@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))
view.fireUpdate("trusted-table")
}
@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))
view.fireUpdate("distrusted-table")
}
}

View File

@@ -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
@@ -73,6 +76,14 @@ class MainFrameModel {
@Observable boolean retryButtonEnabled
@Observable boolean pauseButtonEnabled
@Observable String resumeButtonText
@Observable boolean subscribeButtonEnabled
@Observable boolean markNeutralFromTrustedButtonEnabled
@Observable boolean markDistrustedButtonEnabled
@Observable boolean markNeutralFromDistrustedButtonEnabled
@Observable boolean markTrustedButtonEnabled
@Observable boolean reviewButtonEnabled
@Observable boolean updateButtonEnabled
@Observable boolean unsubscribeButtonEnabled
private final Set<InfoHash> infoHashes = new HashSet<>()
@@ -145,6 +156,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 +185,6 @@ class MainFrameModel {
trusted.addAll(core.trustService.good.values())
distrusted.addAll(core.trustService.bad.values())
resumeButtonText = "Retry"
}
})
@@ -185,6 +196,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 +331,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

View File

@@ -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<String, String> 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)
}
}

View File

@@ -0,0 +1,22 @@
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
import griffon.metadata.ArtifactProviderFor
@ArtifactProviderFor(GriffonModel)
class TrustListModel {
RemoteTrustList trustList
TrustService trustService
def trusted
def distrusted
void mvcGroupInit(Map<String,String> args) {
trusted = new ArrayList<>(trustList.good)
distrusted = new ArrayList<>(trustList.bad)
}
}

View File

@@ -44,7 +44,7 @@ class I2PStatusView {
label(text : "Participating Tunnels", constraints : gbc(gridx:0, gridy:4))
label(text : bind {model.participatingTunnels}, constraints : gbc(gridx: 1, gridy:4))
label(text : "Participating Bandwidth", constraints : gbc(gridx:0, gridy:5))
label(text : bind {model.participatingBW}, constraints : gbc(gridx: 1, gridy:6))
label(text : bind {model.participatingBW}, constraints : gbc(gridx: 1, gridy:5))
label(text : "Active Peers", constraints : gbc(gridx:0, gridy:6))
label(text : bind {model.activePeers}, constraints : gbc(gridx: 1, gridy:6))
label(text : "Receive Bps (15 seconds)", constraints : gbc(gridx:0, gridy:7))
@@ -77,4 +77,4 @@ class I2PStatusView {
})
dialog.show()
}
}
}

View File

@@ -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
@@ -54,6 +55,7 @@ class MainFrameView {
def lastDownloadSortEvent
def lastSharedSortEvent
def lastWatchedSortEvent
def trustTablesSortEvents = [:]
void initUI() {
UISettings settings = application.context.get("ui-settings")
@@ -251,35 +253,66 @@ 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.SOUTH) {
gridBagLayout()
button(text : "Subscribe", enabled : bind {model.subscribeButtonEnabled}, constraints : gbc(gridx: 0, gridy : 0), subscribeAction)
button(text : "Mark Neutral", enabled : bind {model.markNeutralFromTrustedButtonEnabled}, constraints : gbc(gridx: 1, gridy: 0), markNeutralFromTrustedAction)
button(text : "Mark Distrusted", enabled : bind {model.markDistrustedButtonEnabled}, constraints : gbc(gridx: 2, gridy:0), 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.SOUTH) {
gridBagLayout()
button(text: "Mark Neutral", enabled : bind {model.markNeutralFromDistrustedButtonEnabled}, constraints: gbc(gridx: 0, gridy: 0), markNeutralFromDistrustedAction)
button(text: "Mark Trusted", enabled : bind {model.markTrustedButtonEnabled}, constraints : gbc(gridx: 1, gridy : 0), 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", preferredWidth: 200, type: String, read : {it.persona.getHumanReadableName()})
closureColumn(header : "Trusted", preferredWidth : 20, type: Integer, read : {it.good.size()})
closureColumn(header : "Distrusted", preferredWidth: 20, type: Integer, read : {it.bad.size()})
closureColumn(header : "Status", preferredWidth: 30, type: String, read : {it.status.toString()})
closureColumn(header : "Last Updated", preferredWidth: 200, type : String, read : {
if (it.timestamp == 0)
return "Never"
else
return 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", enabled : bind {model.reviewButtonEnabled}, reviewAction)
button(text : "Update", enabled : bind {model.updateButtonEnabled}, updateAction)
button(text : "Unsubscribe", enabled : bind {model.unsubscribeButtonEnabled}, unsubscribeAction)
}
}
}
@@ -426,6 +459,79 @@ class MainFrameView {
}
})
// subscription table
def subscriptionTable = builder.getVariable("subscription-table")
subscriptionTable.setDefaultRenderer(Integer.class, centerRenderer)
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
case RemoteTrustList.Status.UPDATE_FAILED:
model.reviewButtonEnabled = false
model.updateButtonEnabled = true
model.unsubscribeButtonEnabled = true
break
}
})
// trusted table
def trustedTable = builder.getVariable("trusted-table")
trustedTable.rowSorter.addRowSorterListener({evt -> trustTablesSortEvents["trusted-table"] = evt})
trustedTable.rowSorter.setSortsOnUpdates(true)
selectionModel = trustedTable.getSelectionModel()
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
selectionModel.addListSelectionListener({
int selectedRow = getSelectedTrustTablesRow("trusted-table")
if (selectedRow < 0) {
model.subscribeButtonEnabled = false
model.markDistrustedButtonEnabled = false
model.markNeutralFromTrustedButtonEnabled = false
} else {
model.subscribeButtonEnabled = true
model.markDistrustedButtonEnabled = true
model.markNeutralFromTrustedButtonEnabled = true
}
})
// distrusted table
def distrustedTable = builder.getVariable("distrusted-table")
distrustedTable.rowSorter.addRowSorterListener({evt -> trustTablesSortEvents["distrusted-table"] = evt})
distrustedTable.rowSorter.setSortsOnUpdates(true)
selectionModel = distrustedTable.getSelectionModel()
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
selectionModel.addListSelectionListener({
int selectedRow = getSelectedTrustTablesRow("distrusted-table")
if (selectedRow < 0) {
model.markTrustedButtonEnabled = false
model.markNeutralFromDistrustedButtonEnabled = false
} else {
model.markTrustedButtonEnabled = true
model.markNeutralFromDistrustedButtonEnabled = true
}
})
}
private static void showPopupMenu(JPopupMenu menu, MouseEvent event) {
@@ -590,4 +696,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
}
}

View File

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

View File

@@ -0,0 +1,120 @@
package com.muwire.gui
import griffon.core.artifact.GriffonView
import griffon.inject.MVCMember
import griffon.metadata.ArtifactProviderFor
import javax.swing.JDialog
import javax.swing.ListSelectionModel
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 mainPanel
def sortEvents = [:]
void initUI() {
mainFrame = application.windowManager.findWindow("main-frame")
dialog = new JDialog(mainFrame, model.trustList.persona.getHumanReadableName(), true)
mainPanel = builder.panel {
borderLayout()
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<String,String> args) {
def trustedTable = builder.getVariable("trusted-table")
trustedTable.rowSorter.addRowSorterListener({evt -> sortEvents["trusted-table"] = evt})
trustedTable.rowSorter.setSortsOnUpdates(true)
trustedTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
def distrustedTable = builder.getVariable("distrusted-table")
distrustedTable.rowSorter.addRowSorterListener({evt -> sortEvents["distrusted-table"] = evt})
distrustedTable.rowSorter.setSortsOnUpdates(true)
distrustedTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
dialog.getContentPane().add(mainPanel)
dialog.pack()
dialog.setLocationRelativeTo(mainFrame)
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
dialog.addWindowListener(new WindowAdapter() {
public void windowClosed(WindowEvent e) {
mvcGroup.destroy()
}
})
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
}
void fireUpdate(String tableName) {
def table = builder.getVariable(tableName)
table.model.fireTableDataChanged()
}
}

View File

@@ -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!')
}
}

View File

@@ -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!')
}
}