Compare commits

...

34 Commits

Author SHA1 Message Date
Zlatin Balevsky
5a54b2dcda shift focus to search pane on search 2019-07-10 22:33:21 +01:00
Zlatin Balevsky
581293b24f column sizes 2019-07-10 22:27:07 +01:00
Zlatin Balevsky
cd072b9f76 enable/disable download button correctly 2019-07-10 22:23:20 +01:00
Zlatin Balevsky
6b74fc5956 fix trust/distrust buttons 2019-07-10 22:17:32 +01:00
Zlatin Balevsky
3de2f872bb show results per sender 2019-07-10 22:08:18 +01:00
Zlatin Balevsky
fcde917d08 fix context menu and double-click 2019-07-10 21:26:13 +01:00
Zlatin Balevsky
4ded065010 move buttons onto search result tab 2019-07-10 21:23:00 +01:00
Zlatin Balevsky
18a1c7091a move downloads to their own pane 2019-07-10 20:54:45 +01:00
Zlatin Balevsky
46aee19f80 disable the button of the currently open pane 2019-07-10 20:37:09 +01:00
Zlatin Balevsky
92dd7064c6 Release 0.4.9 2019-07-10 12:02:36 +01:00
Zlatin Balevsky
b2e4dda677 rearrange tables 2019-07-10 11:55:06 +01:00
Zlatin Balevsky
e77a2c8961 clear hits table on refresh 2019-07-09 21:42:52 +01:00
Zlatin Balevsky
ee2fd2ef68 single hit per search uuid 2019-07-09 21:22:31 +01:00
Zlatin Balevsky
3f95d2bf1d trust and distrust buttons 2019-07-09 21:15:08 +01:00
Zlatin Balevsky
1390983732 populate hits table 2019-07-09 21:05:49 +01:00
Zlatin Balevsky
ce660cefe9 deleting of rules 2019-07-09 20:50:07 +01:00
Zlatin Balevsky
72b81eb886 fix matching 2019-07-09 20:27:28 +01:00
Zlatin Balevsky
57d593a68a persist watched keywords and regexes 2019-07-09 20:11:29 +01:00
Zlatin Balevsky
39a81a3376 hook up rule creation 2019-07-09 19:53:40 +01:00
Zlatin Balevsky
fd0bf17c24 add ability to unregister event listeners 2019-07-09 19:53:08 +01:00
Zlatin Balevsky
ac12bff69b wip on content control panel ui 2019-07-09 19:20:06 +01:00
Zlatin Balevsky
feef773bac hook up content control panel to rest of UI 2019-07-09 17:55:36 +01:00
Zlatin Balevsky
239d8f12a7 wip on core side of content management 2019-07-09 17:13:09 +01:00
Zlatin Balevsky
8bbc61a7cb add settings for watched keywords and regexes 2019-07-09 16:50:51 +01:00
Zlatin Balevsky
7f31c4477f matchers for keywords 2019-07-09 11:47:55 +01:00
Zlatin Balevsky
6bad67c1bf Release 0.4.8 2019-07-08 18:30:19 +01:00
Zlatin Balevsky
c76e6dc99f Merge pull request #9 from zetok/backticks
Replace deprecated backticks with $() for command substitution
2019-07-08 08:24:37 +01:00
Zetok Zalbavar
acf9db0db3 Replace deprecated backticks with $() for command substitution
Although it's a Bash FAQ, the point also applies to POSIX-compatible
shells: https://mywiki.wooledge.org/BashFAQ/082
2019-07-08 06:29:33 +01:00
Zlatin Balevsky
69b4f0b547 Add trust/distrust action from monitor window. Thanks Aegon 2019-07-07 15:31:21 +01:00
Zlatin Balevsky
80e165b505 fix download size in renderer, thanks Aegon 2019-07-07 11:17:56 +01:00
Zlatin Balevsky
bcce55b873 fix integer overflow 2019-07-07 10:58:39 +01:00
Zlatin Balevsky
d5c92560db fix integer overflow 2019-07-07 10:56:14 +01:00
Zlatin Balevsky
f827c1c9bf Home directories for different OSes 2019-07-07 09:14:13 +01:00
Zlatin Balevsky
88c5f1a02d Add GPG key link 2019-07-07 09:04:52 +01:00
28 changed files with 841 additions and 142 deletions

View File

@@ -25,7 +25,11 @@ Some of the UI tests will fail because they haven't been written yet :-/
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 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 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 `i2p.properties` and put `i2cp.tcp.host=<host>` and `i2cp.tcp.port=<port>` in there. On Windows that file should go into `%HOME%\AppData\Roaming\MuWire`, on Mac into `$HOME/Library/Application Support/MuWire` and on Linux `$HOME/.MuWire`
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.
### GPG Fingerprint
471B 9FD4 5517 A5ED 101F C57D A728 3207 2D52 5E41
You can find the full key at https://keybase.io/zlatinb

View File

@@ -35,7 +35,7 @@ class Cli {
Core core
try {
core = new Core(props, home, "0.4.7")
core = new Core(props, home, "0.4.9")
} 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.7")
core = new Core(props, home, "0.4.9")
} catch (Exception bad) {
bad.printStackTrace(System.out)
println "Failed to initialize core, exiting"

View File

@@ -48,6 +48,8 @@ import com.muwire.core.trust.TrustSubscriptionEvent
import com.muwire.core.update.UpdateClient
import com.muwire.core.upload.UploadManager
import com.muwire.core.util.MuWireLogManager
import com.muwire.core.content.ContentControlEvent
import com.muwire.core.content.ContentManager
import groovy.util.logging.Log
import net.i2p.I2PAppContext
@@ -90,6 +92,7 @@ public class Core {
private final DirectoryWatcher directoryWatcher
final FileManager fileManager
final UploadManager uploadManager
final ContentManager contentManager
private final Router router
@@ -289,6 +292,11 @@ public class Core {
trustSubscriber = new TrustSubscriber(eventBus, i2pConnector, props)
eventBus.register(UILoadedEvent.class, trustSubscriber)
eventBus.register(TrustSubscriptionEvent.class, trustSubscriber)
log.info("initializing content manager")
contentManager = new ContentManager()
eventBus.register(ContentControlEvent.class, contentManager)
eventBus.register(QueryEvent.class, contentManager)
}
public void startServices() {
@@ -353,7 +361,7 @@ public class Core {
}
}
Core core = new Core(props, home, "0.4.7")
Core core = new Core(props, home, "0.4.9")
core.startServices()
// ... at the end, sleep or execute script

View File

@@ -48,4 +48,9 @@ class EventBus {
}
currentHandlers.add handler
}
synchronized void unregister(Class<? extends Event> eventType, def handler) {
log.info("Unregistering $handler for type $eventType")
handlers[eventType]?.remove(handler)
}
}

View File

@@ -28,6 +28,8 @@ class MuWireSettings {
int meshExpiration
boolean embeddedRouter
int inBw, outBw
Set<String> watchedKeywords
Set<String> watchedRegexes
MuWireSettings() {
this(new Properties())
@@ -54,11 +56,9 @@ class MuWireSettings {
inBw = Integer.valueOf(props.getProperty("inBw","256"))
outBw = Integer.valueOf(props.getProperty("outBw","128"))
watchedDirectories = new HashSet<>()
if (props.containsKey("watchedDirectories")) {
String[] encoded = props.getProperty("watchedDirectories").split(",")
encoded.each { watchedDirectories << DataUtil.readi18nString(Base64.decode(it)) }
}
watchedDirectories = readEncodedSet(props, "watchedDirectories")
watchedKeywords = readEncodedSet(props, "watchedKeywords")
watchedRegexes = readEncodedSet(props, "watchedRegexes")
trustSubscriptions = new HashSet<>()
if (props.containsKey("trustSubscriptions")) {
@@ -66,6 +66,8 @@ class MuWireSettings {
trustSubscriptions.add(new Persona(new ByteArrayInputStream(Base64.decode(it))))
}
}
}
void write(OutputStream out) throws IOException {
@@ -89,12 +91,9 @@ class MuWireSettings {
props.setProperty("inBw", String.valueOf(inBw))
props.setProperty("outBw", String.valueOf(outBw))
if (!watchedDirectories.isEmpty()) {
String encoded = watchedDirectories.stream().
map({Base64.encode(DataUtil.encodei18nString(it))}).
collect(Collectors.joining(","))
props.setProperty("watchedDirectories", encoded)
}
writeEncodedSet(watchedDirectories, "watchedDirectories", props)
writeEncodedSet(watchedKeywords, "watchedKeywords", props)
writeEncodedSet(watchedRegexes, "watchedRegexes", props)
if (!trustSubscriptions.isEmpty()) {
String encoded = trustSubscriptions.stream().
@@ -105,6 +104,24 @@ class MuWireSettings {
props.store(out, "")
}
private static Set<String> readEncodedSet(Properties props, String property) {
Set<String> rv = new HashSet<>()
if (props.containsKey(property)) {
String[] encoded = props.getProperty(property).split(",")
encoded.each { rv << DataUtil.readi18nString(Base64.decode(it)) }
}
rv
}
private static void writeEncodedSet(Set<String> set, String property, Properties props) {
if (set.isEmpty())
return
String encoded = set.stream().
map({Base64.encode(DataUtil.encodei18nString(it))}).
collect(Collectors.joining(","))
props.setProperty(property, encoded)
}
boolean isLeaf() {
isLeaf

View File

@@ -0,0 +1,9 @@
package com.muwire.core.content
import com.muwire.core.Event
class ContentControlEvent extends Event {
String term
boolean regex
boolean add
}

View File

@@ -0,0 +1,30 @@
package com.muwire.core.content
import java.util.concurrent.ConcurrentHashMap
import com.muwire.core.search.QueryEvent
import net.i2p.util.ConcurrentHashSet
class ContentManager {
Set<Matcher> matchers = new ConcurrentHashSet()
void onContentControlEvent(ContentControlEvent e) {
Matcher m
if (e.regex)
m = new RegexMatcher(e.term)
else
m = new KeywordMatcher(e.term)
if (e.add)
matchers.add(m)
else
matchers.remove(m)
}
void onQueryEvent(QueryEvent e) {
if (e.searchEvent.searchTerms == null)
return
matchers.each { it.process(e) }
}
}

View File

@@ -0,0 +1,36 @@
package com.muwire.core.content
class KeywordMatcher extends Matcher {
private final String keyword
KeywordMatcher(String keyword) {
this.keyword = keyword
}
@Override
protected boolean match(List<String> searchTerms) {
boolean found = false
searchTerms.each {
if (keyword == it)
found = true
}
found
}
@Override
public String getTerm() {
keyword
}
@Override
public int hashCode() {
keyword.hashCode()
}
@Override
public boolean equals(Object o) {
if (!(o instanceof KeywordMatcher))
return false
KeywordMatcher other = (KeywordMatcher) o
keyword.equals(other.keyword)
}
}

View File

@@ -0,0 +1,9 @@
package com.muwire.core.content
import com.muwire.core.Persona
class Match {
Persona persona
String [] keywords
long timestamp
}

View File

@@ -0,0 +1,20 @@
package com.muwire.core.content
import com.muwire.core.search.QueryEvent
abstract class Matcher {
final List<Match> matches = Collections.synchronizedList(new ArrayList<>())
final Set<UUID> uuids = new HashSet<>()
protected abstract boolean match(List<String> searchTerms);
public abstract String getTerm();
public void process(QueryEvent qe) {
def terms = qe.searchEvent.searchTerms
if (match(terms) && uuids.add(qe.searchEvent.uuid)) {
long now = System.currentTimeMillis()
matches << new Match(persona : qe.originator, keywords : terms, timestamp : now)
}
}
}

View File

@@ -0,0 +1,35 @@
package com.muwire.core.content
import java.util.regex.Pattern
import java.util.stream.Collectors
class RegexMatcher extends Matcher {
private final Pattern pattern
RegexMatcher(String pattern) {
this.pattern = Pattern.compile(pattern)
}
@Override
protected boolean match(List<String> keywords) {
String combined = keywords.join(" ")
return pattern.matcher(combined).find()
}
@Override
public String getTerm() {
pattern.pattern()
}
@Override
public int hashCode() {
pattern.pattern().hashCode()
}
@Override
public boolean equals(Object o) {
if (!(o instanceof RegexMatcher))
return false
RegexMatcher other = (RegexMatcher) o
pattern.pattern() == other.pattern.pattern()
}
}

View File

@@ -84,7 +84,7 @@ class DownloadSession {
log.info("will download piece $piece from position $position steal $steal")
long pieceStart = piece * pieceSize
long pieceStart = piece * ((long)pieceSize)
long end = Math.min(fileLength, pieceStart + pieceSize) - 1
long start = pieceStart + position
String root = Base64.encode(infoHash.getRoot())

View File

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

34
gradlew vendored
View File

@@ -11,21 +11,21 @@
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
ls=$(ls -ld "$PRG")
link=$(expr "$ls" : '.*-> \(.*\)$')
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
PRG=$(dirname "$PRG")"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
SAVED="$(pwd)"
cd "$(dirname "$PRG")/" >/dev/null
APP_HOME="$(pwd -P)"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
APP_BASE_NAME=$(basename "$0")
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
@@ -49,7 +49,7 @@ cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
case "$(uname)" in
CYGWIN* )
cygwin=true
;;
@@ -90,7 +90,7 @@ fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
MAX_FD_LIMIT=$(ulimit -H -n)
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
@@ -111,12 +111,12 @@ fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
APP_HOME=$(cygpath --path --mixed "$APP_HOME")
CLASSPATH=$(cygpath --path --mixed "$CLASSPATH")
JAVACMD=$(cygpath --unix "$JAVACMD")
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
ROOTDIRSRAW=$(find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null)
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
@@ -130,13 +130,13 @@ if $cygwin ; then
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
CHECK=$(echo "$arg"|egrep -c "$OURCYGPATTERN" -)
CHECK2=$(echo "$arg"|egrep -c "^-") ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
eval $(echo args$i)=$(cygpath --path --ignore --mixed "$arg")
else
eval `echo args$i`="\"$arg\""
eval $(echo args$i)="\"$arg\""
fi
i=$((i+1))
done

View File

@@ -41,4 +41,9 @@ mvcGroups {
view = 'com.muwire.gui.TrustListView'
controller = 'com.muwire.gui.TrustListController'
}
'content-panel' {
model = 'com.muwire.gui.ContentPanelModel'
view = 'com.muwire.gui.ContentPanelView'
controller = 'com.muwire.gui.ContentPanelController'
}
}

View File

@@ -0,0 +1,95 @@
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.Core
import com.muwire.core.EventBus
import com.muwire.core.content.ContentControlEvent
import com.muwire.core.content.Match
import com.muwire.core.content.Matcher
import com.muwire.core.content.RegexMatcher
import com.muwire.core.trust.TrustEvent
import com.muwire.core.trust.TrustLevel
@ArtifactProviderFor(GriffonController)
class ContentPanelController {
@MVCMember @Nonnull
ContentPanelModel model
@MVCMember @Nonnull
ContentPanelView view
Core core
@ControllerAction
void addRule() {
def term = view.ruleTextField.text
if (model.regex)
core.muOptions.watchedRegexes.add(term)
else
core.muOptions.watchedKeywords.add(term)
saveMuWireSettings()
core.eventBus.publish(new ContentControlEvent(term : term, regex : model.regex, add:true))
}
@ControllerAction
void deleteRule() {
int rule = view.getSelectedRule()
if (rule < 0)
return
Matcher matcher = model.rules[rule]
String term = matcher.getTerm()
if (matcher instanceof RegexMatcher)
core.muOptions.watchedRegexes.remove(term)
else
core.muOptions.watchedKeywords.remove(term)
saveMuWireSettings()
core.eventBus.publish(new ContentControlEvent(term : term, regex : (matcher instanceof RegexMatcher), add: false))
}
@ControllerAction
void keyword() {
model.regex = false
}
@ControllerAction
void regex() {
model.regex = true
}
@ControllerAction
void refresh() {
model.refresh()
}
@ControllerAction
void trust() {
int selectedHit = view.getSelectedHit()
if (selectedHit < 0)
return
Match m = model.hits[selectedHit]
core.eventBus.publish(new TrustEvent(persona : m.persona, level : TrustLevel.TRUSTED))
}
@ControllerAction
void distrust() {
int selectedHit = view.getSelectedHit()
if (selectedHit < 0)
return
Match m = model.hits[selectedHit]
core.eventBus.publish(new TrustEvent(persona : m.persona, level : TrustLevel.DISTRUSTED))
}
void saveMuWireSettings() {
File f = new File(core.home, "MuWire.properties")
f.withOutputStream {
core.muOptions.write(it)
}
}
}

View File

@@ -59,6 +59,7 @@ class MainFrameController {
Map<String, Object> params = new HashMap<>()
params["search-terms"] = search
params["uuid"] = uuid.toString()
params["core"] = core
def group = mvcGroup.createMVCGroup("SearchTab", uuid.toString(), params)
model.results[uuid.toString()] = group
@@ -96,6 +97,7 @@ class MainFrameController {
Map<String, Object> params = new HashMap<>()
params["search-terms"] = tabTitle
params["uuid"] = uuid.toString()
params["core"] = core
def group = mvcGroup.createMVCGroup("SearchTab", uuid.toString(), params)
model.results[uuid.toString()] = group
@@ -106,20 +108,6 @@ class MainFrameController {
originator : core.me))
}
private def selectedResult() {
def selected = builder.getVariable("result-tabs").getSelectedComponent()
def group = selected.getClientProperty("mvc-group")
def table = selected.getClientProperty("results-table")
int row = table.getSelectedRow()
if (row == -1)
return
def sortEvt = group.view.lastSortEvent
if (sortEvt != null) {
row = group.view.resultsTable.rowSorter.convertRowIndexToModel(row)
}
group.model.results[row]
}
private int selectedDownload() {
def downloadsTable = builder.getVariable("downloads-table")
def selected = downloadsTable.getSelectedRow()
@@ -130,39 +118,21 @@ class MainFrameController {
}
@ControllerAction
void download() {
def result = selectedResult()
if (result == null)
void trustPersonaFromSearch() {
int selected = builder.getVariable("searches-table").getSelectedRow()
if (selected < 0)
return
if (!model.canDownload(result.infohash))
return
def file = new File(application.context.get("muwire-settings").downloadLocation, result.name)
def selected = builder.getVariable("result-tabs").getSelectedComponent()
def group = selected.getClientProperty("mvc-group")
def resultsBucket = group.model.hashBucket[result.infohash]
def sources = group.model.sourcesBucket[result.infohash]
core.eventBus.publish(new UIDownloadEvent(result : resultsBucket, sources: sources, target : file))
Persona p = model.searches[selected].originator
core.eventBus.publish( new TrustEvent(persona : p, level : TrustLevel.TRUSTED) )
}
@ControllerAction
void trust() {
def result = selectedResult()
if (result == null)
return // TODO disable button
core.eventBus.publish( new TrustEvent(persona : result.sender, level : TrustLevel.TRUSTED))
}
@ControllerAction
void distrust() {
def result = selectedResult()
if (result == null)
return // TODO disable button
core.eventBus.publish( new TrustEvent(persona : result.sender, level : TrustLevel.DISTRUSTED))
void distrustPersonaFromSearch() {
int selected = builder.getVariable("searches-table").getSelectedRow()
if (selected < 0)
return
Persona p = model.searches[selected].originator
core.eventBus.publish( new TrustEvent(persona : p, level : TrustLevel.DISTRUSTED) )
}
@ControllerAction

View File

@@ -6,6 +6,65 @@ import griffon.inject.MVCMember
import griffon.metadata.ArtifactProviderFor
import javax.annotation.Nonnull
import com.muwire.core.Core
import com.muwire.core.download.UIDownloadEvent
import com.muwire.core.trust.TrustEvent
import com.muwire.core.trust.TrustLevel
@ArtifactProviderFor(GriffonController)
class SearchTabController {
@MVCMember @Nonnull
SearchTabModel model
@MVCMember @Nonnull
SearchTabView view
Core core
private def selectedResult() {
int row = view.resultsTable.getSelectedRow()
if (row == -1)
return null
def sortEvt = view.lastSortEvent
if (sortEvt != null) {
row = view.resultsTable.rowSorter.convertRowIndexToModel(row)
}
model.results[row]
}
@ControllerAction
void download() {
def result = selectedResult()
if (result == null)
return
if (!mvcGroup.parentGroup.model.canDownload(result.infohash))
return
def file = new File(application.context.get("muwire-settings").downloadLocation, result.name)
def resultsBucket = model.hashBucket[result.infohash]
def sources = model.sourcesBucket[result.infohash]
core.eventBus.publish(new UIDownloadEvent(result : resultsBucket, sources: sources, target : file))
mvcGroup.parentGroup.view.showDownloadsWindow.call()
}
@ControllerAction
void trust() {
int row = view.selectedSenderRow()
if (row < 0)
return
def sender = model.senders[row]
core.eventBus.publish( new TrustEvent(persona : sender, level : TrustLevel.TRUSTED))
}
@ControllerAction
void distrust() {
int row = view.selectedSenderRow()
if (row < 0)
return
def sender = model.senders[row]
core.eventBus.publish( new TrustEvent(persona : sender, level : TrustLevel.DISTRUSTED))
}
}

View File

@@ -0,0 +1,55 @@
package com.muwire.gui
import javax.annotation.Nonnull
import com.muwire.core.Core
import com.muwire.core.EventBus
import com.muwire.core.content.ContentControlEvent
import com.muwire.core.content.ContentManager
import griffon.core.artifact.GriffonModel
import griffon.inject.MVCMember
import griffon.transform.Observable
import griffon.metadata.ArtifactProviderFor
@ArtifactProviderFor(GriffonModel)
class ContentPanelModel {
@MVCMember @Nonnull
ContentPanelView view
Core core
private ContentManager contentManager
def rules = []
def hits = []
@Observable boolean regex
@Observable boolean deleteButtonEnabled
@Observable boolean trustButtonsEnabled
void mvcGroupInit(Map<String,String> args) {
contentManager = application.context.get("core").contentManager
rules.addAll(contentManager.matchers)
core.eventBus.register(ContentControlEvent.class, this)
}
void mvcGroupDestroy() {
core.eventBus.unregister(ContentControlEvent.class, this)
}
void refresh() {
rules.clear()
rules.addAll(contentManager.matchers)
hits.clear()
view.rulesTable.model.fireTableDataChanged()
view.hitsTable.model.fireTableDataChanged()
}
void onContentControlEvent(ContentControlEvent e) {
runInsideUIAsync {
refresh()
}
}
}

View File

@@ -17,6 +17,7 @@ import com.muwire.core.RouterDisconnectedEvent
import com.muwire.core.connection.ConnectionAttemptStatus
import com.muwire.core.connection.ConnectionEvent
import com.muwire.core.connection.DisconnectionEvent
import com.muwire.core.content.ContentControlEvent
import com.muwire.core.download.DownloadStartedEvent
import com.muwire.core.download.Downloader
import com.muwire.core.files.AllFilesLoadedEvent
@@ -75,8 +76,6 @@ class MainFrameModel {
@Observable String me
@Observable int loadedFiles
@Observable File hashingFile
@Observable boolean downloadActionEnabled
@Observable boolean trustButtonsEnabled
@Observable boolean cancelButtonEnabled
@Observable boolean retryButtonEnabled
@Observable boolean pauseButtonEnabled
@@ -89,6 +88,12 @@ class MainFrameModel {
@Observable boolean reviewButtonEnabled
@Observable boolean updateButtonEnabled
@Observable boolean unsubscribeButtonEnabled
@Observable boolean searchesPaneButtonEnabled
@Observable boolean downloadsPaneButtonEnabled
@Observable boolean uploadsPaneButtonEnabled
@Observable boolean monitorPaneButtonEnabled
@Observable boolean trustPaneButtonEnabled
private final Set<InfoHash> infoHashes = new HashSet<>()
@@ -164,6 +169,13 @@ class MainFrameModel {
core.eventBus.register(UpdateDownloadedEvent.class, this)
core.eventBus.register(TrustSubscriptionUpdatedEvent.class, this)
core.muOptions.watchedKeywords.each {
core.eventBus.publish(new ContentControlEvent(term : it, regex: false, add: true))
}
core.muOptions.watchedRegexes.each {
core.eventBus.publish(new ContentControlEvent(term : it, regex: true, add: true))
}
timer.schedule({
if (core.shutdown.get())
return
@@ -192,6 +204,12 @@ class MainFrameModel {
distrusted.addAll(core.trustService.bad.values())
resumeButtonText = "Retry"
searchesPaneButtonEnabled = false
downloadsPaneButtonEnabled = true
uploadsPaneButtonEnabled = true
monitorPaneButtonEnabled = true
trustPaneButtonEnabled = true
}
})

View File

@@ -5,6 +5,7 @@ import javax.inject.Inject
import javax.swing.JTable
import com.muwire.core.Core
import com.muwire.core.Persona
import com.muwire.core.search.UIResultEvent
import griffon.core.artifact.GriffonModel
@@ -17,14 +18,19 @@ import griffon.metadata.ArtifactProviderFor
class SearchTabModel {
@MVCMember @Nonnull
FactoryBuilderSupport builder
@Observable boolean downloadActionEnabled
@Observable boolean trustButtonsEnabled
Core core
UISettings uiSettings
String uuid
def senders = []
def results = []
def hashBucket = [:]
def sourcesBucket = [:]
def sendersBucket = new LinkedHashMap<>()
void mvcGroupInit(Map<String, String> args) {
core = mvcGroup.parentGroup.model.core
@@ -48,6 +54,15 @@ class SearchTabModel {
}
bucket << e
def senderBucket = sendersBucket.get(e.sender)
if (senderBucket == null) {
senderBucket = []
sendersBucket[e.sender] = senderBucket
senders.clear()
senders.addAll(sendersBucket.keySet())
}
senderBucket << e
Set sourceBucket = sourcesBucket.get(e.infohash)
if (sourceBucket == null) {
sourceBucket = new HashSet()
@@ -55,8 +70,7 @@ class SearchTabModel {
}
sourceBucket.addAll(e.sources)
results << e
JTable table = builder.getVariable("results-table")
JTable table = builder.getVariable("senders-table")
table.model.fireTableDataChanged()
}
}
@@ -72,6 +86,14 @@ class SearchTabModel {
bucket = []
hashBucket[it.infohash] = bucket
}
def senderBucket = sendersBucket.get(it.sender)
if (senderBucket == null) {
senderBucket = []
sendersBucket[it.sender] = senderBucket
senders.clear()
senders.addAll(sendersBucket.keySet())
}
Set sourceBucket = sourcesBucket.get(it.infohash)
if (sourceBucket == null) {
@@ -81,9 +103,9 @@ class SearchTabModel {
sourceBucket.addAll(it.sources)
bucket << it
results << it
senderBucket << it
}
JTable table = builder.getVariable("results-table")
JTable table = builder.getVariable("senders-table")
table.model.fireTableDataChanged()
}
}

View File

@@ -0,0 +1,154 @@
package com.muwire.gui
import griffon.core.artifact.GriffonView
import griffon.inject.MVCMember
import griffon.metadata.ArtifactProviderFor
import javax.swing.JDialog
import javax.swing.JLabel
import javax.swing.ListSelectionModel
import javax.swing.SwingConstants
import javax.swing.table.DefaultTableCellRenderer
import com.muwire.core.content.Matcher
import com.muwire.core.content.RegexMatcher
import java.awt.BorderLayout
import java.awt.event.WindowAdapter
import java.awt.event.WindowEvent
import javax.annotation.Nonnull
@ArtifactProviderFor(GriffonView)
class ContentPanelView {
@MVCMember @Nonnull
FactoryBuilderSupport builder
@MVCMember @Nonnull
ContentPanelModel model
def dialog
def mainFrame
def mainPanel
def rulesTable
def ruleTextField
def lastRulesSortEvent
def hitsTable
def lastHitsSortEvent
void initUI() {
mainFrame = application.windowManager.findWindow("main-frame")
dialog = new JDialog(mainFrame, "Content Control Panel", true)
mainPanel = builder.panel {
gridLayout(rows:1, cols:2)
panel {
borderLayout()
panel (constraints : BorderLayout.NORTH) {
label(text : "Rules")
}
scrollPane (constraints : BorderLayout.CENTER) {
rulesTable = table(id : "rules-table", autoCreateRowSorter : true) {
tableModel(list : model.rules) {
closureColumn(header: "Term", type:String, read: {row -> row.getTerm()})
closureColumn(header: "Regex?", type:Boolean, read: {row -> row instanceof RegexMatcher})
closureColumn(header: "Hits", type:Integer, read : {row -> row.matches.size()})
}
}
}
panel (constraints : BorderLayout.SOUTH) {
borderLayout()
ruleTextField = textField(constraints: BorderLayout.CENTER, action: addRuleAction)
panel (constraints: BorderLayout.EAST) {
buttonGroup(id : "ruleType")
radioButton(text: "Keyword", selected : true, buttonGroup: ruleType, keywordAction)
radioButton(text: "Regex", selected : false, buttonGroup: ruleType, regexAction)
button(text : "Add Rule", addRuleAction)
button(text : "Delete Rule", enabled : bind {model.deleteButtonEnabled}, deleteRuleAction)
}
}
}
panel (border : etchedBorder()){
borderLayout()
panel (constraints : BorderLayout.NORTH) {
label(text : "Hits")
}
scrollPane(constraints : BorderLayout.CENTER) {
hitsTable = table(id : "hits-table", autoCreateRowSorter : true) {
tableModel(list : model.hits) {
closureColumn(header : "Searcher", type : String, read : {row -> row.persona.getHumanReadableName()})
closureColumn(header : "Keywords", type : String, read : {row -> row.keywords.join(" ")})
closureColumn(header : "Date", type : String, read : {row -> String.valueOf(new Date(row.timestamp))})
}
}
}
panel (constraints : BorderLayout.SOUTH) {
button(text : "Refresh", refreshAction)
button(text : "Trust", enabled : bind {model.trustButtonsEnabled}, trustAction)
button(text : "Distrust", enabled : bind {model.trustButtonsEnabled}, distrustAction)
}
}
}
}
int getSelectedRule() {
int selectedRow = rulesTable.getSelectedRow()
if (selectedRow < 0)
return -1
if (lastRulesSortEvent != null)
selectedRow = rulesTable.rowSorter.convertRowIndexToModel(selectedRow)
selectedRow
}
int getSelectedHit() {
int selectedRow = hitsTable.getSelectedRow()
if (selectedRow < 0)
return -1
if (lastHitsSortEvent != null)
selectedRow = hitsTable.rowSorter.convertRowIndexToModel(selectedRow)
selectedRow
}
void mvcGroupInit(Map<String,String> args) {
def centerRenderer = new DefaultTableCellRenderer()
centerRenderer.setHorizontalAlignment(JLabel.CENTER)
rulesTable.setDefaultRenderer(Integer.class, centerRenderer)
rulesTable.rowSorter.addRowSorterListener({evt -> lastRulesSortEvent = evt})
rulesTable.rowSorter.setSortsOnUpdates(true)
def selectionModel = rulesTable.getSelectionModel()
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
selectionModel.addListSelectionListener({
int selectedRow = getSelectedRule()
if (selectedRow < 0) {
model.deleteButtonEnabled = false
return
} else {
model.deleteButtonEnabled = true
model.hits.clear()
Matcher matcher = model.rules[selectedRow]
model.hits.addAll(matcher.matches)
hitsTable.model.fireTableDataChanged()
}
})
hitsTable.rowSorter.addRowSorterListener({evt -> lastHitsSortEvent = evt})
hitsTable.rowSorter.setSortsOnUpdates(true)
selectionModel = hitsTable.getSelectionModel()
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
selectionModel.addListSelectionListener({
int selectedRow = getSelectedHit()
model.trustButtonsEnabled = selectedRow >= 0
})
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()
}
}

View File

@@ -26,7 +26,6 @@ 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
import java.awt.FlowLayout
@@ -73,6 +72,11 @@ class MainFrameView {
menuBar {
menu (text : "Options") {
menuItem("Configuration", actionPerformed : {mvcGroup.createMVCGroup("Options")})
menuItem("Content Control", actionPerformed : {
def env = [:]
env["core"] = model.core
mvcGroup.createMVCGroup("content-panel", env)
})
}
menu (text : "Status") {
menuItem("MuWire", actionPerformed : {mvcGroup.createMVCGroup("mu-wire-status")})
@@ -85,11 +89,12 @@ class MainFrameView {
borderLayout()
panel (constraints: BorderLayout.WEST) {
gridLayout(rows:1, cols: 2)
button(text: "Searches", actionPerformed : showSearchWindow)
button(text: "Uploads", actionPerformed : showUploadsWindow)
button(text: "Searches", enabled : bind{model.searchesPaneButtonEnabled},actionPerformed : showSearchWindow)
button(text: "Downloads", enabled : bind{model.downloadsPaneButtonEnabled}, actionPerformed : showDownloadsWindow)
button(text: "Uploads", enabled : bind{model.uploadsPaneButtonEnabled}, actionPerformed : showUploadsWindow)
if (settings.showMonitor)
button(text: "Monitor", actionPerformed : showMonitorWindow)
button(text: "Trust", actionPerformed : showTrustWindow)
button(text: "Monitor", enabled: bind{model.monitorPaneButtonEnabled},actionPerformed : showMonitorWindow)
button(text: "Trust", enabled:bind{model.trustPaneButtonEnabled},actionPerformed : showTrustWindow)
}
panel(id: "top-panel", constraints: BorderLayout.CENTER) {
cardLayout()
@@ -113,37 +118,38 @@ class MainFrameView {
cardLayout()
panel (constraints : "search window") {
borderLayout()
splitPane( orientation : JSplitPane.VERTICAL_SPLIT, dividerLocation : 500,
continuousLayout : true, constraints : BorderLayout.CENTER) {
panel (constraints : JSplitPane.TOP) {
borderLayout()
tabbedPane(id : "result-tabs", constraints: BorderLayout.CENTER)
panel(constraints : BorderLayout.SOUTH) {
button(text : "Download", enabled : bind {model.downloadActionEnabled}, downloadAction)
button(text : "Trust", enabled: bind {model.trustButtonsEnabled }, trustAction)
button(text : "Distrust", enabled : bind {model.trustButtonsEnabled}, distrustAction)
}
}
panel (constraints : JSplitPane.BOTTOM) {
borderLayout()
scrollPane (constraints : BorderLayout.CENTER) {
downloadsTable = table(id : "downloads-table", autoCreateRowSorter : true) {
tableModel(list: model.downloads) {
closureColumn(header: "Name", preferredWidth: 300, type: String, read : {row -> row.downloader.file.getName()})
closureColumn(header: "Status", preferredWidth: 50, type: String, read : {row -> row.downloader.getCurrentState().toString()})
closureColumn(header: "Progress", preferredWidth: 70, type: Downloader, read: { row -> row.downloader })
closureColumn(header: "Sources", preferredWidth : 10, type: Integer, read : {row -> row.downloader.activeWorkers()})
closureColumn(header: "Speed", preferredWidth: 50, type:String, read :{row ->
DataHelper.formatSize2Decimal(row.downloader.speed(), false) + "B/sec"
})
}
tabbedPane(id : "result-tabs", constraints: BorderLayout.CENTER)
}
panel (constraints: "downloads window") {
gridLayout(rows : 2, cols: 1)
panel {
borderLayout()
scrollPane (constraints : BorderLayout.CENTER) {
downloadsTable = table(id : "downloads-table", autoCreateRowSorter : true) {
tableModel(list: model.downloads) {
closureColumn(header: "Name", preferredWidth: 300, type: String, read : {row -> row.downloader.file.getName()})
closureColumn(header: "Status", preferredWidth: 50, type: String, read : {row -> row.downloader.getCurrentState().toString()})
closureColumn(header: "Progress", preferredWidth: 70, type: Downloader, read: { row -> row.downloader })
closureColumn(header: "Sources", preferredWidth : 10, type: Integer, read : {row -> row.downloader.activeWorkers()})
closureColumn(header: "Speed", preferredWidth: 50, type:String, read :{row ->
DataHelper.formatSize2Decimal(row.downloader.speed(), false) + "B/sec"
})
}
}
panel (constraints : BorderLayout.SOUTH) {
button(text: "Pause", enabled : bind {model.pauseButtonEnabled}, pauseAction)
button(text: "Cancel", enabled : bind {model.cancelButtonEnabled }, cancelAction )
button(text: bind { model.resumeButtonText }, enabled : bind {model.retryButtonEnabled}, resumeAction)
}
}
panel (constraints : BorderLayout.SOUTH) {
button(text: "Pause", enabled : bind {model.pauseButtonEnabled}, pauseAction)
button(text: "Cancel", enabled : bind {model.cancelButtonEnabled }, cancelAction )
button(text: bind { model.resumeButtonText }, enabled : bind {model.retryButtonEnabled}, resumeAction)
}
}
panel {
borderLayout()
panel(constraints : BorderLayout.NORTH) {
label(text : "Download Details")
}
panel(constraints : BorderLayout.CENTER) {
label(text : "Details go here...")
}
}
}
@@ -456,9 +462,18 @@ class MainFrameView {
// searches table
def searchesTable = builder.getVariable("searches-table")
JPopupMenu searchTableMenu = new JPopupMenu()
JMenuItem copySearchToClipboard = new JMenuItem("Copy search to clipboard")
copySearchToClipboard.addActionListener({mvcGroup.view.copySearchToClipboard(searchesTable)})
JMenuItem trustSearcher = new JMenuItem("Trust searcher")
trustSearcher.addActionListener({mvcGroup.controller.trustPersonaFromSearch()})
JMenuItem distrustSearcher = new JMenuItem("Distrust searcher")
distrustSearcher.addActionListener({mvcGroup.controller.distrustPersonaFromSearch()})
searchTableMenu.add(copySearchToClipboard)
searchTableMenu.add(trustSearcher)
searchTableMenu.add(distrustSearcher)
searchesTable.addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
@@ -677,21 +692,51 @@ class MainFrameView {
def showSearchWindow = {
def cardsPanel = builder.getVariable("cards-panel")
cardsPanel.getLayout().show(cardsPanel, "search window")
model.searchesPaneButtonEnabled = false
model.downloadsPaneButtonEnabled = true
model.uploadsPaneButtonEnabled = true
model.monitorPaneButtonEnabled = true
model.trustPaneButtonEnabled = true
}
def showDownloadsWindow = {
def cardsPanel = builder.getVariable("cards-panel")
cardsPanel.getLayout().show(cardsPanel, "downloads window")
model.searchesPaneButtonEnabled = true
model.downloadsPaneButtonEnabled = false
model.uploadsPaneButtonEnabled = true
model.monitorPaneButtonEnabled = true
model.trustPaneButtonEnabled = true
}
def showUploadsWindow = {
def cardsPanel = builder.getVariable("cards-panel")
cardsPanel.getLayout().show(cardsPanel, "uploads window")
model.searchesPaneButtonEnabled = true
model.downloadsPaneButtonEnabled = true
model.uploadsPaneButtonEnabled = false
model.monitorPaneButtonEnabled = true
model.trustPaneButtonEnabled = true
}
def showMonitorWindow = {
def cardsPanel = builder.getVariable("cards-panel")
cardsPanel.getLayout().show(cardsPanel,"monitor window")
model.searchesPaneButtonEnabled = true
model.downloadsPaneButtonEnabled = true
model.uploadsPaneButtonEnabled = true
model.monitorPaneButtonEnabled = false
model.trustPaneButtonEnabled = true
}
def showTrustWindow = {
def cardsPanel = builder.getVariable("cards-panel")
cardsPanel.getLayout().show(cardsPanel,"trust window")
model.searchesPaneButtonEnabled = true
model.downloadsPaneButtonEnabled = true
model.uploadsPaneButtonEnabled = true
model.monitorPaneButtonEnabled = true
model.trustPaneButtonEnabled = false
}
def shareFiles = {

View File

@@ -16,6 +16,7 @@ import javax.swing.ListSelectionModel
import javax.swing.SwingConstants
import javax.swing.table.DefaultTableCellRenderer
import com.muwire.core.Persona
import com.muwire.core.util.DataUtil
import java.awt.BorderLayout
@@ -37,23 +38,49 @@ class SearchTabView {
def pane
def parent
def searchTerms
def sendersTable
def lastSendersSortEvent
def resultsTable
def lastSortEvent
void initUI() {
builder.with {
def resultsTable
def pane = scrollPane {
resultsTable = table(id : "results-table", autoCreateRowSorter : true) {
tableModel(list: model.results) {
closureColumn(header: "Name", preferredWidth: 350, type: String, read : {row -> row.name.replace('<','_')})
closureColumn(header: "Size", preferredWidth: 20, type: Long, read : {row -> row.size})
closureColumn(header: "Direct Sources", preferredWidth: 50, type : Integer, read : { row -> model.hashBucket[row.infohash].size()})
closureColumn(header: "Possible Sources", preferredWidth : 50, type : Integer, read : {row -> model.sourcesBucket[row.infohash].size()})
closureColumn(header: "Sender", preferredWidth: 170, type: String, read : {row -> row.sender.getHumanReadableName()})
closureColumn(header: "Trust", preferredWidth: 50, type: String, read : {row ->
model.core.trustService.getLevel(row.sender.destination).toString()
})
def sendersTable
def pane = panel {
gridLayout(rows : 2, cols: 1)
panel {
borderLayout()
scrollPane (constraints : BorderLayout.CENTER) {
sendersTable = table(id : "senders-table", autoCreateRowSorter : true) {
tableModel(list : model.senders) {
closureColumn(header : "Sender", preferredWidth : 500, type: String, read : {row -> row.getHumanReadableName()})
closureColumn(header : "Results", preferredWidth : 20, type: Integer, read : {row -> model.sendersBucket[row].size()})
closureColumn(header : "Trust", preferredWidth : 50, type: String, read : { row ->
model.core.trustService.getLevel(row.destination).toString()
})
}
}
}
panel(constraints : BorderLayout.SOUTH) {
button(text : "Trust", enabled: bind {model.trustButtonsEnabled }, trustAction)
button(text : "Distrust", enabled : bind {model.trustButtonsEnabled}, distrustAction)
}
}
panel {
borderLayout()
scrollPane (constraints : BorderLayout.CENTER) {
resultsTable = table(id : "results-table", autoCreateRowSorter : true) {
tableModel(list: model.results) {
closureColumn(header: "Name", preferredWidth: 350, type: String, read : {row -> row.name.replace('<','_')})
closureColumn(header: "Size", preferredWidth: 20, type: Long, read : {row -> row.size})
closureColumn(header: "Direct Sources", preferredWidth: 50, type : Integer, read : { row -> model.hashBucket[row.infohash].size()})
closureColumn(header: "Possible Sources", preferredWidth : 50, type : Integer, read : {row -> model.sourcesBucket[row.infohash].size()})
}
}
}
panel(constraints : BorderLayout.SOUTH) {
button(text : "Download", enabled : bind {model.downloadActionEnabled}, downloadAction)
}
}
}
@@ -63,17 +90,19 @@ class SearchTabView {
this.pane.putClientProperty("results-table",resultsTable)
this.resultsTable = resultsTable
this.sendersTable = sendersTable
def selectionModel = resultsTable.getSelectionModel()
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
selectionModel.addListSelectionListener( {
int row = resultsTable.getSelectedRow()
if (row < 0)
if (row < 0) {
model.downloadActionEnabled = false
return
}
if (lastSortEvent != null)
row = resultsTable.rowSorter.convertRowIndexToModel(row)
mvcGroup.parentGroup.model.trustButtonsEnabled = true
mvcGroup.parentGroup.model.downloadActionEnabled = mvcGroup.parentGroup.model.canDownload(model.results[row].infohash)
model.downloadActionEnabled = mvcGroup.parentGroup.model.canDownload(model.results[row].infohash)
})
}
}
@@ -98,12 +127,11 @@ class SearchTabView {
}
parent.setTabComponentAt(index, tabPanel)
mvcGroup.parentGroup.view.showSearchWindow.call()
def centerRenderer = new DefaultTableCellRenderer()
centerRenderer.setHorizontalAlignment(JLabel.CENTER)
resultsTable.columnModel.getColumn(1).setCellRenderer(centerRenderer)
resultsTable.setDefaultRenderer(Integer.class,centerRenderer)
resultsTable.columnModel.getColumn(4).setCellRenderer(centerRenderer)
resultsTable.columnModel.getColumn(1).setCellRenderer(new SizeRenderer())
@@ -118,7 +146,7 @@ class SearchTabView {
if (e.button == MouseEvent.BUTTON3)
showPopupMenu(e)
else if (e.button == MouseEvent.BUTTON1 && e.clickCount == 2)
mvcGroup.parentGroup.controller.download()
mvcGroup.controller.download()
}
@Override
public void mouseReleased(MouseEvent e) {
@@ -126,21 +154,42 @@ class SearchTabView {
showPopupMenu(e)
}
})
// senders table
sendersTable.setDefaultRenderer(Integer.class, centerRenderer)
sendersTable.rowSorter.addRowSorterListener({evt -> lastSendersSortEvent = evt})
sendersTable.rowSorter.setSortsOnUpdates(true)
def selectionModel = sendersTable.getSelectionModel()
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
selectionModel.addListSelectionListener({
int row = selectedSenderRow()
if (row < 0) {
model.trustButtonsEnabled = false
return
} else {
model.trustButtonsEnabled = true
model.results.clear()
Persona p = model.senders[row]
model.results.addAll(model.sendersBucket[p])
resultsTable.model.fireTableDataChanged()
}
})
}
def closeTab = {
int index = parent.indexOfTab(searchTerms)
parent.removeTabAt(index)
mvcGroup.parentGroup.model.trustButtonsEnabled = false
mvcGroup.parentGroup.model.downloadActionEnabled = false
model.trustButtonsEnabled = false
model.downloadActionEnabled = false
mvcGroup.destroy()
}
def showPopupMenu(MouseEvent e) {
JPopupMenu menu = new JPopupMenu()
if (mvcGroup.parentGroup.model.downloadActionEnabled) {
if (model.downloadActionEnabled) {
JMenuItem download = new JMenuItem("Download")
download.addActionListener({mvcGroup.parentGroup.controller.download()})
download.addActionListener({mvcGroup.controller.download()})
menu.add(download)
}
JMenuItem copyHashToClipboard = new JMenuItem("Copy hash to clipboard")
@@ -160,4 +209,13 @@ class SearchTabView {
def clipboard = Toolkit.getDefaultToolkit().getSystemClipboard()
clipboard.setContents(selection, null)
}
int selectedSenderRow() {
int row = sendersTable.getSelectedRow()
if (row < 0)
return -1
if (lastSendersSortEvent != null)
row = sendersTable.rowSorter.convertRowIndexToModel(row)
row
}
}

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 ContentPanelIntegrationTest {
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

@@ -22,9 +22,8 @@ class DownloadProgressRenderer extends DefaultTableCellRenderer {
int done = d.donePieces()
int percent = -1
if (pieces != 0)
percent = (int)(done * 100.0 / pieces)
long size = d.pieceSize * pieces
String totalSize = DataHelper.formatSize2Decimal(size, false) + "B"
percent = (done * 100 / pieces)
String totalSize = DataHelper.formatSize2Decimal(d.length, false) + "B"
setText(String.format("%2d", percent) + "% of ${totalSize} ($done/$pieces pcs)".toString())
if (isSelected) {

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(ContentPanelController)
class ContentPanelControllerTest {
private ContentPanelController controller
@Rule
public final GriffonUnitRule griffon = new GriffonUnitRule()
@Test
void smokeTest() {
fail('Not yet implemented!')
}
}