Compare commits
42 Commits
muwire-0.5
...
muwire-0.5
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ff1df88601 | ||
![]() |
4ed572ba51 | ||
![]() |
fd3f55ab4d | ||
![]() |
1358e14467 | ||
![]() |
e22d5fea11 | ||
![]() |
7ade4aa10d | ||
![]() |
a9f623a91a | ||
![]() |
1ce410e943 | ||
![]() |
27aad9d75d | ||
![]() |
24591b10f2 | ||
![]() |
e4f1ea5c10 | ||
![]() |
c73c44c5f2 | ||
![]() |
309cbcc580 | ||
![]() |
86894f242b | ||
![]() |
568255140f | ||
![]() |
f6d2bac5bb | ||
![]() |
1c396711ed | ||
![]() |
c154d9538d | ||
![]() |
8043782446 | ||
![]() |
00c529cca1 | ||
![]() |
094b9ac2b0 | ||
![]() |
0dae0a561b | ||
![]() |
82eaafc2c3 | ||
![]() |
a3fc1a62e7 | ||
![]() |
2fd8f45107 | ||
![]() |
2429bbf59e | ||
![]() |
f7e28e04f6 | ||
![]() |
cc0188f20e | ||
![]() |
af9b4f4679 | ||
![]() |
625a559d02 | ||
![]() |
6e20193d57 | ||
![]() |
88ac267f99 | ||
![]() |
9b3a7473d1 | ||
![]() |
5b0180280e | ||
![]() |
d0462034fc | ||
![]() |
f3e4098107 | ||
![]() |
26e7ca0b21 | ||
![]() |
11007e5f19 | ||
![]() |
ae651cb6bd | ||
![]() |
cad3a88517 | ||
![]() |
29c81646af | ||
![]() |
8a0257927b |
@@ -4,7 +4,7 @@ MuWire is an easy to use file-sharing program which offers anonymity using [I2P
|
||||
|
||||
It is inspired by the LimeWire Gnutella client and developped by a former LimeWire developer.
|
||||
|
||||
The current stable release - 0.5.3 is avaiable for download at https://muwire.com. You can find technical documentation in the "doc" folder.
|
||||
The current stable release - 0.5.5 is avaiable for download at https://muwire.com. You can find technical documentation in the "doc" folder.
|
||||
|
||||
### Building
|
||||
|
||||
@@ -31,7 +31,7 @@ If you have an I2P router running on the same machine that is all you need to do
|
||||
|
||||
### Running the CLI
|
||||
|
||||
Look inside `cli-lanterna/build/distributions`. Untar/unzip one of the `shadow` files and then run the jar contained inside by typing `java -jar cli-lanterna-x.y.z.jar` in a terminal. The CLI will ask you about the router host and port on startup, no need to edit any files.
|
||||
Look inside `cli-lanterna/build/distributions`. Untar/unzip one of the `shadow` files and then run the jar contained inside by typing `java -jar cli-lanterna-x.y.z.jar` in a terminal. The CLI will ask you about the router host and port on startup, no need to edit any files. However, the CLI does not have an options window yet, so if you need to change any options you will need to edit the configuration files. The CLI options are documented here https://github.com/zlatinb/muwire/wiki/CLI-Configuration-Options
|
||||
|
||||
The CLI is under active development and doesn't have all the features of the GUI.
|
||||
|
||||
|
@@ -11,10 +11,14 @@ buildscript {
|
||||
}
|
||||
|
||||
apply plugin : 'application'
|
||||
mainClassName = 'com.muwire.clilanterna.CliLanterna'
|
||||
application {
|
||||
mainClassName = 'com.muwire.clilanterna.CliLanterna'
|
||||
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties','-Xmx256M']
|
||||
applicationName = 'MuWire-cli'
|
||||
}
|
||||
|
||||
apply plugin : 'com.github.johnrengelman.shadow'
|
||||
|
||||
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties']
|
||||
|
||||
dependencies {
|
||||
compile project(":core")
|
||||
|
@@ -3,6 +3,8 @@ package com.muwire.clilanterna
|
||||
import com.googlecode.lanterna.gui2.TextBox
|
||||
import com.googlecode.lanterna.gui2.TextGUI
|
||||
import com.googlecode.lanterna.gui2.Window
|
||||
import com.googlecode.lanterna.gui2.dialogs.MessageDialog
|
||||
import com.googlecode.lanterna.gui2.dialogs.MessageDialogButton
|
||||
import com.googlecode.lanterna.TerminalSize
|
||||
import com.googlecode.lanterna.gui2.BasicWindow
|
||||
import com.googlecode.lanterna.gui2.Button
|
||||
@@ -10,6 +12,7 @@ import com.googlecode.lanterna.gui2.GridLayout
|
||||
import com.googlecode.lanterna.gui2.GridLayout.Alignment
|
||||
import com.googlecode.lanterna.gui2.LayoutData
|
||||
import com.googlecode.lanterna.gui2.Panel
|
||||
import com.muwire.core.Constants
|
||||
import com.muwire.core.Core
|
||||
import com.muwire.core.SharedFile
|
||||
import com.muwire.core.files.UICommentEvent
|
||||
@@ -49,11 +52,16 @@ class AddCommentView extends BasicWindow {
|
||||
|
||||
Button saveButton = new Button("Save", {
|
||||
String newComment = textBox.getText()
|
||||
newComment = Base64.encode(DataUtil.encodei18nString(newComment))
|
||||
String encodedOldComment = sharedFile.getComment()
|
||||
sharedFile.setComment(newComment)
|
||||
core.eventBus.publish(new UICommentEvent(sharedFile : sharedFile, oldComment : encodedOldComment))
|
||||
close()
|
||||
if (newComment.length() > Constants.MAX_COMMENT_LENGTH) {
|
||||
String error = "Your comment is too long - ${newComment.length()} bytes. Maximum is $Constants.MAX_COMMENT_LENGTH bytes"
|
||||
MessageDialog.showMessageDialog(textGUI, "Comment Too Long", error, MessageDialogButton.Close)
|
||||
} else {
|
||||
newComment = Base64.encode(DataUtil.encodei18nString(newComment))
|
||||
String encodedOldComment = sharedFile.getComment()
|
||||
sharedFile.setComment(newComment)
|
||||
core.eventBus.publish(new UICommentEvent(sharedFile : sharedFile, oldComment : encodedOldComment))
|
||||
close()
|
||||
}
|
||||
})
|
||||
Button cancelButton = new Button("Cancel", {close()})
|
||||
|
||||
|
@@ -32,7 +32,7 @@ import com.muwire.core.UILoadedEvent
|
||||
import com.muwire.core.files.AllFilesLoadedEvent
|
||||
|
||||
class CliLanterna {
|
||||
private static final String MW_VERSION = "0.5.5"
|
||||
private static final String MW_VERSION = "0.5.7"
|
||||
|
||||
private static volatile Core core
|
||||
|
||||
@@ -82,14 +82,14 @@ class CliLanterna {
|
||||
props.setDownloadLocation(downloadLocationFile)
|
||||
props.incompleteLocation = incompletesLocationFile
|
||||
|
||||
propsFile.withOutputStream {
|
||||
propsFile.withPrintWriter("UTF-8", {
|
||||
props.write(it)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
props = new Properties()
|
||||
propsFile.withInputStream {
|
||||
propsFile.withReader("UTF-8", {
|
||||
props.load(it)
|
||||
}
|
||||
})
|
||||
props = new MuWireSettings(props)
|
||||
}
|
||||
props.updateType = "cli-lanterna"
|
||||
|
@@ -30,6 +30,7 @@ import com.muwire.core.update.UpdateAvailableEvent
|
||||
import com.muwire.core.update.UpdateDownloadedEvent
|
||||
|
||||
import net.i2p.data.Base64
|
||||
import net.i2p.data.DataHelper
|
||||
|
||||
class MainWindowView extends BasicWindow {
|
||||
|
||||
@@ -49,6 +50,7 @@ class MainWindowView extends BasicWindow {
|
||||
private final Label sharedFiles
|
||||
private final Label timesBrowsed
|
||||
private final Label updateStatus
|
||||
private final Label usedRam, totalRam, maxRam
|
||||
|
||||
public MainWindowView(String title, Core core, TextGUI textGUI, Screen screen, CliSettings props) {
|
||||
super(title);
|
||||
@@ -130,6 +132,9 @@ class MainWindowView extends BasicWindow {
|
||||
sharedFiles = new Label("0")
|
||||
timesBrowsed = new Label("0")
|
||||
updateStatus = new Label("Unknown")
|
||||
usedRam = new Label("0")
|
||||
maxRam = new Label("0")
|
||||
totalRam = new Label("0")
|
||||
|
||||
statusPanel.with {
|
||||
addComponent(new Label("Incoming Connections: "), layoutData)
|
||||
@@ -148,6 +153,14 @@ class MainWindowView extends BasicWindow {
|
||||
addComponent(timesBrowsed, layoutData)
|
||||
addComponent(new Label("Update Status: "), layoutData)
|
||||
addComponent(updateStatus, layoutData)
|
||||
addComponent(new Label("Java Version: "), layoutData)
|
||||
addComponent(new Label(System.getProperty("java.vendor")+ " " + System.getProperty("java.version")), layoutData)
|
||||
addComponent(new Label("Used Memory: "), layoutData)
|
||||
addComponent(usedRam, layoutData)
|
||||
addComponent(new Label("Total Memory: "), layoutData)
|
||||
addComponent(totalRam, layoutData)
|
||||
addComponent(new Label("Maximum Memory: "), layoutData)
|
||||
addComponent(maxRam, layoutData)
|
||||
}
|
||||
|
||||
refreshStats()
|
||||
@@ -204,8 +217,11 @@ class MainWindowView extends BasicWindow {
|
||||
textGUI.getGUIThread().invokeLater {
|
||||
String label = "$e.version is available with hash $e.infoHash"
|
||||
updateStatus.setText(label)
|
||||
String message = "Version $e.version is available from $e.signer, search for $e.infoHash"
|
||||
MessageDialog.showMessageDialog(textGUI, "Update Available", message, MessageDialogButton.OK)
|
||||
String message = "Version $e.version is available, with hash $e.infoHash . Show details?"
|
||||
def button = MessageDialog.showMessageDialog(textGUI, "Update Available", message, MessageDialogButton.Yes, MessageDialogButton.No)
|
||||
if (button == MessageDialogButton.No)
|
||||
return
|
||||
textGUI.addWindowAndWait(new UpdateTextView(e.text, sizeForTables()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,8 +229,11 @@ class MainWindowView extends BasicWindow {
|
||||
textGUI.getGUIThread().invokeLater {
|
||||
String label = "$e.version downloaded"
|
||||
updateStatus.setText(label)
|
||||
String message = "Version $e.version from $e.signer has been downloaded. You can update now."
|
||||
MessageDialog.showMessageDialog(textGUI, "Update Available", message, MessageDialogButton.OK)
|
||||
String message = "MuWire version $e.version has been downloaded. Show details?."
|
||||
def button = MessageDialog.showMessageDialog(textGUI, "Update Available", message, MessageDialogButton.Yes, MessageDialogButton.No)
|
||||
if (button == MessageDialogButton.No)
|
||||
return
|
||||
textGUI.addWindowAndWait(new UpdateTextView(e.text, sizeForTables()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,6 +285,17 @@ class MainWindowView extends BasicWindow {
|
||||
int hopelessHosts = core.hostCache.countHopelessHosts()
|
||||
int shared = core.fileManager.fileToSharedFile.size()
|
||||
int browsed = core.connectionAcceptor.browsed
|
||||
long freeMemL = Runtime.getRuntime().freeMemory()
|
||||
long totalMemL = Runtime.getRuntime().totalMemory()
|
||||
String usedMem = DataHelper.formatSize2Decimal(freeMemL, false) + "B"
|
||||
String totalMem = DataHelper.formatSize2Decimal(totalMemL, false)+"B"
|
||||
String maxMem
|
||||
long maxMemL = Runtime.getRuntime().maxMemory()
|
||||
if (maxMemL >= Long.MAX_VALUE / 2)
|
||||
maxMem = "Unlimited"
|
||||
else
|
||||
maxMem = DataHelper.formatSize2Decimal(maxMemL, false) + "B"
|
||||
|
||||
|
||||
incoming.setText(String.valueOf(inCon))
|
||||
outgoing.setText(String.valueOf(outCon))
|
||||
@@ -274,5 +304,8 @@ class MainWindowView extends BasicWindow {
|
||||
hopeless.setText(String.valueOf(hopelessHosts))
|
||||
sharedFiles.setText(String.valueOf(shared))
|
||||
timesBrowsed.setText(String.valueOf(browsed))
|
||||
usedRam.setText(usedMem)
|
||||
totalRam.setText(totalMem)
|
||||
maxRam.setText(maxMem)
|
||||
}
|
||||
}
|
||||
|
@@ -8,7 +8,11 @@ import com.muwire.core.search.SearchEvent
|
||||
import com.muwire.core.search.UIResultBatchEvent
|
||||
import com.muwire.core.search.UIResultEvent
|
||||
|
||||
import net.i2p.crypto.DSAEngine
|
||||
import net.i2p.data.Base64
|
||||
import net.i2p.data.Signature
|
||||
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
import com.googlecode.lanterna.gui2.TextGUIThread
|
||||
import com.googlecode.lanterna.gui2.table.TableModel
|
||||
@@ -40,20 +44,27 @@ class SearchModel {
|
||||
}
|
||||
|
||||
def searchEvent
|
||||
byte [] payload
|
||||
if (hashSearch) {
|
||||
searchEvent = new SearchEvent(searchHash : root, uuid : UUID.randomUUID(), oobInfohash : true, compressedResults : true)
|
||||
payload = root
|
||||
} else {
|
||||
def replaced = query.toLowerCase().trim().replaceAll(SplitPattern.SPLIT_PATTERN, " ")
|
||||
def terms = replaced.split(" ")
|
||||
def nonEmpty = []
|
||||
terms.each { if (it.length() > 0) nonEmpty << it }
|
||||
payload = String.join(" ", nonEmpty).getBytes(StandardCharsets.UTF_8)
|
||||
searchEvent = new SearchEvent(searchTerms : nonEmpty, uuid : UUID.randomUUID(), oobInfohash: true,
|
||||
searchComments : core.muOptions.searchComments, compressedResults : true)
|
||||
}
|
||||
|
||||
boolean firstHop = core.muOptions.allowUntrusted || core.muOptions.searchExtraHop
|
||||
|
||||
Signature sig = DSAEngine.getInstance().sign(payload, core.spk)
|
||||
|
||||
core.eventBus.publish(new QueryEvent(searchEvent : searchEvent, firstHop : firstHop,
|
||||
replyTo: core.me.destination, receivedOn: core.me.destination,
|
||||
originator : core.me))
|
||||
originator : core.me, sig: sig.data))
|
||||
}
|
||||
|
||||
void unregister() {
|
||||
|
@@ -199,6 +199,6 @@ class TrustView extends BasicWindow {
|
||||
|
||||
private void saveMuSettings() {
|
||||
File settingsFile = new File(core.home,"MuWire.properties")
|
||||
settingsFile.withOutputStream { core.muOptions.write(it) }
|
||||
settingsFile.withPrintWriter("UTF-8",{ core.muOptions.write(it) })
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,34 @@
|
||||
package com.muwire.clilanterna
|
||||
|
||||
import com.googlecode.lanterna.TerminalSize
|
||||
import com.googlecode.lanterna.gui2.BasicWindow
|
||||
import com.googlecode.lanterna.gui2.Button
|
||||
import com.googlecode.lanterna.gui2.GridLayout
|
||||
import com.googlecode.lanterna.gui2.GridLayout.Alignment
|
||||
import com.googlecode.lanterna.gui2.LayoutData
|
||||
import com.googlecode.lanterna.gui2.Panel
|
||||
import com.googlecode.lanterna.gui2.TextBox
|
||||
import com.googlecode.lanterna.gui2.Window
|
||||
|
||||
class UpdateTextView extends BasicWindow {
|
||||
private final TextBox textBox
|
||||
private final LayoutData layoutData = GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER)
|
||||
|
||||
UpdateTextView(String text, TerminalSize terminalSize) {
|
||||
super("Update Details")
|
||||
|
||||
setHints([Window.Hint.CENTERED])
|
||||
|
||||
Panel contentPanel = new Panel()
|
||||
contentPanel.setLayoutManager(new GridLayout(1))
|
||||
|
||||
TerminalSize boxSize = new TerminalSize((terminalSize.getColumns() / 2).toInteger(), (terminalSize.getRows() / 2).toInteger())
|
||||
textBox = new TextBox(boxSize, text, TextBox.Style.MULTI_LINE)
|
||||
contentPanel.addComponent(textBox, layoutData)
|
||||
|
||||
Button closeButton = new Button("Close", {close()})
|
||||
contentPanel.addComponent(closeButton, layoutData)
|
||||
|
||||
setComponent(contentPanel)
|
||||
}
|
||||
}
|
@@ -28,6 +28,7 @@ import com.muwire.core.files.FileSharedEvent
|
||||
import com.muwire.core.files.FileUnsharedEvent
|
||||
import com.muwire.core.files.HasherService
|
||||
import com.muwire.core.files.PersisterService
|
||||
import com.muwire.core.files.SideCarFileEvent
|
||||
import com.muwire.core.files.UICommentEvent
|
||||
import com.muwire.core.files.UIPersistFilesEvent
|
||||
import com.muwire.core.files.AllFilesLoadedEvent
|
||||
@@ -102,6 +103,8 @@ public class Core {
|
||||
private final Router router
|
||||
|
||||
final AtomicBoolean shutdown = new AtomicBoolean()
|
||||
|
||||
final SigningPrivateKey spk
|
||||
|
||||
public Core(MuWireSettings props, File home, String myVersion) {
|
||||
this.home = home
|
||||
@@ -179,7 +182,7 @@ public class Core {
|
||||
i2pSession = socketManager.getSession()
|
||||
|
||||
def destination = new Destination()
|
||||
def spk = new SigningPrivateKey(Constants.SIG_TYPE)
|
||||
spk = new SigningPrivateKey(Constants.SIG_TYPE)
|
||||
keyDat.withInputStream {
|
||||
destination.readBytes(it)
|
||||
def privateKey = new PrivateKey()
|
||||
@@ -190,8 +193,9 @@ public class Core {
|
||||
def baos = new ByteArrayOutputStream()
|
||||
def daos = new DataOutputStream(baos)
|
||||
daos.write(Constants.PERSONA_VERSION)
|
||||
daos.writeShort((short)props.getNickname().length())
|
||||
daos.write(props.getNickname().getBytes(StandardCharsets.UTF_8))
|
||||
byte [] name = props.getNickname().getBytes(StandardCharsets.UTF_8)
|
||||
daos.writeShort((short)name.length)
|
||||
daos.write(name)
|
||||
destination.writeBytes(daos)
|
||||
daos.flush()
|
||||
byte [] payload = baos.toByteArray()
|
||||
@@ -221,6 +225,7 @@ public class Core {
|
||||
eventBus.register(SearchEvent.class, fileManager)
|
||||
eventBus.register(DirectoryUnsharedEvent.class, fileManager)
|
||||
eventBus.register(UICommentEvent.class, fileManager)
|
||||
eventBus.register(SideCarFileEvent.class, fileManager)
|
||||
|
||||
log.info("initializing mesh manager")
|
||||
MeshManager meshManager = new MeshManager(fileManager, home, props)
|
||||
@@ -333,8 +338,7 @@ public class Core {
|
||||
return
|
||||
}
|
||||
log.info("saving settings")
|
||||
File f = new File(home, "MuWire.properties")
|
||||
f.withOutputStream { muOptions.write(it) }
|
||||
saveMuSettings()
|
||||
log.info("shutting down trust subscriber")
|
||||
trustSubscriber.stop()
|
||||
log.info("shutting down download manageer")
|
||||
@@ -355,6 +359,11 @@ public class Core {
|
||||
}
|
||||
log.info("shutdown complete")
|
||||
}
|
||||
|
||||
public void saveMuSettings() {
|
||||
File f = new File(home, "MuWire.properties")
|
||||
f.withPrintWriter("UTF-8", { muOptions.write(it) })
|
||||
}
|
||||
|
||||
static main(args) {
|
||||
def home = System.getProperty("user.home") + File.separator + ".MuWire"
|
||||
@@ -380,7 +389,7 @@ public class Core {
|
||||
}
|
||||
}
|
||||
|
||||
Core core = new Core(props, home, "0.5.5")
|
||||
Core core = new Core(props, home, "0.5.7")
|
||||
core.startServices()
|
||||
|
||||
// ... at the end, sleep or execute script
|
||||
|
@@ -78,10 +78,10 @@ class MuWireSettings {
|
||||
totalUploadSlots = Integer.valueOf(props.getProperty("totalUploadSlots","-1"))
|
||||
uploadSlotsPerUser = Integer.valueOf(props.getProperty("uploadSlotsPerUser","-1"))
|
||||
|
||||
watchedDirectories = readEncodedSet(props, "watchedDirectories")
|
||||
watchedKeywords = readEncodedSet(props, "watchedKeywords")
|
||||
watchedRegexes = readEncodedSet(props, "watchedRegexes")
|
||||
negativeFileTree = readEncodedSet(props, "negativeFileTree")
|
||||
watchedDirectories = DataUtil.readEncodedSet(props, "watchedDirectories")
|
||||
watchedKeywords = DataUtil.readEncodedSet(props, "watchedKeywords")
|
||||
watchedRegexes = DataUtil.readEncodedSet(props, "watchedRegexes")
|
||||
negativeFileTree = DataUtil.readEncodedSet(props, "negativeFileTree")
|
||||
|
||||
trustSubscriptions = new HashSet<>()
|
||||
if (props.containsKey("trustSubscriptions")) {
|
||||
@@ -93,7 +93,7 @@ class MuWireSettings {
|
||||
|
||||
}
|
||||
|
||||
void write(OutputStream out) throws IOException {
|
||||
void write(Writer out) throws IOException {
|
||||
Properties props = new Properties()
|
||||
props.setProperty("leaf", isLeaf.toString())
|
||||
props.setProperty("allowUntrusted", allowUntrusted.toString())
|
||||
@@ -125,10 +125,10 @@ class MuWireSettings {
|
||||
props.setProperty("totalUploadSlots", String.valueOf(totalUploadSlots))
|
||||
props.setProperty("uploadSlotsPerUser", String.valueOf(uploadSlotsPerUser))
|
||||
|
||||
writeEncodedSet(watchedDirectories, "watchedDirectories", props)
|
||||
writeEncodedSet(watchedKeywords, "watchedKeywords", props)
|
||||
writeEncodedSet(watchedRegexes, "watchedRegexes", props)
|
||||
writeEncodedSet(negativeFileTree, "negativeFileTree", props)
|
||||
DataUtil.writeEncodedSet(watchedDirectories, "watchedDirectories", props)
|
||||
DataUtil.writeEncodedSet(watchedKeywords, "watchedKeywords", props)
|
||||
DataUtil.writeEncodedSet(watchedRegexes, "watchedRegexes", props)
|
||||
DataUtil.writeEncodedSet(negativeFileTree, "negativeFileTree", props)
|
||||
|
||||
if (!trustSubscriptions.isEmpty()) {
|
||||
String encoded = trustSubscriptions.stream().
|
||||
@@ -137,25 +137,7 @@ class MuWireSettings {
|
||||
props.setProperty("trustSubscriptions", encoded)
|
||||
}
|
||||
|
||||
props.store(out, "")
|
||||
}
|
||||
|
||||
private static Set<String> readEncodedSet(Properties props, String property) {
|
||||
Set<String> rv = new ConcurrentHashSet<>()
|
||||
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)
|
||||
props.store(out, "This file is UTF-8")
|
||||
}
|
||||
|
||||
boolean isLeaf() {
|
||||
|
@@ -22,8 +22,9 @@ public class Name {
|
||||
|
||||
public void write(OutputStream out) throws IOException {
|
||||
DataOutputStream dos = new DataOutputStream(out)
|
||||
dos.writeShort(name.length())
|
||||
dos.write(name.getBytes(StandardCharsets.UTF_8))
|
||||
byte [] bytes = name.getBytes(StandardCharsets.UTF_8)
|
||||
dos.writeShort(bytes.length)
|
||||
dos.write(bytes)
|
||||
}
|
||||
|
||||
public getName() {
|
||||
|
@@ -1,5 +1,6 @@
|
||||
package com.muwire.core.connection
|
||||
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.concurrent.BlockingQueue
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.ExecutorService
|
||||
@@ -10,6 +11,7 @@ import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.logging.Level
|
||||
|
||||
import com.muwire.core.Constants
|
||||
import com.muwire.core.EventBus
|
||||
import com.muwire.core.MuWireSettings
|
||||
import com.muwire.core.Persona
|
||||
@@ -21,8 +23,10 @@ import com.muwire.core.trust.TrustLevel
|
||||
import com.muwire.core.trust.TrustService
|
||||
|
||||
import groovy.util.logging.Log
|
||||
import net.i2p.crypto.DSAEngine
|
||||
import net.i2p.data.Base64
|
||||
import net.i2p.data.Destination
|
||||
import net.i2p.data.Signature
|
||||
|
||||
@Log
|
||||
abstract class Connection implements Closeable {
|
||||
@@ -147,6 +151,8 @@ abstract class Connection implements Closeable {
|
||||
query.replyTo = e.replyTo.toBase64()
|
||||
if (e.originator != null)
|
||||
query.originator = e.originator.toBase64()
|
||||
if (e.sig != null)
|
||||
query.sig = Base64.encode(e.sig)
|
||||
messages.put(query)
|
||||
}
|
||||
|
||||
@@ -225,6 +231,24 @@ abstract class Connection implements Closeable {
|
||||
boolean compressedResults = false
|
||||
if (search.compressedResults != null)
|
||||
compressedResults = search.compressedResults
|
||||
byte[] sig = null
|
||||
// TODO: make this mandatory at some point
|
||||
if (search.sig != null) {
|
||||
sig = Base64.decode(search.sig)
|
||||
byte [] payload
|
||||
if (infohash != null)
|
||||
payload = infohash
|
||||
else
|
||||
payload = String.join(" ",search.keywords).getBytes(StandardCharsets.UTF_8)
|
||||
def spk = originator.destination.getSigningPublicKey()
|
||||
def signature = new Signature(Constants.SIG_TYPE, sig)
|
||||
if (!DSAEngine.getInstance().verifySignature(signature, payload, spk)) {
|
||||
log.info("signature didn't match keywords")
|
||||
return
|
||||
} else
|
||||
log.info("query signature verified")
|
||||
} else
|
||||
log.info("no signature in query")
|
||||
|
||||
SearchEvent searchEvent = new SearchEvent(searchTerms : search.keywords,
|
||||
searchHash : infohash,
|
||||
@@ -236,7 +260,8 @@ abstract class Connection implements Closeable {
|
||||
replyTo : replyTo,
|
||||
originator : originator,
|
||||
receivedOn : endpoint.destination,
|
||||
firstHop : search.firstHop )
|
||||
firstHop : search.firstHop,
|
||||
sig : sig )
|
||||
eventBus.publish(event)
|
||||
|
||||
}
|
||||
|
@@ -21,6 +21,7 @@ import java.nio.file.Files
|
||||
import java.nio.file.StandardOpenOption
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import java.util.logging.Level
|
||||
|
||||
@Log
|
||||
@@ -37,13 +38,12 @@ class DownloadSession {
|
||||
private final Set<Integer> available
|
||||
private final MessageDigest digest
|
||||
|
||||
private long lastSpeedRead = System.currentTimeMillis()
|
||||
private long dataSinceLastRead
|
||||
private final AtomicLong dataSinceLastRead
|
||||
|
||||
private MappedByteBuffer mapped
|
||||
|
||||
DownloadSession(EventBus eventBus, String meB64, Pieces pieces, InfoHash infoHash, Endpoint endpoint, File file,
|
||||
int pieceSize, long fileLength, Set<Integer> available) {
|
||||
int pieceSize, long fileLength, Set<Integer> available, AtomicLong dataSinceLastRead) {
|
||||
this.eventBus = eventBus
|
||||
this.meB64 = meB64
|
||||
this.pieces = pieces
|
||||
@@ -53,6 +53,7 @@ class DownloadSession {
|
||||
this.pieceSize = pieceSize
|
||||
this.fileLength = fileLength
|
||||
this.available = available
|
||||
this.dataSinceLastRead = dataSinceLastRead
|
||||
try {
|
||||
digest = MessageDigest.getInstance("SHA-256")
|
||||
} catch (NoSuchAlgorithmException impossible) {
|
||||
@@ -190,7 +191,7 @@ class DownloadSession {
|
||||
throw new IOException()
|
||||
synchronized(this) {
|
||||
mapped.put(tmp, 0, read)
|
||||
dataSinceLastRead += read
|
||||
dataSinceLastRead.addAndGet(read)
|
||||
pieces.markPartial(piece, mapped.position())
|
||||
}
|
||||
}
|
||||
@@ -222,13 +223,4 @@ class DownloadSession {
|
||||
return 0
|
||||
mapped.position()
|
||||
}
|
||||
|
||||
synchronized int speed() {
|
||||
final long now = System.currentTimeMillis()
|
||||
long interval = Math.max(1000, now - lastSpeedRead)
|
||||
lastSpeedRead = now;
|
||||
int rv = (int) (dataSinceLastRead * 1000.0 / interval)
|
||||
dataSinceLastRead = 0
|
||||
rv
|
||||
}
|
||||
}
|
||||
|
@@ -12,6 +12,7 @@ import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import java.util.logging.Level
|
||||
|
||||
import com.muwire.core.Constants
|
||||
@@ -61,10 +62,11 @@ public class Downloader {
|
||||
private final AtomicBoolean eventFired = new AtomicBoolean()
|
||||
private boolean piecesFileClosed
|
||||
|
||||
private final AtomicLong dataSinceLastRead = new AtomicLong(0)
|
||||
private volatile long lastSpeedRead = System.currentTimeMillis()
|
||||
private ArrayList speedArr = new ArrayList<Integer>()
|
||||
private int speedPos = 0
|
||||
private int speedAvg = 0
|
||||
private long timestamp = Instant.now().toEpochMilli()
|
||||
|
||||
public Downloader(EventBus eventBus, DownloadManager downloadManager,
|
||||
Persona me, File file, long length, InfoHash infoHash,
|
||||
@@ -139,10 +141,11 @@ public class Downloader {
|
||||
public int speed() {
|
||||
int currSpeed = 0
|
||||
if (getCurrentState() == DownloadState.DOWNLOADING) {
|
||||
activeWorkers.values().each {
|
||||
if (it.currentState == WorkerState.DOWNLOADING)
|
||||
currSpeed += it.speed()
|
||||
}
|
||||
long dataRead = dataSinceLastRead.getAndSet(0)
|
||||
long now = System.currentTimeMillis()
|
||||
if (now > lastSpeedRead)
|
||||
currSpeed = (int) (dataRead * 1000.0 / (now - lastSpeedRead))
|
||||
lastSpeedRead = now
|
||||
}
|
||||
|
||||
if (speedArr.size() != downloadManager.muSettings.speedSmoothSeconds) {
|
||||
@@ -302,7 +305,7 @@ public class Downloader {
|
||||
boolean requestPerformed
|
||||
while(!pieces.isComplete()) {
|
||||
currentSession = new DownloadSession(eventBus, me.toBase64(), pieces, getInfoHash(),
|
||||
endpoint, incompleteFile, pieceSize, length, available)
|
||||
endpoint, incompleteFile, pieceSize, length, available, dataSinceLastRead)
|
||||
requestPerformed = currentSession.request()
|
||||
if (!requestPerformed)
|
||||
break
|
||||
@@ -339,12 +342,6 @@ public class Downloader {
|
||||
}
|
||||
}
|
||||
|
||||
int speed() {
|
||||
if (currentSession == null)
|
||||
return 0
|
||||
currentSession.speed()
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
downloadThread?.interrupt()
|
||||
}
|
||||
|
@@ -86,9 +86,9 @@ class DirectoryWatcher {
|
||||
|
||||
private void saveMuSettings() {
|
||||
File muSettingsFile = new File(home, "MuWire.properties")
|
||||
muSettingsFile.withOutputStream {
|
||||
muSettingsFile.withPrintWriter("UTF-8", {
|
||||
muOptions.write(it)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private void watch() {
|
||||
|
@@ -25,6 +25,7 @@ class FileManager {
|
||||
final Map<String, Set<File>> commentToFile = new HashMap<>()
|
||||
final SearchIndex index = new SearchIndex()
|
||||
final FileTree negativeTree = new FileTree()
|
||||
final Set<File> sideCarFiles = new HashSet<>()
|
||||
|
||||
FileManager(EventBus eventBus, MuWireSettings settings) {
|
||||
this.settings = settings
|
||||
@@ -36,8 +37,15 @@ class FileManager {
|
||||
}
|
||||
|
||||
void onFileHashedEvent(FileHashedEvent e) {
|
||||
if (e.sharedFile != null)
|
||||
addToIndex(e.sharedFile)
|
||||
if (e.sharedFile == null)
|
||||
return
|
||||
File f = e.sharedFile.getFile()
|
||||
if (sideCarFiles.remove(f)) {
|
||||
File sideCar = new File(f.getParentFile(), f.getName() + ".mwcomment")
|
||||
if (sideCar.exists())
|
||||
e.sharedFile.setComment(Base64.encode(DataUtil.encodei18nString(sideCar.text)))
|
||||
}
|
||||
addToIndex(e.sharedFile)
|
||||
}
|
||||
|
||||
void onFileLoadedEvent(FileLoadedEvent e) {
|
||||
@@ -49,6 +57,21 @@ class FileManager {
|
||||
addToIndex(e.downloadedFile)
|
||||
}
|
||||
}
|
||||
|
||||
void onSideCarFileEvent(SideCarFileEvent e) {
|
||||
String name = e.file.getName()
|
||||
name = name.substring(0, name.length() - ".mwcomment".length())
|
||||
File target = new File(e.file.getParentFile(), name)
|
||||
SharedFile existing = fileToSharedFile.get(target)
|
||||
if (existing == null) {
|
||||
sideCarFiles.add(target)
|
||||
return
|
||||
}
|
||||
String comment = Base64.encode(DataUtil.encodei18nString(e.file.text))
|
||||
String oldComment = existing.getComment()
|
||||
existing.setComment(comment)
|
||||
eventBus.publish(new UICommentEvent(oldComment : oldComment, sharedFile : existing))
|
||||
}
|
||||
|
||||
private void addToIndex(SharedFile sf) {
|
||||
log.info("Adding shared file " + sf.getFile())
|
||||
|
@@ -45,7 +45,7 @@ class FileTree {
|
||||
true
|
||||
}
|
||||
|
||||
private static class TreeNode {
|
||||
public static class TreeNode {
|
||||
TreeNode parent
|
||||
File file
|
||||
final Set<TreeNode> children = new HashSet<>()
|
||||
|
@@ -3,6 +3,7 @@ package com.muwire.core.files
|
||||
import java.util.concurrent.Executor
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
import com.muwire.core.Constants
|
||||
import com.muwire.core.EventBus
|
||||
import com.muwire.core.MuWireSettings
|
||||
import com.muwire.core.SharedFile
|
||||
@@ -33,7 +34,12 @@ class HasherService {
|
||||
return
|
||||
if (fileManager.fileToSharedFile.containsKey(canonical))
|
||||
return
|
||||
if (hashed.add(canonical))
|
||||
if (canonical.isFile() && fileManager.negativeTree.fileToNode.containsKey(canonical))
|
||||
return
|
||||
if (canonical.getName().endsWith(".mwcomment")) {
|
||||
if (canonical.length() <= Constants.MAX_COMMENT_LENGTH)
|
||||
eventBus.publish(new SideCarFileEvent(file : canonical))
|
||||
} else if (hashed.add(canonical))
|
||||
executor.execute( { -> process(canonical) } as Runnable)
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,12 @@
|
||||
package com.muwire.core.files
|
||||
|
||||
import com.muwire.core.Event
|
||||
|
||||
class SideCarFileEvent extends Event {
|
||||
File file
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + " file: "+file.getAbsolutePath()
|
||||
}
|
||||
}
|
@@ -12,6 +12,7 @@ class QueryEvent extends Event {
|
||||
Destination replyTo
|
||||
Persona originator
|
||||
Destination receivedOn
|
||||
byte[] sig
|
||||
|
||||
String toString() {
|
||||
"searchEvent: $searchEvent firstHop:$firstHop, replyTo:${replyTo.toBase32()}" +
|
||||
|
@@ -7,4 +7,5 @@ class UpdateAvailableEvent extends Event {
|
||||
String version
|
||||
String signer
|
||||
String infoHash
|
||||
String text
|
||||
}
|
||||
|
@@ -40,6 +40,8 @@ class UpdateClient {
|
||||
private volatile InfoHash updateInfoHash
|
||||
private volatile String version, signer
|
||||
private volatile boolean updateDownloading
|
||||
|
||||
private volatile String text
|
||||
|
||||
UpdateClient(EventBus eventBus, I2PSession session, String myVersion, MuWireSettings settings, FileManager fileManager, Persona me) {
|
||||
this.eventBus = eventBus
|
||||
@@ -75,7 +77,7 @@ class UpdateClient {
|
||||
if (e.downloadedFile.infoHash != updateInfoHash)
|
||||
return
|
||||
updateDownloading = false
|
||||
eventBus.publish(new UpdateDownloadedEvent(version : version, signer : signer))
|
||||
eventBus.publish(new UpdateDownloadedEvent(version : version, signer : signer, text : text))
|
||||
}
|
||||
|
||||
private void checkUpdate() {
|
||||
@@ -147,15 +149,16 @@ class UpdateClient {
|
||||
} else
|
||||
infoHash = payload[settings.updateType]
|
||||
|
||||
|
||||
text = payload.text
|
||||
|
||||
if (!settings.autoDownloadUpdate) {
|
||||
log.info("new version $payload.version available, publishing event")
|
||||
eventBus.publish(new UpdateAvailableEvent(version : payload.version, signer : payload.signer, infoHash : infoHash))
|
||||
eventBus.publish(new UpdateAvailableEvent(version : payload.version, signer : payload.signer, infoHash : infoHash, text : text))
|
||||
} else {
|
||||
log.info("new version $payload.version available")
|
||||
updateInfoHash = new InfoHash(Base64.decode(infoHash))
|
||||
if (fileManager.rootToFiles.containsKey(updateInfoHash))
|
||||
eventBus.publish(new UpdateDownloadedEvent(version : payload.version, signer : payload.signer))
|
||||
eventBus.publish(new UpdateDownloadedEvent(version : payload.version, signer : payload.signer, text : text))
|
||||
else {
|
||||
updateDownloading = false
|
||||
version = payload.version
|
||||
|
@@ -5,4 +5,5 @@ import com.muwire.core.Event
|
||||
class UpdateDownloadedEvent extends Event {
|
||||
String version
|
||||
String signer
|
||||
String text
|
||||
}
|
||||
|
@@ -10,4 +10,6 @@ public class Constants {
|
||||
public static final int MAX_HEADERS = 16;
|
||||
|
||||
public static final int MAX_RESULTS = 0x1 << 16;
|
||||
|
||||
public static final int MAX_COMMENT_LENGTH = 0x1 << 15;
|
||||
}
|
||||
|
@@ -11,10 +11,14 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.muwire.core.Constants;
|
||||
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.util.ConcurrentHashSet;
|
||||
|
||||
public class DataUtil {
|
||||
|
||||
@@ -165,4 +169,22 @@ public class DataUtil {
|
||||
} catch(Exception ex) { }
|
||||
cb = null;
|
||||
}
|
||||
|
||||
public static Set<String> readEncodedSet(Properties props, String property) {
|
||||
Set<String> rv = new ConcurrentHashSet<>();
|
||||
if (props.containsKey(property)) {
|
||||
String [] encoded = props.getProperty(property).split(",");
|
||||
for(String s : encoded)
|
||||
rv.add(readi18nString(Base64.decode(s)));
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static void writeEncodedSet(Set<String> set, String property, Properties props) {
|
||||
if (set.isEmpty())
|
||||
return;
|
||||
String encoded = set.stream().map(s -> Base64.encode(encodei18nString(s)))
|
||||
.collect(Collectors.joining(","));
|
||||
props.setProperty(property, encoded);
|
||||
}
|
||||
}
|
||||
|
@@ -2,6 +2,8 @@ package com.muwire.core.download
|
||||
|
||||
import static org.junit.Assert.fail
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
@@ -76,7 +78,7 @@ class DownloadSessionTest {
|
||||
toUploader = new PipedOutputStream(fromDownloader)
|
||||
endpoint = new Endpoint(null, fromUploader, toUploader, null)
|
||||
|
||||
session = new DownloadSession(eventBus, "",pieces, infoHash, endpoint, target, pieceSize, size, available)
|
||||
session = new DownloadSession(eventBus, "",pieces, infoHash, endpoint, target, pieceSize, size, available, new AtomicLong())
|
||||
downloadThread = new Thread( { perform() } as Runnable)
|
||||
downloadThread.setDaemon(true)
|
||||
downloadThread.start()
|
||||
|
@@ -1,11 +1,12 @@
|
||||
group = com.muwire
|
||||
version = 0.5.5
|
||||
version = 0.5.7
|
||||
i2pVersion = 0.9.43
|
||||
groovyVersion = 2.4.15
|
||||
slf4jVersion = 1.7.25
|
||||
spockVersion = 1.1-groovy-2.4
|
||||
grailsVersion=4.0.0
|
||||
gorm.version=7.0.2.RELEASE
|
||||
griffonEnv=prod
|
||||
|
||||
sourceCompatibility=1.8
|
||||
targetCompatibility=1.8
|
||||
|
@@ -40,8 +40,11 @@ griffon {
|
||||
]
|
||||
}
|
||||
|
||||
mainClassName = 'com.muwire.gui.Launcher'
|
||||
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties']
|
||||
application {
|
||||
mainClassName = 'com.muwire.gui.Launcher'
|
||||
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties','-Xmx256M']
|
||||
applicationName = 'MuWire'
|
||||
}
|
||||
|
||||
apply from: 'gradle/publishing.gradle'
|
||||
// apply from: 'gradle/code-coverage.gradle'
|
||||
|
@@ -41,6 +41,11 @@ mvcGroups {
|
||||
view = 'com.muwire.gui.I2PStatusView'
|
||||
controller = 'com.muwire.gui.I2PStatusController'
|
||||
}
|
||||
'system-status' {
|
||||
model = 'com.muwire.gui.SystemStatusModel'
|
||||
view = 'com.muwire.gui.SystemStatusView'
|
||||
controller = 'com.muwire.gui.SystemStatusController'
|
||||
}
|
||||
'trust-list' {
|
||||
model = 'com.muwire.gui.TrustListModel'
|
||||
view = 'com.muwire.gui.TrustListView'
|
||||
@@ -71,4 +76,14 @@ mvcGroups {
|
||||
view = 'com.muwire.gui.CloseWarningView'
|
||||
controller = 'com.muwire.gui.CloseWarningController'
|
||||
}
|
||||
'update' {
|
||||
model = 'com.muwire.gui.UpdateModel'
|
||||
view = 'com.muwire.gui.UpdateView'
|
||||
controller = 'com.muwire.gui.UpdateController'
|
||||
}
|
||||
'advanced-sharing' {
|
||||
model = 'com.muwire.gui.AdvancedSharingModel'
|
||||
view = 'com.muwire.gui.AdvancedSharingView'
|
||||
controller = 'com.muwire.gui.AdvancedSharingController'
|
||||
}
|
||||
}
|
||||
|
@@ -7,7 +7,9 @@ import griffon.metadata.ArtifactProviderFor
|
||||
import net.i2p.data.Base64
|
||||
|
||||
import javax.annotation.Nonnull
|
||||
import javax.swing.JOptionPane
|
||||
|
||||
import com.muwire.core.Constants
|
||||
import com.muwire.core.Core
|
||||
import com.muwire.core.files.UICommentEvent
|
||||
import com.muwire.core.util.DataUtil
|
||||
@@ -24,6 +26,11 @@ class AddCommentController {
|
||||
@ControllerAction
|
||||
void save() {
|
||||
String comment = view.textarea.getText()
|
||||
if (comment.length() > Constants.MAX_COMMENT_LENGTH ) {
|
||||
JOptionPane.showMessageDialog(null, "Your comment is too long - ${comment.length()} bytes. The maximum size is $Constants.MAX_COMMENT_LENGTH bytes",
|
||||
"Comment Too Long", JOptionPane.WARNING_MESSAGE)
|
||||
return
|
||||
}
|
||||
if (comment.trim().length() == 0)
|
||||
comment = null
|
||||
else
|
||||
|
@@ -0,0 +1,17 @@
|
||||
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
|
||||
|
||||
@ArtifactProviderFor(GriffonController)
|
||||
class AdvancedSharingController {
|
||||
@MVCMember @Nonnull
|
||||
AdvancedSharingModel model
|
||||
@MVCMember @Nonnull
|
||||
AdvancedSharingView view
|
||||
}
|
@@ -97,9 +97,6 @@ class ContentPanelController {
|
||||
}
|
||||
|
||||
void saveMuWireSettings() {
|
||||
File f = new File(core.home, "MuWire.properties")
|
||||
f.withOutputStream {
|
||||
core.muOptions.write(it)
|
||||
}
|
||||
core.saveMuSettings()
|
||||
}
|
||||
}
|
@@ -7,7 +7,12 @@ import griffon.core.mvc.MVCGroup
|
||||
import griffon.core.mvc.MVCGroupConfiguration
|
||||
import griffon.inject.MVCMember
|
||||
import griffon.metadata.ArtifactProviderFor
|
||||
import net.i2p.crypto.DSAEngine
|
||||
import net.i2p.data.Base64
|
||||
import net.i2p.data.Signature
|
||||
|
||||
import java.awt.event.ActionEvent
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
import javax.annotation.Nonnull
|
||||
import javax.inject.Inject
|
||||
@@ -47,11 +52,29 @@ class MainFrameController {
|
||||
private volatile Core core
|
||||
|
||||
@ControllerAction
|
||||
void search() {
|
||||
void clearSearch() {
|
||||
def searchField = builder.getVariable("search-field")
|
||||
searchField.setSelectedItem(null)
|
||||
searchField.requestFocus()
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void search(ActionEvent evt) {
|
||||
if (evt?.getActionCommand() == null)
|
||||
return
|
||||
def cardsPanel = builder.getVariable("cards-panel")
|
||||
cardsPanel.getLayout().show(cardsPanel, "search window")
|
||||
|
||||
def search = builder.getVariable("search-field").text
|
||||
def searchField = builder.getVariable("search-field")
|
||||
def search = searchField.getSelectedItem()
|
||||
searchField.model.addElement(search)
|
||||
performSearch(search)
|
||||
}
|
||||
|
||||
private void performSearch(String search) {
|
||||
|
||||
model.sessionRestored = true
|
||||
|
||||
search = search.trim()
|
||||
if (search.length() == 0)
|
||||
return
|
||||
@@ -77,21 +100,28 @@ class MainFrameController {
|
||||
}
|
||||
|
||||
def searchEvent
|
||||
byte [] payload
|
||||
if (hashSearch) {
|
||||
searchEvent = new SearchEvent(searchHash : root, uuid : uuid, oobInfohash: true, compressedResults : true)
|
||||
payload = root
|
||||
} else {
|
||||
// this can be improved a lot
|
||||
def replaced = search.toLowerCase().trim().replaceAll(SplitPattern.SPLIT_PATTERN, " ")
|
||||
def terms = replaced.split(" ")
|
||||
def nonEmpty = []
|
||||
terms.each { if (it.length() > 0) nonEmpty << it }
|
||||
searchEvent = new SearchEvent(searchTerms : nonEmpty, uuid : uuid, oobInfohash: true,
|
||||
searchComments : core.muOptions.searchComments, compressedResults : true)
|
||||
payload = String.join(" ",nonEmpty).getBytes(StandardCharsets.UTF_8)
|
||||
searchEvent = new SearchEvent(searchTerms : nonEmpty, uuid : uuid, oobInfohash: true,
|
||||
searchComments : core.muOptions.searchComments, compressedResults : true)
|
||||
}
|
||||
boolean firstHop = core.muOptions.allowUntrusted || core.muOptions.searchExtraHop
|
||||
|
||||
Signature sig = DSAEngine.getInstance().sign(payload, core.spk)
|
||||
|
||||
core.eventBus.publish(new QueryEvent(searchEvent : searchEvent, firstHop : firstHop,
|
||||
replyTo: core.me.destination, receivedOn: core.me.destination,
|
||||
originator : core.me))
|
||||
replyTo: core.me.destination, receivedOn: core.me.destination,
|
||||
originator : core.me, sig : sig.data))
|
||||
|
||||
}
|
||||
|
||||
void search(String infoHash, String tabTitle) {
|
||||
@@ -297,12 +327,17 @@ class MainFrameController {
|
||||
void clearUploads() {
|
||||
model.uploads.removeAll { it.finished }
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void restoreSession() {
|
||||
model.sessionRestored = true
|
||||
view.settings.openTabs.each {
|
||||
performSearch(it)
|
||||
}
|
||||
}
|
||||
|
||||
void saveMuWireSettings() {
|
||||
File f = new File(core.home, "MuWire.properties")
|
||||
f.withOutputStream {
|
||||
core.muOptions.write(it)
|
||||
}
|
||||
core.saveMuSettings()
|
||||
}
|
||||
|
||||
void mvcGroupInit(Map<String, String> args) {
|
||||
|
@@ -10,6 +10,7 @@ import java.util.logging.Level
|
||||
|
||||
import javax.annotation.Nonnull
|
||||
import javax.swing.JFileChooser
|
||||
import javax.swing.JOptionPane
|
||||
|
||||
import com.muwire.core.Core
|
||||
import com.muwire.core.MuWireSettings
|
||||
@@ -20,12 +21,14 @@ class OptionsController {
|
||||
OptionsModel model
|
||||
@MVCMember @Nonnull
|
||||
OptionsView view
|
||||
|
||||
Core core
|
||||
MuWireSettings settings
|
||||
UISettings uiSettings
|
||||
|
||||
@ControllerAction
|
||||
void save() {
|
||||
String text
|
||||
Core core = application.context.get("core")
|
||||
MuWireSettings settings = application.context.get("muwire-settings")
|
||||
|
||||
def i2pProps = core.i2pOptions
|
||||
|
||||
@@ -137,14 +140,10 @@ class OptionsController {
|
||||
model.trustListInterval = trustListInterval
|
||||
settings.trustListInterval = Integer.parseInt(trustListInterval)
|
||||
|
||||
File settingsFile = new File(core.home, "MuWire.properties")
|
||||
settingsFile.withOutputStream {
|
||||
settings.write(it)
|
||||
}
|
||||
core.saveMuSettings()
|
||||
|
||||
// UI Setttings
|
||||
|
||||
UISettings uiSettings = application.context.get("ui-settings")
|
||||
text = view.lnfField.text
|
||||
model.lnf = text
|
||||
uiSettings.lnf = text
|
||||
@@ -172,16 +171,24 @@ class OptionsController {
|
||||
model.clearUploads = clearUploads
|
||||
uiSettings.clearUploads = clearUploads
|
||||
|
||||
boolean storeSearchHistory = view.storeSearchHistoryCheckbox.model.isSelected()
|
||||
model.storeSearchHistory = storeSearchHistory
|
||||
uiSettings.storeSearchHistory = storeSearchHistory
|
||||
|
||||
uiSettings.exitOnClose = model.exitOnClose
|
||||
if (model.closeDecisionMade)
|
||||
uiSettings.closeWarning = false
|
||||
|
||||
saveUISettings()
|
||||
|
||||
cancel()
|
||||
}
|
||||
|
||||
private void saveUISettings() {
|
||||
File uiSettingsFile = new File(core.home, "gui.properties")
|
||||
uiSettingsFile.withOutputStream {
|
||||
uiSettings.write(it)
|
||||
}
|
||||
|
||||
cancel()
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
@@ -234,4 +241,11 @@ class OptionsController {
|
||||
model.exitOnClose = false
|
||||
model.closeDecisionMade = true
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void clearHistory() {
|
||||
uiSettings.searchHistory.clear()
|
||||
saveUISettings()
|
||||
JOptionPane.showMessageDialog(null, "Search history has been cleared")
|
||||
}
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
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 SystemStatusController {
|
||||
@MVCMember @Nonnull
|
||||
SystemStatusModel model
|
||||
@MVCMember @Nonnull
|
||||
SystemStatusView view
|
||||
|
||||
@ControllerAction
|
||||
void refresh() {
|
||||
|
||||
long totalRam = Runtime.getRuntime().totalMemory()
|
||||
long usedRam = totalRam - Runtime.getRuntime().freeMemory()
|
||||
|
||||
model.usedRam = usedRam
|
||||
model.totalRam = totalRam
|
||||
model.maxRam = Runtime.getRuntime().maxMemory()
|
||||
model.javaVendor = System.getProperty("java.vendor")
|
||||
model.javaVersion = System.getProperty("java.version")
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void close() {
|
||||
view.dialog.setVisible(false)
|
||||
mvcGroup.destroy()
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
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 UpdateController {
|
||||
@MVCMember @Nonnull
|
||||
UpdateView view
|
||||
@MVCMember @Nonnull
|
||||
UpdateModel model
|
||||
|
||||
@ControllerAction
|
||||
void close() {
|
||||
view.dialog.setVisible(false)
|
||||
mvcGroup.destroy()
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void search() {
|
||||
mvcGroup.parentGroup.controller.search(model.available.infoHash, "MuWire update")
|
||||
close()
|
||||
}
|
||||
}
|
@@ -117,6 +117,7 @@ class Initialize extends AbstractLifecycleHandler {
|
||||
|
||||
def guiPropsFile = new File(home, "gui.properties")
|
||||
UISettings uiSettings
|
||||
int rowHeight = 15
|
||||
if (guiPropsFile.exists()) {
|
||||
Properties props = new Properties()
|
||||
guiPropsFile.withInputStream { props.load(it) }
|
||||
@@ -149,15 +150,17 @@ class Initialize extends AbstractLifecycleHandler {
|
||||
} else {
|
||||
fontSize = uiSettings.fontSize
|
||||
}
|
||||
|
||||
rowHeight = fontSize + 3
|
||||
FontUIResource font = new FontUIResource(fontName, Font.PLAIN, fontSize)
|
||||
|
||||
def keys = lnf.getDefaults().keys()
|
||||
while(keys.hasMoreElements()) {
|
||||
def key = keys.nextElement()
|
||||
def value = lnf.getDefaults().get(key)
|
||||
if (value instanceof FontUIResource)
|
||||
if (value instanceof FontUIResource) {
|
||||
lnf.getDefaults().put(key, font)
|
||||
UIManager.put(key, font)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -181,6 +184,7 @@ class Initialize extends AbstractLifecycleHandler {
|
||||
}
|
||||
}
|
||||
|
||||
application.context.put("row-height", rowHeight)
|
||||
application.context.put("ui-settings", uiSettings)
|
||||
}
|
||||
|
||||
|
@@ -41,9 +41,9 @@ class Ready extends AbstractLifecycleHandler {
|
||||
def propsFile = new File(home, "MuWire.properties")
|
||||
if (propsFile.exists()) {
|
||||
log.info("loading existing props file")
|
||||
propsFile.withInputStream {
|
||||
propsFile.withReader("UTF-8", {
|
||||
props.load(it)
|
||||
}
|
||||
})
|
||||
props = new MuWireSettings(props)
|
||||
if (props.incompleteLocation == null)
|
||||
props.incompleteLocation = new File(home, "incompletes")
|
||||
@@ -90,9 +90,9 @@ class Ready extends AbstractLifecycleHandler {
|
||||
props.downloadLocation = chooser.getSelectedFile()
|
||||
}
|
||||
|
||||
propsFile.withOutputStream {
|
||||
propsFile.withPrintWriter("UTF-8", {
|
||||
props.write(it)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Core core
|
||||
|
@@ -0,0 +1,39 @@
|
||||
package com.muwire.gui
|
||||
|
||||
import javax.swing.tree.DefaultMutableTreeNode
|
||||
import javax.swing.tree.DefaultTreeModel
|
||||
import javax.swing.tree.MutableTreeNode
|
||||
|
||||
import com.muwire.core.Core
|
||||
import com.muwire.core.files.FileTree
|
||||
|
||||
import griffon.core.artifact.GriffonModel
|
||||
import griffon.transform.Observable
|
||||
import griffon.metadata.ArtifactProviderFor
|
||||
|
||||
@ArtifactProviderFor(GriffonModel)
|
||||
class AdvancedSharingModel {
|
||||
def watchedDirectories = []
|
||||
def treeRoot
|
||||
def negativeTree
|
||||
|
||||
Core core
|
||||
|
||||
void mvcGroupInit(Map<String,String> args) {
|
||||
watchedDirectories.addAll(core.muOptions.watchedDirectories)
|
||||
|
||||
treeRoot = new DefaultMutableTreeNode()
|
||||
negativeTree = new DefaultTreeModel(treeRoot)
|
||||
copyTree(treeRoot, core.fileManager.negativeTree.root)
|
||||
}
|
||||
|
||||
private void copyTree(DefaultMutableTreeNode jtreeNode, FileTree.TreeNode fileTreeNode) {
|
||||
jtreeNode.setUserObject(fileTreeNode.file?.getName())
|
||||
fileTreeNode.children.each {
|
||||
MutableTreeNode newChild = new DefaultMutableTreeNode()
|
||||
jtreeNode.add(newChild)
|
||||
copyTree(newChild, it)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,3 +1,4 @@
|
||||
|
||||
package com.muwire.gui
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
@@ -35,6 +36,7 @@ import com.muwire.core.files.FileHashingEvent
|
||||
import com.muwire.core.files.FileLoadedEvent
|
||||
import com.muwire.core.files.FileSharedEvent
|
||||
import com.muwire.core.files.FileUnsharedEvent
|
||||
import com.muwire.core.files.SideCarFileEvent
|
||||
import com.muwire.core.search.QueryEvent
|
||||
import com.muwire.core.search.SearchEvent
|
||||
import com.muwire.core.search.UIResultBatchEvent
|
||||
@@ -86,6 +88,8 @@ class MainFrameModel {
|
||||
def trusted = []
|
||||
def distrusted = []
|
||||
def subscriptions = []
|
||||
|
||||
boolean sessionRestored
|
||||
|
||||
@Observable int connections
|
||||
@Observable String me
|
||||
@@ -189,6 +193,7 @@ class MainFrameModel {
|
||||
core.eventBus.register(ConnectionEvent.class, this)
|
||||
core.eventBus.register(DisconnectionEvent.class, this)
|
||||
core.eventBus.register(FileHashedEvent.class, this)
|
||||
core.eventBus.register(SideCarFileEvent.class, this)
|
||||
core.eventBus.register(FileHashingEvent.class, this)
|
||||
core.eventBus.register(FileLoadedEvent.class, this)
|
||||
core.eventBus.register(UploadEvent.class, this)
|
||||
@@ -252,7 +257,7 @@ class MainFrameModel {
|
||||
|
||||
void onAllFilesLoadedEvent(AllFilesLoadedEvent e) {
|
||||
runInsideUIAsync {
|
||||
core.muOptions.watchedDirectories.each { core.eventBus.publish(new DirectoryWatchedEvent(directory : new File(it))) }
|
||||
core.muOptions.watchedDirectories.each { core.eventBus.publish(new FileSharedEvent(file : new File(it))) }
|
||||
|
||||
core.muOptions.trustSubscriptions.each {
|
||||
core.eventBus.publish(new TrustSubscriptionEvent(persona : it, subscribe : true))
|
||||
@@ -262,8 +267,10 @@ class MainFrameModel {
|
||||
|
||||
void onUpdateDownloadedEvent(UpdateDownloadedEvent e) {
|
||||
runInsideUIAsync {
|
||||
JOptionPane.showMessageDialog(null, "MuWire $e.version has been downloaded. You can update now",
|
||||
"Update Downloaded", JOptionPane.INFORMATION_MESSAGE)
|
||||
Map<String, Object> args = new HashMap<>()
|
||||
args['available'] = null
|
||||
args['downloaded'] = e
|
||||
mvcGroup.createMVCGroup("update", "update", args)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,6 +297,8 @@ class MainFrameModel {
|
||||
runInsideUIAsync {
|
||||
connections = core.connectionManager.getConnections().size()
|
||||
|
||||
view.showRestoreOrEmpty()
|
||||
|
||||
if (connections > 0) {
|
||||
def topPanel = builder.getVariable("top-panel")
|
||||
topPanel.getLayout().show(topPanel, "top-search-panel")
|
||||
@@ -527,13 +536,10 @@ class MainFrameModel {
|
||||
|
||||
void onUpdateAvailableEvent(UpdateAvailableEvent e) {
|
||||
runInsideUIAsync {
|
||||
|
||||
int option = JOptionPane.showConfirmDialog(null,
|
||||
"MuWire $e.version is available from $e.signer. You have "+ metadata["application.version"]+" Update?",
|
||||
"New MuWire version availble", JOptionPane.OK_CANCEL_OPTION)
|
||||
if (option == JOptionPane.CANCEL_OPTION)
|
||||
return
|
||||
controller.search(e.infoHash,"MuWire update")
|
||||
Map<String, Object> args = new HashMap<>()
|
||||
args['available'] = e
|
||||
args['downloaded'] = null
|
||||
mvcGroup.createMVCGroup("update", "update", args)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -559,6 +565,12 @@ class MainFrameModel {
|
||||
}
|
||||
}
|
||||
|
||||
void onSideCarFileEvent(SideCarFileEvent e) {
|
||||
runInsideUIAsync {
|
||||
view.refreshSharedFiles()
|
||||
}
|
||||
}
|
||||
|
||||
private void insertIntoTree(SharedFile file) {
|
||||
List<File> parents = new ArrayList<>()
|
||||
File tmp = file.file.getParentFile()
|
||||
|
@@ -21,6 +21,7 @@ class OptionsModel {
|
||||
@Observable int speedSmoothSeconds
|
||||
@Observable int totalUploadSlots
|
||||
@Observable int uploadSlotsPerUser
|
||||
@Observable boolean storeSearchHistory
|
||||
|
||||
// i2p options
|
||||
@Observable String inboundLength
|
||||
@@ -90,6 +91,7 @@ class OptionsModel {
|
||||
showSearchHashes = uiSettings.showSearchHashes
|
||||
clearUploads = uiSettings.clearUploads
|
||||
exitOnClose = uiSettings.exitOnClose
|
||||
storeSearchHistory = uiSettings.storeSearchHistory
|
||||
|
||||
if (core.router != null) {
|
||||
inBw = String.valueOf(settings.inBw)
|
||||
|
@@ -0,0 +1,24 @@
|
||||
package com.muwire.gui
|
||||
|
||||
import javax.annotation.Nonnull
|
||||
|
||||
import griffon.core.artifact.GriffonModel
|
||||
import griffon.inject.MVCMember
|
||||
import griffon.transform.Observable
|
||||
import griffon.metadata.ArtifactProviderFor
|
||||
|
||||
@ArtifactProviderFor(GriffonModel)
|
||||
class SystemStatusModel {
|
||||
@MVCMember @Nonnull
|
||||
SystemStatusController controller
|
||||
|
||||
@Observable String javaVendor
|
||||
@Observable String javaVersion
|
||||
@Observable long usedRam
|
||||
@Observable long totalRam
|
||||
@Observable long maxRam
|
||||
|
||||
void mvcGroupInit(Map<String,String> args) {
|
||||
controller.refresh()
|
||||
}
|
||||
}
|
14
gui/griffon-app/models/com/muwire/gui/UpdateModel.groovy
Normal file
14
gui/griffon-app/models/com/muwire/gui/UpdateModel.groovy
Normal file
@@ -0,0 +1,14 @@
|
||||
package com.muwire.gui
|
||||
|
||||
import com.muwire.core.update.UpdateAvailableEvent
|
||||
import com.muwire.core.update.UpdateDownloadedEvent
|
||||
|
||||
import griffon.core.artifact.GriffonModel
|
||||
import griffon.transform.Observable
|
||||
import griffon.metadata.ArtifactProviderFor
|
||||
|
||||
@ArtifactProviderFor(GriffonModel)
|
||||
class UpdateModel {
|
||||
UpdateAvailableEvent available
|
||||
UpdateDownloadedEvent downloaded
|
||||
}
|
@@ -43,7 +43,7 @@ class AddCommentView {
|
||||
borderLayout()
|
||||
panel (constraints : BorderLayout.CENTER) {
|
||||
scrollPane {
|
||||
textarea = textArea(text : comment, rows : 20, columns : 100, editable : true)
|
||||
textarea = textArea(text : comment, rows : 20, columns : 100, editable : true, lineWrap:true, wrapStyleWord:true)
|
||||
}
|
||||
}
|
||||
panel (constraints : BorderLayout.SOUTH) {
|
||||
|
@@ -0,0 +1,82 @@
|
||||
package com.muwire.gui
|
||||
|
||||
import griffon.core.artifact.GriffonView
|
||||
import griffon.inject.MVCMember
|
||||
import griffon.metadata.ArtifactProviderFor
|
||||
|
||||
import javax.swing.JDialog
|
||||
import javax.swing.JTabbedPane
|
||||
import javax.swing.JTree
|
||||
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 AdvancedSharingView {
|
||||
@MVCMember @Nonnull
|
||||
FactoryBuilderSupport builder
|
||||
@MVCMember @Nonnull
|
||||
AdvancedSharingModel model
|
||||
|
||||
def mainFrame
|
||||
def dialog
|
||||
def watchedDirsPanel
|
||||
def negativeTreePanel
|
||||
|
||||
def watchedDirsTable
|
||||
|
||||
void initUI() {
|
||||
mainFrame = application.windowManager.findWindow("main-frame")
|
||||
int rowHeight = application.context.get("row-height")
|
||||
dialog = new JDialog(mainFrame,"Advanced Sharing",true)
|
||||
dialog.setResizable(true)
|
||||
|
||||
watchedDirsPanel = builder.panel {
|
||||
borderLayout()
|
||||
panel (constraints : BorderLayout.NORTH) {
|
||||
label(text : "Directories watched for file changes")
|
||||
}
|
||||
scrollPane( constraints : BorderLayout.CENTER ) {
|
||||
watchedDirsTable = table(autoCreateRowSorter : true, rowHeight : rowHeight) {
|
||||
tableModel(list : model.watchedDirectories) {
|
||||
closureColumn(header : "Directory", type : String, read : {it})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
negativeTreePanel = builder.panel {
|
||||
borderLayout()
|
||||
panel(constraints : BorderLayout.NORTH) {
|
||||
label(text : "Files which are explicitly not shared")
|
||||
}
|
||||
scrollPane( constraints : BorderLayout.CENTER ) {
|
||||
def jtree = new JTree(model.negativeTree)
|
||||
tree(rootVisible : false, rowHeight : rowHeight,jtree)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mvcGroupInit(Map<String,String> args) {
|
||||
def tabbedPane = new JTabbedPane()
|
||||
tabbedPane.addTab("Watched Directories", watchedDirsPanel)
|
||||
tabbedPane.addTab("Negative Tree", negativeTreePanel)
|
||||
|
||||
dialog.with {
|
||||
getContentPane().add(tabbedPane)
|
||||
pack()
|
||||
setLocationRelativeTo(mainFrame)
|
||||
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
|
||||
addWindowListener(new WindowAdapter() {
|
||||
public void windowClosed(WindowEvent e) {
|
||||
mvcGroup.destroy()
|
||||
}
|
||||
})
|
||||
show()
|
||||
}
|
||||
}
|
||||
}
|
@@ -38,6 +38,7 @@ class ContentPanelView {
|
||||
|
||||
void initUI() {
|
||||
mainFrame = application.windowManager.findWindow("main-frame")
|
||||
int rowHeight = application.context.get("row-height")
|
||||
dialog = new JDialog(mainFrame, "Content Control Panel", true)
|
||||
|
||||
mainPanel = builder.panel {
|
||||
@@ -48,7 +49,7 @@ class ContentPanelView {
|
||||
label(text : "Rules")
|
||||
}
|
||||
scrollPane (constraints : BorderLayout.CENTER) {
|
||||
rulesTable = table(id : "rules-table", autoCreateRowSorter : true) {
|
||||
rulesTable = table(id : "rules-table", autoCreateRowSorter : true, rowHeight : rowHeight) {
|
||||
tableModel(list : model.rules) {
|
||||
closureColumn(header: "Term", type:String, read: {row -> row.getTerm()})
|
||||
closureColumn(header: "Regex?", type:Boolean, read: {row -> row instanceof RegexMatcher})
|
||||
@@ -74,7 +75,7 @@ class ContentPanelView {
|
||||
label(text : "Hits")
|
||||
}
|
||||
scrollPane(constraints : BorderLayout.CENTER) {
|
||||
hitsTable = table(id : "hits-table", autoCreateRowSorter : true) {
|
||||
hitsTable = table(id : "hits-table", autoCreateRowSorter : true, rowHeight : rowHeight) {
|
||||
tableModel(list : model.hits) {
|
||||
closureColumn(header : "Searcher", type : String, read : {row -> row.persona.getHumanReadableName()})
|
||||
closureColumn(header : "Keywords", type : String, read : {row -> row.keywords.join(" ")})
|
||||
|
@@ -3,6 +3,7 @@ package com.muwire.gui
|
||||
import griffon.core.artifact.GriffonView
|
||||
import griffon.inject.MVCMember
|
||||
import griffon.metadata.ArtifactProviderFor
|
||||
import net.i2p.data.DataHelper
|
||||
|
||||
import javax.swing.JDialog
|
||||
import javax.swing.JPanel
|
||||
@@ -64,10 +65,10 @@ class I2PStatusView {
|
||||
panel(border : titledBorder(title : "Bandwidth", border : etchedBorder(), titlePosition : TitledBorder.TOP),
|
||||
constraints : gbc(gridx: 0, gridy: 3, fill : GridBagConstraints.HORIZONTAL, weightx: 100)) {
|
||||
gridBagLayout()
|
||||
label(text : "Receive Bps (15 seconds)", constraints : gbc(gridx:0, gridy:0, anchor: GridBagConstraints.LINE_START, weightx: 100))
|
||||
label(text : bind {model.receiveBps}, constraints : gbc(gridx: 1, gridy:0, anchor : GridBagConstraints.LINE_END))
|
||||
label(text : "Send Bps (15 seconds)", constraints : gbc(gridx:0, gridy:1, anchor: GridBagConstraints.LINE_START, weightx: 100))
|
||||
label(text : bind {model.sendBps}, constraints : gbc(gridx: 1, gridy:1, anchor : GridBagConstraints.LINE_END))
|
||||
label(text : "Receive (15 seconds)", constraints : gbc(gridx:0, gridy:0, anchor: GridBagConstraints.LINE_START, weightx: 100))
|
||||
label(text : bind {DataHelper.formatSize2Decimal(model.receiveBps,false)+"B"}, constraints : gbc(gridx: 1, gridy:0, anchor : GridBagConstraints.LINE_END))
|
||||
label(text : "Send (15 seconds)", constraints : gbc(gridx:0, gridy:1, anchor: GridBagConstraints.LINE_START, weightx: 100))
|
||||
label(text : bind {DataHelper.formatSize2Decimal(model.sendBps, false)+"B"}, constraints : gbc(gridx: 1, gridy:1, anchor : GridBagConstraints.LINE_END))
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -11,6 +11,7 @@ import net.i2p.data.DataHelper
|
||||
import javax.swing.BorderFactory
|
||||
import javax.swing.Box
|
||||
import javax.swing.BoxLayout
|
||||
import javax.swing.JComboBox
|
||||
import javax.swing.JFileChooser
|
||||
import javax.swing.JFrame
|
||||
import javax.swing.JLabel
|
||||
@@ -24,6 +25,10 @@ import javax.swing.SwingConstants
|
||||
import javax.swing.SwingUtilities
|
||||
import javax.swing.TransferHandler
|
||||
import javax.swing.border.Border
|
||||
import javax.swing.event.DocumentEvent
|
||||
import javax.swing.event.DocumentListener
|
||||
import javax.swing.event.TreeExpansionEvent
|
||||
import javax.swing.event.TreeExpansionListener
|
||||
import javax.swing.table.DefaultTableCellRenderer
|
||||
import javax.swing.tree.TreeNode
|
||||
import javax.swing.tree.TreePath
|
||||
@@ -67,11 +72,14 @@ class MainFrameView {
|
||||
def lastDownloadSortEvent
|
||||
def lastSharedSortEvent
|
||||
def trustTablesSortEvents = [:]
|
||||
def expansionListener = new TreeExpansions()
|
||||
|
||||
|
||||
UISettings settings
|
||||
|
||||
void initUI() {
|
||||
settings = application.context.get("ui-settings")
|
||||
int rowHeight = application.context.get("row-height")
|
||||
builder.with {
|
||||
application(size : [1024,768], id: 'main-frame',
|
||||
locationRelativeTo : null,
|
||||
@@ -89,17 +97,31 @@ class MainFrameView {
|
||||
menuItem("Exit", actionPerformed : {closeApplication()})
|
||||
}
|
||||
menu (text : "Options") {
|
||||
menuItem("Configuration", actionPerformed : {mvcGroup.createMVCGroup("Options")})
|
||||
menuItem("Content Control", actionPerformed : {
|
||||
def env = [:]
|
||||
env["core"] = model.core
|
||||
mvcGroup.createMVCGroup("content-panel", env)
|
||||
menuItem("Configuration", actionPerformed : {
|
||||
def params = [:]
|
||||
params['core'] = application.context.get("core")
|
||||
params['settings'] = params['core'].muOptions
|
||||
params['uiSettings'] = settings
|
||||
mvcGroup.createMVCGroup("Options", params)
|
||||
})
|
||||
}
|
||||
menu (text : "Status") {
|
||||
menuItem("MuWire", actionPerformed : {mvcGroup.createMVCGroup("mu-wire-status")})
|
||||
MuWireSettings muSettings = application.context.get("muwire-settings")
|
||||
menuItem("I2P", enabled : bind {model.routerPresent}, actionPerformed: {mvcGroup.createMVCGroup("i-2-p-status")})
|
||||
menuItem("System", actionPerformed : {mvcGroup.createMVCGroup("system-status")})
|
||||
}
|
||||
menu (text : "Tools") {
|
||||
menuItem("Content Control", actionPerformed : {
|
||||
def env = [:]
|
||||
env["core"] = model.core
|
||||
mvcGroup.createMVCGroup("content-panel", env)
|
||||
})
|
||||
menuItem("Advanced Sharing", actionPerformed : {
|
||||
def env = [:]
|
||||
env["core"] = model.core
|
||||
mvcGroup.createMVCGroup("advanced-sharing",env)
|
||||
})
|
||||
}
|
||||
}
|
||||
borderLayout()
|
||||
@@ -123,20 +145,39 @@ class MainFrameView {
|
||||
panel(constraints: BorderLayout.CENTER) {
|
||||
borderLayout()
|
||||
label(" Enter search here:", constraints: BorderLayout.WEST) // TODO: fix this
|
||||
textField(id: "search-field", constraints: BorderLayout.CENTER, action : searchAction)
|
||||
|
||||
def searchFieldModel = new SearchFieldModel(settings, new File(application.context.get("muwire-home")))
|
||||
JComboBox myComboBox = new SearchField(searchFieldModel)
|
||||
myComboBox.setAction(searchAction)
|
||||
widget(id: "search-field", constraints: BorderLayout.CENTER, myComboBox)
|
||||
|
||||
}
|
||||
panel( constraints: BorderLayout.EAST) {
|
||||
button(text: "Search", searchAction)
|
||||
button(text : "", icon : imageIcon("/close_tab.png"), clearSearchAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
panel (id: "cards-panel", constraints : BorderLayout.CENTER) {
|
||||
cardLayout()
|
||||
panel (constraints : "search window") {
|
||||
borderLayout()
|
||||
tabbedPane(id : "result-tabs", constraints: BorderLayout.CENTER)
|
||||
panel (id : "search window", constraints : "search window") {
|
||||
cardLayout()
|
||||
panel (constraints : "tabs-panel") {
|
||||
borderLayout()
|
||||
tabbedPane(id : "result-tabs", constraints: BorderLayout.CENTER)
|
||||
}
|
||||
panel(constraints : "restore session") {
|
||||
borderLayout()
|
||||
panel (constraints : BorderLayout.CENTER) {
|
||||
gridBagLayout()
|
||||
label(text : "Saved Tabs:", constraints : gbc(gridx : 0, gridy : 0))
|
||||
scrollPane (constraints : gbc(gridx : 0, gridy : 1)) {
|
||||
list(items : new ArrayList(settings.openTabs))
|
||||
}
|
||||
button(text : "Restore Session", constraints : gbc(gridx :0, gridy : 2), restoreSessionAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
panel (constraints: "downloads window") {
|
||||
gridLayout(rows : 1, cols : 1)
|
||||
@@ -144,7 +185,7 @@ class MainFrameView {
|
||||
panel {
|
||||
borderLayout()
|
||||
scrollPane (constraints : BorderLayout.CENTER) {
|
||||
downloadsTable = table(id : "downloads-table", autoCreateRowSorter : true) {
|
||||
downloadsTable = table(id : "downloads-table", autoCreateRowSorter : true, rowHeight : rowHeight) {
|
||||
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()})
|
||||
@@ -223,7 +264,7 @@ class MainFrameView {
|
||||
panel (constraints : "shared files table") {
|
||||
borderLayout()
|
||||
scrollPane(constraints : BorderLayout.CENTER) {
|
||||
table(id : "shared-files-table", autoCreateRowSorter: true) {
|
||||
table(id : "shared-files-table", autoCreateRowSorter: true, rowHeight : rowHeight) {
|
||||
tableModel(list : model.shared) {
|
||||
closureColumn(header : "Name", preferredWidth : 500, type : String, read : {row -> row.getCachedPath()})
|
||||
closureColumn(header : "Size", preferredWidth : 50, type : Long, read : {row -> row.getCachedLength() })
|
||||
@@ -239,7 +280,7 @@ class MainFrameView {
|
||||
scrollPane(constraints : BorderLayout.CENTER) {
|
||||
def jtree = new JTree(model.sharedTree)
|
||||
jtree.setCellRenderer(new SharedTreeRenderer())
|
||||
tree(id : "shared-files-tree", rootVisible : false, expandsSelectedPaths: true, jtree)
|
||||
tree(id : "shared-files-tree", rowHeight : rowHeight, rootVisible : false, expandsSelectedPaths: true, jtree)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -269,7 +310,7 @@ class MainFrameView {
|
||||
label("Uploads")
|
||||
}
|
||||
scrollPane (constraints : BorderLayout.CENTER) {
|
||||
table(id : "uploads-table") {
|
||||
table(id : "uploads-table", rowHeight : rowHeight) {
|
||||
tableModel(list : model.uploads) {
|
||||
closureColumn(header : "Name", type : String, read : {row -> row.uploader.getName() })
|
||||
closureColumn(header : "Progress", type : String, read : { row ->
|
||||
@@ -315,7 +356,7 @@ class MainFrameView {
|
||||
label("Connections")
|
||||
}
|
||||
scrollPane(constraints : BorderLayout.CENTER) {
|
||||
table(id : "connections-table") {
|
||||
table(id : "connections-table", rowHeight : rowHeight) {
|
||||
tableModel(list : model.connectionList) {
|
||||
closureColumn(header : "Destination", preferredWidth: 250, type: String, read : { row -> row.destination.toBase32() })
|
||||
closureColumn(header : "Direction", preferredWidth: 20, type: String, read : { row ->
|
||||
@@ -334,7 +375,7 @@ class MainFrameView {
|
||||
label("Incoming searches")
|
||||
}
|
||||
scrollPane(constraints : BorderLayout.CENTER) {
|
||||
table(id : "searches-table") {
|
||||
table(id : "searches-table", rowHeight : rowHeight) {
|
||||
tableModel(list : model.searches) {
|
||||
closureColumn(header : "Keywords", type : String, read : {
|
||||
sanitized = it.search.replace('<', ' ')
|
||||
@@ -367,7 +408,7 @@ class MainFrameView {
|
||||
panel (border : etchedBorder()){
|
||||
borderLayout()
|
||||
scrollPane(constraints : BorderLayout.CENTER) {
|
||||
table(id : "trusted-table", autoCreateRowSorter : true) {
|
||||
table(id : "trusted-table", autoCreateRowSorter : true, rowHeight : rowHeight) {
|
||||
tableModel(list : model.trusted) {
|
||||
closureColumn(header : "Trusted Users", type : String, read : { it.getHumanReadableName() } )
|
||||
}
|
||||
@@ -383,7 +424,7 @@ class MainFrameView {
|
||||
panel (border : etchedBorder()){
|
||||
borderLayout()
|
||||
scrollPane(constraints : BorderLayout.CENTER) {
|
||||
table(id : "distrusted-table", autoCreateRowSorter : true) {
|
||||
table(id : "distrusted-table", autoCreateRowSorter : true, rowHeight : rowHeight) {
|
||||
tableModel(list : model.distrusted) {
|
||||
closureColumn(header: "Distrusted Users", type : String, read : { it.getHumanReadableName() } )
|
||||
}
|
||||
@@ -402,7 +443,7 @@ class MainFrameView {
|
||||
label(text : "Trust List Subscriptions")
|
||||
}
|
||||
scrollPane(constraints : BorderLayout.CENTER) {
|
||||
table(id : "subscription-table", autoCreateRowSorter : true) {
|
||||
table(id : "subscription-table", autoCreateRowSorter : true, rowHeight : rowHeight) {
|
||||
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()})
|
||||
@@ -472,6 +513,10 @@ class MainFrameView {
|
||||
}
|
||||
}})
|
||||
|
||||
// search field
|
||||
def searchField = builder.getVariable("search-field")
|
||||
|
||||
// downloads table
|
||||
def downloadsTable = builder.getVariable("downloads-table")
|
||||
def selectionModel = downloadsTable.getSelectionModel()
|
||||
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
|
||||
@@ -589,6 +634,8 @@ class MainFrameView {
|
||||
model.addCommentButtonEnabled = selectedNode != null
|
||||
|
||||
})
|
||||
|
||||
sharedFilesTree.addTreeExpansionListener(expansionListener)
|
||||
|
||||
// searches table
|
||||
def searchesTable = builder.getVariable("searches-table")
|
||||
@@ -694,6 +741,9 @@ class MainFrameView {
|
||||
|
||||
// show tree by default
|
||||
showSharedFilesTree.call()
|
||||
|
||||
// show search panel by default
|
||||
showSearchWindow.call()
|
||||
}
|
||||
|
||||
private static void showPopupMenu(JPopupMenu menu, MouseEvent event) {
|
||||
@@ -838,10 +888,23 @@ class MainFrameView {
|
||||
|
||||
showPopupMenu(menu, e)
|
||||
}
|
||||
|
||||
void showRestoreOrEmpty() {
|
||||
def searchWindow = builder.getVariable("search window")
|
||||
String id
|
||||
if (!model.sessionRestored && !settings.openTabs.isEmpty())
|
||||
id = model.connections > 0 ? "restore session" : "tabs-panel"
|
||||
else
|
||||
id = "tabs-panel"
|
||||
searchWindow.getLayout().show(searchWindow, id)
|
||||
}
|
||||
|
||||
def showSearchWindow = {
|
||||
def cardsPanel = builder.getVariable("cards-panel")
|
||||
cardsPanel.getLayout().show(cardsPanel, "search window")
|
||||
|
||||
showRestoreOrEmpty()
|
||||
|
||||
model.searchesPaneButtonEnabled = false
|
||||
model.downloadsPaneButtonEnabled = true
|
||||
model.uploadsPaneButtonEnabled = true
|
||||
@@ -929,16 +992,31 @@ class MainFrameView {
|
||||
public void refreshSharedFiles() {
|
||||
def tree = builder.getVariable("shared-files-tree")
|
||||
TreePath[] selectedPaths = tree.getSelectionPaths()
|
||||
Set<TreePath> expanded = new HashSet<>(expansionListener.expandedPaths)
|
||||
|
||||
model.sharedTree.nodeStructureChanged(model.treeRoot)
|
||||
|
||||
expanded.each { tree.expandPath(it) }
|
||||
tree.setSelectionPaths(selectedPaths)
|
||||
|
||||
builder.getVariable("shared-files-table").model.fireTableDataChanged()
|
||||
}
|
||||
|
||||
private void closeApplication() {
|
||||
Core core = application.getContext().get("core")
|
||||
|
||||
def tabbedPane = builder.getVariable("result-tabs")
|
||||
settings.openTabs.clear()
|
||||
int count = tabbedPane.getTabCount()
|
||||
for (int i = 0; i < count; i++)
|
||||
settings.openTabs.add(tabbedPane.getTitleAt(i))
|
||||
|
||||
File uiPropsFile = new File(core.home, "gui.properties")
|
||||
uiPropsFile.withOutputStream { settings.write(it) }
|
||||
|
||||
def mainFrame = builder.getVariable("main-frame")
|
||||
mainFrame.setVisible(false)
|
||||
application.getWindowManager().findWindow("shutdown-window").setVisible(true)
|
||||
Core core = application.getContext().get("core")
|
||||
if (core != null) {
|
||||
Thread t = new Thread({
|
||||
core.shutdown()
|
||||
@@ -947,4 +1025,19 @@ class MainFrameView {
|
||||
t.start()
|
||||
}
|
||||
}
|
||||
|
||||
private static class TreeExpansions implements TreeExpansionListener {
|
||||
private final Set<TreePath> expandedPaths = new HashSet<>()
|
||||
|
||||
|
||||
@Override
|
||||
public void treeExpanded(TreeExpansionEvent event) {
|
||||
expandedPaths.add(event.getPath())
|
||||
}
|
||||
|
||||
@Override
|
||||
public void treeCollapsed(TreeExpansionEvent event) {
|
||||
expandedPaths.remove(event.getPath())
|
||||
}
|
||||
}
|
||||
}
|
@@ -61,6 +61,7 @@ class OptionsView {
|
||||
def excludeLocalResultCheckbox
|
||||
def showSearchHashesCheckbox
|
||||
def clearUploadsCheckbox
|
||||
def storeSearchHistoryCheckbox
|
||||
|
||||
def inBwField
|
||||
def outBwField
|
||||
@@ -196,8 +197,17 @@ class OptionsView {
|
||||
constraints : gbc(gridx : 3, gridy : 2, anchor : GridBagConstraints.LINE_END))
|
||||
|
||||
}
|
||||
panel (border : titledBorder(title : "Other Settings", border : etchedBorder(), titlePosition : TitledBorder.TOP),
|
||||
panel (border : titledBorder(title : "Search Settings", border : etchedBorder(), titlePosition : TitledBorder.TOP),
|
||||
constraints : gbc(gridx : 0, gridy : 1, fill : GridBagConstraints.HORIZONTAL, weightx : 100)) {
|
||||
gridBagLayout()
|
||||
label(text : "Remember search history", constraints: gbc(gridx: 0, gridy:0, anchor : GridBagConstraints.LINE_START, weightx: 100))
|
||||
storeSearchHistoryCheckbox = checkBox(selected : bind {model.storeSearchHistory},
|
||||
constraints : gbc(gridx : 1, gridy:0, anchor : GridBagConstraints.LINE_END))
|
||||
button(text : "Clear history", constraints : gbc(gridx : 1, gridy : 1, anchor : GridBagConstraints.LINE_END), clearHistoryAction)
|
||||
|
||||
}
|
||||
panel (border : titledBorder(title : "Other Settings", border : etchedBorder(), titlePosition : TitledBorder.TOP),
|
||||
constraints : gbc(gridx : 0, gridy : 2, fill : GridBagConstraints.HORIZONTAL, weightx : 100)) {
|
||||
gridBagLayout()
|
||||
label(text : "Automatically clear cancelled downloads", constraints: gbc(gridx: 0, gridy:0, anchor : GridBagConstraints.LINE_START, weightx: 100))
|
||||
clearCancelledDownloadsCheckbox = checkBox(selected : bind {model.clearCancelledDownloads},
|
||||
@@ -221,7 +231,7 @@ class OptionsView {
|
||||
radioButton(text : "Exit", selected : bind {model.exitOnClose}, buttonGroup : closeBehaviorGroup, exitOnCloseAction)
|
||||
}
|
||||
}
|
||||
panel (constraints : gbc(gridx: 0, gridy: 2, weighty: 100))
|
||||
panel (constraints : gbc(gridx: 0, gridy: 3, weighty: 100))
|
||||
}
|
||||
bandwidth = builder.panel {
|
||||
gridBagLayout()
|
||||
|
@@ -49,6 +49,7 @@ class SearchTabView {
|
||||
def sequentialDownloadCheckbox
|
||||
|
||||
void initUI() {
|
||||
int rowHeight = application.context.get("row-height")
|
||||
builder.with {
|
||||
def resultsTable
|
||||
def sendersTable
|
||||
@@ -59,7 +60,7 @@ class SearchTabView {
|
||||
panel {
|
||||
borderLayout()
|
||||
scrollPane (constraints : BorderLayout.CENTER) {
|
||||
sendersTable = table(id : "senders-table", autoCreateRowSorter : true) {
|
||||
sendersTable = table(id : "senders-table", autoCreateRowSorter : true, rowHeight : rowHeight) {
|
||||
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()})
|
||||
@@ -85,7 +86,7 @@ class SearchTabView {
|
||||
panel {
|
||||
borderLayout()
|
||||
scrollPane (constraints : BorderLayout.CENTER) {
|
||||
resultsTable = table(id : "results-table", autoCreateRowSorter : true) {
|
||||
resultsTable = table(id : "results-table", autoCreateRowSorter : true, rowHeight : rowHeight) {
|
||||
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})
|
||||
|
@@ -37,7 +37,7 @@ class ShowCommentView {
|
||||
borderLayout()
|
||||
panel (constraints : BorderLayout.CENTER) {
|
||||
scrollPane {
|
||||
textArea(text : model.result.comment, rows : 20, columns : 100, editable : false)
|
||||
textArea(text : model.result.comment, rows : 20, columns : 100, editable : false, lineWrap : true, wrapStyleWord : true)
|
||||
}
|
||||
}
|
||||
panel (constraints : BorderLayout.SOUTH) {
|
||||
|
83
gui/griffon-app/views/com/muwire/gui/SystemStatusView.groovy
Normal file
83
gui/griffon-app/views/com/muwire/gui/SystemStatusView.groovy
Normal file
@@ -0,0 +1,83 @@
|
||||
package com.muwire.gui
|
||||
|
||||
import griffon.core.artifact.GriffonView
|
||||
import griffon.inject.MVCMember
|
||||
import griffon.metadata.ArtifactProviderFor
|
||||
import net.i2p.data.DataHelper
|
||||
|
||||
import javax.swing.JDialog
|
||||
import javax.swing.JPanel
|
||||
import javax.swing.SwingConstants
|
||||
import javax.swing.border.TitledBorder
|
||||
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.GridBagConstraints
|
||||
import java.awt.event.WindowAdapter
|
||||
import java.awt.event.WindowEvent
|
||||
|
||||
import javax.annotation.Nonnull
|
||||
|
||||
@ArtifactProviderFor(GriffonView)
|
||||
class SystemStatusView {
|
||||
@MVCMember @Nonnull
|
||||
FactoryBuilderSupport builder
|
||||
@MVCMember @Nonnull
|
||||
SystemStatusModel model
|
||||
|
||||
def mainFrame
|
||||
def dialog
|
||||
def panel
|
||||
def buttonsPanel
|
||||
|
||||
void initUI() {
|
||||
mainFrame = application.windowManager.findWindow("main-frame")
|
||||
|
||||
dialog = new JDialog(mainFrame, "System Status", true)
|
||||
|
||||
panel = builder.panel {
|
||||
gridBagLayout()
|
||||
panel(border : titledBorder(title : "Java", border : etchedBorder(), titlePosition : TitledBorder.TOP),
|
||||
constraints : gbc(gridx : 0, gridy: 0, fill : GridBagConstraints.HORIZONTAL, weightx: 100)) {
|
||||
gridBagLayout()
|
||||
label(text : "Vendor ", constraints : gbc(gridx:0, gridy:0, anchor : GridBagConstraints.LINE_START, weightx : 100))
|
||||
label(text : bind {model.javaVendor}, constraints : gbc(gridx:1, gridy:0, anchor : GridBagConstraints.LINE_END))
|
||||
label(text : "Version", constraints : gbc(gridx:0, gridy:1, anchor : GridBagConstraints.LINE_START, weightx : 100))
|
||||
label(text : bind {model.javaVersion}, constraints : gbc(gridx:1, gridy:1, anchor : GridBagConstraints.LINE_END))
|
||||
}
|
||||
panel(border : titledBorder(title : "Memory", border : etchedBorder(), titlePosition : TitledBorder.TOP),
|
||||
constraints : gbc(gridx : 0, gridy: 1, fill : GridBagConstraints.HORIZONTAL, weightx: 100)) {
|
||||
gridBagLayout()
|
||||
label(text : "Used", constraints : gbc(gridx:0, gridy:0, anchor : GridBagConstraints.LINE_START, weightx : 100))
|
||||
label(text : bind {DataHelper.formatSize2Decimal(model.usedRam,false)+"B"}, constraints : gbc(gridx:1, gridy:0, anchor : GridBagConstraints.LINE_END))
|
||||
label(text : "Total", constraints : gbc(gridx:0, gridy:1, anchor : GridBagConstraints.LINE_START, weightx : 100))
|
||||
label(text : bind {DataHelper.formatSize2Decimal(model.totalRam,false)+"B"}, constraints : gbc(gridx:1, gridy:1, anchor : GridBagConstraints.LINE_END))
|
||||
label(text : "Max", constraints : gbc(gridx:0, gridy:2, anchor : GridBagConstraints.LINE_START, weightx : 100))
|
||||
label(text : bind {DataHelper.formatSize2Decimal(model.maxRam,false)+"B"}, constraints : gbc(gridx:1, gridy:2, anchor : GridBagConstraints.LINE_END))
|
||||
}
|
||||
buttonsPanel = builder.panel {
|
||||
gridBagLayout()
|
||||
button(text : "Refresh", constraints: gbc(gridx: 0, gridy: 0), refreshAction)
|
||||
button(text : "Close", constraints : gbc(gridx : 1, gridy :0), closeAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mvcGroupInit(Map<String,String> args) {
|
||||
JPanel statusPanel = new JPanel()
|
||||
statusPanel.setLayout(new BorderLayout())
|
||||
statusPanel.add(panel, BorderLayout.CENTER)
|
||||
statusPanel.add(buttonsPanel, BorderLayout.SOUTH)
|
||||
|
||||
dialog.getContentPane().add(statusPanel)
|
||||
dialog.pack()
|
||||
dialog.setResizable(false)
|
||||
dialog.setLocationRelativeTo(mainFrame)
|
||||
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
|
||||
dialog.addWindowListener(new WindowAdapter() {
|
||||
public void windowClosed(WindowEvent e) {
|
||||
mvcGroup.destroy()
|
||||
}
|
||||
})
|
||||
dialog.show()
|
||||
}
|
||||
}
|
@@ -29,6 +29,7 @@ class TrustListView {
|
||||
|
||||
void initUI() {
|
||||
mainFrame = application.windowManager.findWindow("main-frame")
|
||||
int rowHeight = application.context.get("row-height")
|
||||
dialog = new JDialog(mainFrame, model.trustList.persona.getHumanReadableName(), true)
|
||||
mainPanel = builder.panel {
|
||||
borderLayout()
|
||||
@@ -46,7 +47,7 @@ class TrustListView {
|
||||
panel {
|
||||
borderLayout()
|
||||
scrollPane (constraints : BorderLayout.CENTER){
|
||||
table(id : "trusted-table", autoCreateRowSorter : true) {
|
||||
table(id : "trusted-table", autoCreateRowSorter : true, rowHeight : rowHeight) {
|
||||
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()})
|
||||
@@ -62,7 +63,7 @@ class TrustListView {
|
||||
panel {
|
||||
borderLayout()
|
||||
scrollPane (constraints : BorderLayout.CENTER ){
|
||||
table(id : "distrusted-table", autoCreateRowSorter : true) {
|
||||
table(id : "distrusted-table", autoCreateRowSorter : true, rowHeight : rowHeight) {
|
||||
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()})
|
||||
|
61
gui/griffon-app/views/com/muwire/gui/UpdateView.groovy
Normal file
61
gui/griffon-app/views/com/muwire/gui/UpdateView.groovy
Normal file
@@ -0,0 +1,61 @@
|
||||
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 UpdateView {
|
||||
@MVCMember @Nonnull
|
||||
FactoryBuilderSupport builder
|
||||
@MVCMember @Nonnull
|
||||
UpdateModel model
|
||||
|
||||
def mainFrame
|
||||
def dialog
|
||||
def p
|
||||
|
||||
void initUI() {
|
||||
mainFrame = application.windowManager.findWindow("main-frame")
|
||||
String title = model.downloaded != null ? "Update Downloaded" : "Update Available"
|
||||
dialog = new JDialog(mainFrame, title, true)
|
||||
dialog.setResizable(true)
|
||||
|
||||
p = builder.panel {
|
||||
borderLayout()
|
||||
panel (constraints : BorderLayout.CENTER) {
|
||||
scrollPane {
|
||||
def text = model.downloaded != null ? model.downloaded.text : model.available.text
|
||||
textArea(text : text, rows : 20, columns : 50, editable : false, lineWrap : true, wrapStyleWord : true)
|
||||
}
|
||||
}
|
||||
panel (constraints : BorderLayout.SOUTH) {
|
||||
if (model.available != null)
|
||||
button(text : "Find", searchAction)
|
||||
button(text : "Close", closeAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mvcGroupInit(Map<String,String> args) {
|
||||
dialog.getContentPane().add(p)
|
||||
dialog.pack()
|
||||
dialog.setLocationRelativeTo(mainFrame)
|
||||
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
|
||||
dialog.addWindowListener( new WindowAdapter() {
|
||||
public void windowClosed(WindowEvent e) {
|
||||
mvcGroup.destroy()
|
||||
}
|
||||
})
|
||||
dialog.show()
|
||||
}
|
||||
}
|
14
gui/src/main/groovy/com/muwire/gui/SearchField.groovy
Normal file
14
gui/src/main/groovy/com/muwire/gui/SearchField.groovy
Normal file
@@ -0,0 +1,14 @@
|
||||
package com.muwire.gui
|
||||
|
||||
import java.awt.event.KeyEvent
|
||||
|
||||
import javax.swing.JComboBox
|
||||
|
||||
class SearchField extends JComboBox {
|
||||
SearchField(SearchFieldModel model) {
|
||||
super()
|
||||
setEditable(true)
|
||||
setModel(model)
|
||||
setEditor(new SearchFieldEditor(model, this))
|
||||
}
|
||||
}
|
47
gui/src/main/groovy/com/muwire/gui/SearchFieldEditor.groovy
Normal file
47
gui/src/main/groovy/com/muwire/gui/SearchFieldEditor.groovy
Normal file
@@ -0,0 +1,47 @@
|
||||
package com.muwire.gui
|
||||
|
||||
import javax.swing.JTextField
|
||||
import javax.swing.SwingUtilities
|
||||
import javax.swing.event.DocumentEvent
|
||||
import javax.swing.event.DocumentListener
|
||||
import javax.swing.plaf.basic.BasicComboBoxEditor
|
||||
|
||||
class SearchFieldEditor extends BasicComboBoxEditor {
|
||||
|
||||
private final SearchFieldModel model
|
||||
private final SearchField field
|
||||
|
||||
SearchFieldEditor(SearchFieldModel model, SearchField field) {
|
||||
super()
|
||||
this.model = model
|
||||
this.field = field
|
||||
def action = field.getAction()
|
||||
field.setAction(null)
|
||||
editor.setAction(action)
|
||||
editor.getDocument().addDocumentListener(new DocumentListener() {
|
||||
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
SwingUtilities.invokeLater({
|
||||
field.hidePopup()
|
||||
if (model.onKeyStroke(editor.text))
|
||||
field.showPopup()
|
||||
})
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
SwingUtilities.invokeLater({
|
||||
field.hidePopup()
|
||||
if (model.onKeyStroke(editor.text))
|
||||
field.showPopup()
|
||||
})
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
100
gui/src/main/groovy/com/muwire/gui/SearchFieldModel.groovy
Normal file
100
gui/src/main/groovy/com/muwire/gui/SearchFieldModel.groovy
Normal file
@@ -0,0 +1,100 @@
|
||||
package com.muwire.gui
|
||||
|
||||
import javax.swing.AbstractListModel
|
||||
import javax.swing.MutableComboBoxModel
|
||||
|
||||
class SearchFieldModel extends AbstractListModel implements MutableComboBoxModel {
|
||||
private final UISettings uiSettings
|
||||
private final File settingsFile
|
||||
private final List<String> objects = new ArrayList<>()
|
||||
private String selectedObject
|
||||
|
||||
SearchFieldModel(UISettings uiSettings, File home) {
|
||||
super()
|
||||
this.uiSettings = uiSettings
|
||||
this.settingsFile = new File(home, "gui.properties")
|
||||
uiSettings.searchHistory.each { objects.add(it) }
|
||||
fireIntervalAdded(this, 0, objects.size() - 1)
|
||||
}
|
||||
|
||||
public void addElement(Object string) {
|
||||
if (string == null)
|
||||
return
|
||||
if (uiSettings.storeSearchHistory) {
|
||||
if (!uiSettings.searchHistory.add(string))
|
||||
return
|
||||
settingsFile.withOutputStream { uiSettings.write(it) }
|
||||
}
|
||||
objects.add(string);
|
||||
fireIntervalAdded(this,objects.size()-1, objects.size()-1);
|
||||
if ( objects.size() == 1 && selectedObject == null && string != null ) {
|
||||
setSelectedItem( string );
|
||||
}
|
||||
}
|
||||
|
||||
boolean onKeyStroke(String selected) {
|
||||
selectedObject = selected
|
||||
if (selected == null || selected.length() == 0) {
|
||||
objects.clear()
|
||||
uiSettings.searchHistory.each { objects.add(it) }
|
||||
return true
|
||||
}
|
||||
|
||||
objects.clear()
|
||||
|
||||
Set<String> matching = new HashSet<>(uiSettings.searchHistory)
|
||||
matching.retainAll { it.contains(selected) }
|
||||
|
||||
matching.each {
|
||||
objects.add(it)
|
||||
}
|
||||
Collections.sort(objects)
|
||||
if (!objects.isEmpty()) {
|
||||
fireIntervalAdded(this, 0, objects.size() - 1)
|
||||
return true
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelectedItem(Object anObject) {
|
||||
if ((selectedObject != null && !selectedObject.equals( anObject )) ||
|
||||
selectedObject == null && anObject != null) {
|
||||
selectedObject = anObject;
|
||||
fireContentsChanged(this, -1, -1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getSelectedItem() {
|
||||
selectedObject
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
objects.size()
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getElementAt(int index) {
|
||||
if ( index >= 0 && index < objects.size() )
|
||||
return objects.get(index);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeElement(Object obj) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertElementAt(Object item, int index) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeElementAt(int index) {
|
||||
|
||||
}
|
||||
}
|
@@ -1,5 +1,7 @@
|
||||
package com.muwire.gui
|
||||
|
||||
import com.muwire.core.util.DataUtil
|
||||
|
||||
class UISettings {
|
||||
|
||||
String lnf
|
||||
@@ -14,6 +16,9 @@ class UISettings {
|
||||
boolean closeWarning
|
||||
boolean exitOnClose
|
||||
boolean clearUploads
|
||||
boolean storeSearchHistory
|
||||
Set<String> searchHistory
|
||||
Set<String> openTabs
|
||||
|
||||
UISettings(Properties props) {
|
||||
lnf = props.getProperty("lnf", "system")
|
||||
@@ -28,6 +33,10 @@ class UISettings {
|
||||
closeWarning = Boolean.parseBoolean(props.getProperty("closeWarning","true"))
|
||||
exitOnClose = Boolean.parseBoolean(props.getProperty("exitOnClose","false"))
|
||||
clearUploads = Boolean.parseBoolean(props.getProperty("clearUploads","false"))
|
||||
storeSearchHistory = Boolean.parseBoolean(props.getProperty("storeSearchHistory","true"))
|
||||
|
||||
searchHistory = DataUtil.readEncodedSet(props, "searchHistory")
|
||||
openTabs = DataUtil.readEncodedSet(props, "openTabs")
|
||||
}
|
||||
|
||||
void write(OutputStream out) throws IOException {
|
||||
@@ -43,9 +52,12 @@ class UISettings {
|
||||
props.setProperty("closeWarning", String.valueOf(closeWarning))
|
||||
props.setProperty("exitOnClose", String.valueOf(exitOnClose))
|
||||
props.setProperty("clearUploads", String.valueOf(clearUploads))
|
||||
props.setProperty("storeSearchHistory", String.valueOf(storeSearchHistory))
|
||||
if (font != null)
|
||||
props.setProperty("font", font)
|
||||
|
||||
DataUtil.writeEncodedSet(searchHistory, "searchHistory", props)
|
||||
DataUtil.writeEncodedSet(openTabs, "openTabs", props)
|
||||
|
||||
props.store(out, "UI Properties")
|
||||
}
|
||||
|
30
logging/0_logging.properties
Normal file
30
logging/0_logging.properties
Normal file
@@ -0,0 +1,30 @@
|
||||
############################################################
|
||||
# Default Logging Configuration File
|
||||
#
|
||||
# You can use a different file by specifying a filename
|
||||
# with the java.util.logging.config.file system property.
|
||||
# For example java -Djava.util.logging.config.file=myfile
|
||||
############################################################
|
||||
|
||||
############################################################
|
||||
# Global properties
|
||||
############################################################
|
||||
|
||||
# "handlers" specifies a comma separated list of log Handler
|
||||
# classes. These handlers will be installed during VM startup.
|
||||
# Note that these classes must be on the system classpath.
|
||||
# By default we only configure a ConsoleHandler, which will only
|
||||
# show messages at the INFO and above levels.
|
||||
handlers= java.util.logging.FileHandler
|
||||
|
||||
# To also add the FileHandler, use the following line instead.
|
||||
#handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler
|
||||
|
||||
# Default global logging level.
|
||||
# This specifies which kinds of events are logged across
|
||||
# all loggers. For any given facility this global level
|
||||
# can be overriden by a facility specific level
|
||||
# Note that the ConsoleHandler also has a separate level
|
||||
# setting to limit messages printed to the console.
|
||||
.level= OFF
|
||||
|
Reference in New Issue
Block a user