Compare commits
137 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 | ||
![]() |
3b882ae644 | ||
![]() |
5b61738ca9 | ||
![]() |
c77d79513e | ||
![]() |
9f12442897 | ||
![]() |
477b0a47ad | ||
![]() |
7f1041dd96 | ||
![]() |
99393c59bd | ||
![]() |
a78d8c84ca | ||
![]() |
fa9c697bfa | ||
![]() |
e5b12701f5 | ||
![]() |
f69727ab43 | ||
![]() |
d7c7afe2c0 | ||
![]() |
6c806c4441 | ||
![]() |
c4095abdb4 | ||
![]() |
8801546854 | ||
![]() |
f6ee49c0f5 | ||
![]() |
2320d650f6 | ||
![]() |
e9e6e6920a | ||
![]() |
87e5007f39 | ||
![]() |
8df6715e24 | ||
![]() |
6d587bf228 | ||
![]() |
8684452848 | ||
![]() |
7d652fabcb | ||
![]() |
5eb8d75bba | ||
![]() |
9ca8d1738c | ||
![]() |
2bb9480137 | ||
![]() |
7a6365f87a | ||
![]() |
56540ca3ca | ||
![]() |
eb5a5198b1 | ||
![]() |
29562c42ea | ||
![]() |
f5284f9483 | ||
![]() |
9bd3c4f141 | ||
![]() |
817dd68faf | ||
![]() |
5954cdb342 | ||
![]() |
56d44e6458 | ||
![]() |
c6fb76610d | ||
![]() |
5e329dfa2c | ||
![]() |
742f6da870 | ||
![]() |
7f46347c0f | ||
![]() |
b308ac2f37 | ||
![]() |
9cdabb51d1 | ||
![]() |
45f0736a5e | ||
![]() |
fe753ff978 | ||
![]() |
ac717b5205 | ||
![]() |
6f624e3afc | ||
![]() |
623d675ed9 | ||
![]() |
546b71b632 | ||
![]() |
804113bb1b | ||
![]() |
ab9e10f438 | ||
![]() |
00520acdf0 | ||
![]() |
8c44d196a7 | ||
![]() |
9c5fa0a2ce | ||
![]() |
d7bca05725 | ||
![]() |
45fcb2209e | ||
![]() |
7bf0373b80 | ||
![]() |
5925b42597 | ||
![]() |
13243b05ad | ||
![]() |
43987be463 | ||
![]() |
fcd3414e02 | ||
![]() |
70913ea8fb | ||
![]() |
b30e552498 | ||
![]() |
bae66de4eb | ||
![]() |
626e145e25 | ||
![]() |
bf72c76f13 | ||
![]() |
fce8bbfd97 | ||
![]() |
1cc7925155 | ||
![]() |
12b51ceb02 | ||
![]() |
62811861a4 | ||
![]() |
837aa6974b | ||
![]() |
94e7c42d19 | ||
![]() |
877bf12a93 | ||
![]() |
224266b2dd | ||
![]() |
8f16614dc3 | ||
![]() |
b412f9fb0c | ||
![]() |
b24d04811d | ||
![]() |
771f645df0 | ||
![]() |
b6483ad0f4 | ||
![]() |
decb72c8ef | ||
![]() |
439b3bf18b | ||
![]() |
06679ffee0 | ||
![]() |
1d5b12e2d7 | ||
![]() |
4e6e1b6f5b | ||
![]() |
f0b5361d7b | ||
![]() |
e0c6bfbf51 | ||
![]() |
2a0ecd8a47 | ||
![]() |
fb1804e849 | ||
![]() |
d4eaa0df8d | ||
![]() |
ffde6ac86f | ||
![]() |
7ad677ead2 | ||
![]() |
ddb0568aab | ||
![]() |
ff50a84a48 | ||
![]() |
770396ba41 | ||
![]() |
b55852e993 | ||
![]() |
a6945275a4 | ||
![]() |
7241809e55 |
10
README.md
10
README.md
@@ -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.
|
It is inspired by the LimeWire Gnutella client and developped by a former LimeWire developer.
|
||||||
|
|
||||||
The current stable release - 0.4.15 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
|
### Building
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ If you want to run the unit tests, type
|
|||||||
|
|
||||||
If you want to build binary bundles that do not depend on Java or I2P, see the https://github.com/zlatinb/muwire-pkg project
|
If you want to build binary bundles that do not depend on Java or I2P, see the https://github.com/zlatinb/muwire-pkg project
|
||||||
|
|
||||||
### Running
|
### Running the GUI
|
||||||
|
|
||||||
After you build the application, look inside `gui/build/distributions`. Untar/unzip one of the `shadow` files and then run the jar contained inside by typing `java -jar gui-x.y.z.jar` in a terminal or command prompt.
|
After you build the application, look inside `gui/build/distributions`. Untar/unzip one of the `shadow` files and then run the jar contained inside by typing `java -jar gui-x.y.z.jar` in a terminal or command prompt.
|
||||||
|
|
||||||
@@ -29,6 +29,12 @@ If you have an I2P router running on the same machine that is all you need to do
|
|||||||
|
|
||||||
[Default I2CP port]\: `7654`
|
[Default I2CP port]\: `7654`
|
||||||
|
|
||||||
|
### 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. 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.
|
||||||
|
|
||||||
### GPG Fingerprint
|
### GPG Fingerprint
|
||||||
|
|
||||||
```
|
```
|
||||||
|
27
cli-lanterna/build.gradle
Normal file
27
cli-lanterna/build.gradle
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
buildscript {
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
jcenter()
|
||||||
|
mavenLocal()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.4'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin : 'application'
|
||||||
|
application {
|
||||||
|
mainClassName = 'com.muwire.clilanterna.CliLanterna'
|
||||||
|
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties','-Xmx256M']
|
||||||
|
applicationName = 'MuWire-cli'
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin : 'com.github.johnrengelman.shadow'
|
||||||
|
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile project(":core")
|
||||||
|
compile 'com.googlecode.lanterna:lanterna:3.0.1'
|
||||||
|
}
|
||||||
|
|
@@ -0,0 +1,73 @@
|
|||||||
|
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
|
||||||
|
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
|
||||||
|
import com.muwire.core.util.DataUtil
|
||||||
|
|
||||||
|
import net.i2p.data.Base64
|
||||||
|
|
||||||
|
class AddCommentView extends BasicWindow {
|
||||||
|
private final TextGUI textGUI
|
||||||
|
private final Core core
|
||||||
|
private final TextBox textBox
|
||||||
|
private final LayoutData layoutData = GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER)
|
||||||
|
|
||||||
|
AddCommentView(TextGUI textGUI, Core core, SharedFile sharedFile, TerminalSize terminalSize) {
|
||||||
|
super("Add Comment To "+sharedFile.getFile().getName())
|
||||||
|
this.textGUI = textGUI
|
||||||
|
this.core = core
|
||||||
|
|
||||||
|
setHints([Window.Hint.CENTERED])
|
||||||
|
|
||||||
|
Panel contentPanel = new Panel()
|
||||||
|
contentPanel.setLayoutManager(new GridLayout(1))
|
||||||
|
|
||||||
|
String oldComment = sharedFile.getComment()
|
||||||
|
if (oldComment == null)
|
||||||
|
oldComment = ""
|
||||||
|
else
|
||||||
|
oldComment = DataUtil.readi18nString(Base64.decode(oldComment))
|
||||||
|
|
||||||
|
TerminalSize boxSize = new TerminalSize((terminalSize.getColumns() / 2).toInteger(), (terminalSize.getRows() / 2).toInteger())
|
||||||
|
textBox = new TextBox(boxSize,oldComment,TextBox.Style.MULTI_LINE)
|
||||||
|
contentPanel.addComponent(textBox, layoutData)
|
||||||
|
|
||||||
|
Panel buttonsPanel = new Panel()
|
||||||
|
buttonsPanel.setLayoutManager(new GridLayout(2))
|
||||||
|
contentPanel.addComponent(buttonsPanel, layoutData)
|
||||||
|
|
||||||
|
Button saveButton = new Button("Save", {
|
||||||
|
String newComment = textBox.getText()
|
||||||
|
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()})
|
||||||
|
|
||||||
|
buttonsPanel.addComponent(saveButton, layoutData)
|
||||||
|
buttonsPanel.addComponent(cancelButton, layoutData)
|
||||||
|
|
||||||
|
setComponent(contentPanel)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,75 @@
|
|||||||
|
package com.muwire.clilanterna
|
||||||
|
|
||||||
|
import com.googlecode.lanterna.gui2.Label
|
||||||
|
import com.googlecode.lanterna.gui2.TextGUIThread
|
||||||
|
import com.googlecode.lanterna.gui2.table.TableModel
|
||||||
|
import com.muwire.core.Core
|
||||||
|
import com.muwire.core.Persona
|
||||||
|
import com.muwire.core.search.BrowseStatus
|
||||||
|
import com.muwire.core.search.BrowseStatusEvent
|
||||||
|
import com.muwire.core.search.UIBrowseEvent
|
||||||
|
import com.muwire.core.search.UIResultEvent
|
||||||
|
|
||||||
|
import net.i2p.data.Base64
|
||||||
|
import net.i2p.data.DataHelper
|
||||||
|
|
||||||
|
class BrowseModel {
|
||||||
|
private final Persona persona
|
||||||
|
private final Core core
|
||||||
|
private final TextGUIThread guiThread
|
||||||
|
private final TableModel model = new TableModel("Name","Size","Hash","Comment")
|
||||||
|
private Map<String, UIResultEvent> rootToResult = new HashMap<>()
|
||||||
|
|
||||||
|
private int totalResults
|
||||||
|
|
||||||
|
private Label status
|
||||||
|
private Label percentage
|
||||||
|
|
||||||
|
BrowseModel(Persona persona, Core core, TextGUIThread guiThread) {
|
||||||
|
this.persona = persona
|
||||||
|
this.core = core
|
||||||
|
this.guiThread = guiThread
|
||||||
|
|
||||||
|
core.eventBus.register(BrowseStatusEvent.class, this)
|
||||||
|
core.eventBus.register(UIResultEvent.class, this)
|
||||||
|
core.eventBus.publish(new UIBrowseEvent(host : persona))
|
||||||
|
}
|
||||||
|
|
||||||
|
void unregister() {
|
||||||
|
core.eventBus.unregister(BrowseStatusEvent.class, this)
|
||||||
|
core.eventBus.unregister(UIResultEvent.class, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
void onBrowseStatusEvent(BrowseStatusEvent e) {
|
||||||
|
guiThread.invokeLater {
|
||||||
|
status.setText(e.status.toString())
|
||||||
|
if (e.status == BrowseStatus.FETCHING)
|
||||||
|
totalResults = e.totalResults
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onUIResultEvent(UIResultEvent e) {
|
||||||
|
guiThread.invokeLater {
|
||||||
|
String size = DataHelper.formatSize2Decimal(e.size, false) + "B"
|
||||||
|
String infoHash = Base64.encode(e.infohash.getRoot())
|
||||||
|
String comment = String.valueOf(e.comment != null)
|
||||||
|
model.addRow(e.name, size, infoHash, comment)
|
||||||
|
rootToResult.put(infoHash, e)
|
||||||
|
|
||||||
|
String percentageString = ""
|
||||||
|
if (totalResults != 0) {
|
||||||
|
double percentage = Math.round( (model.getRowCount() * 100 / totalResults).toDouble() )
|
||||||
|
percentageString = String.valueOf(percentage)+"%"
|
||||||
|
}
|
||||||
|
percentage.setText(percentageString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setStatusLabel(Label status) {
|
||||||
|
this.status = status
|
||||||
|
}
|
||||||
|
|
||||||
|
void setPercentageLabel(Label percentage) {
|
||||||
|
this.percentage = percentage
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,111 @@
|
|||||||
|
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.Label
|
||||||
|
import com.googlecode.lanterna.gui2.LayoutData
|
||||||
|
import com.googlecode.lanterna.gui2.Panel
|
||||||
|
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.gui2.table.Table
|
||||||
|
import com.muwire.core.Core
|
||||||
|
import com.muwire.core.download.UIDownloadEvent
|
||||||
|
import com.muwire.core.search.UIResultEvent
|
||||||
|
|
||||||
|
|
||||||
|
class BrowseView extends BasicWindow {
|
||||||
|
private final BrowseModel model
|
||||||
|
private final TextGUI textGUI
|
||||||
|
private final Core core
|
||||||
|
private final Table table
|
||||||
|
private final TerminalSize terminalSize
|
||||||
|
private final LayoutData layoutData = GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER, true, false)
|
||||||
|
|
||||||
|
BrowseView(BrowseModel model, TextGUI textGUI, Core core, TerminalSize terminalSize) {
|
||||||
|
super("Browse "+model.persona.getHumanReadableName())
|
||||||
|
this.model = model
|
||||||
|
this.textGUI = textGUI
|
||||||
|
this.core = core
|
||||||
|
this.terminalSize = terminalSize
|
||||||
|
|
||||||
|
setHints([Window.Hint.EXPANDED])
|
||||||
|
|
||||||
|
Panel contentPanel = new Panel()
|
||||||
|
contentPanel.setLayoutManager(new GridLayout(1))
|
||||||
|
|
||||||
|
Label statusLabel = new Label("")
|
||||||
|
Label percentageLabel = new Label("")
|
||||||
|
model.setStatusLabel(statusLabel)
|
||||||
|
model.setPercentageLabel(percentageLabel)
|
||||||
|
|
||||||
|
Panel topPanel = new Panel()
|
||||||
|
topPanel.setLayoutManager(new GridLayout(2))
|
||||||
|
topPanel.addComponent(statusLabel, layoutData)
|
||||||
|
topPanel.addComponent(percentageLabel, layoutData)
|
||||||
|
contentPanel.addComponent(topPanel, layoutData)
|
||||||
|
|
||||||
|
table = new Table("Name","Size","Hash","Comment")
|
||||||
|
table.with {
|
||||||
|
setCellSelection(false)
|
||||||
|
setTableModel(model.model)
|
||||||
|
setVisibleRows(terminalSize.getRows())
|
||||||
|
setSelectAction({rowSelected()})
|
||||||
|
}
|
||||||
|
contentPanel.addComponent(table, layoutData)
|
||||||
|
|
||||||
|
Button closeButton = new Button("Close",{
|
||||||
|
model.unregister()
|
||||||
|
close()
|
||||||
|
})
|
||||||
|
contentPanel.addComponent(closeButton, layoutData)
|
||||||
|
setComponent(contentPanel)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rowSelected() {
|
||||||
|
int selectedRow = table.getSelectedRow()
|
||||||
|
def row = model.model.getRow(selectedRow)
|
||||||
|
String infoHash = row[2]
|
||||||
|
boolean comment = Boolean.parseBoolean(row[3])
|
||||||
|
if (comment) {
|
||||||
|
Window prompt = new BasicWindow("Download Or View Comment")
|
||||||
|
prompt.setHints([Window.Hint.CENTERED])
|
||||||
|
Panel contentPanel = new Panel()
|
||||||
|
contentPanel.setLayoutManager(new GridLayout(3))
|
||||||
|
Button downloadButton = new Button("Download", {download(infoHash)})
|
||||||
|
Button viewButton = new Button("View Comment", {viewComment(infoHash)})
|
||||||
|
Button closeButton = new Button("Cancel", {prompt.close()})
|
||||||
|
|
||||||
|
contentPanel.with {
|
||||||
|
addComponent(downloadButton, layoutData)
|
||||||
|
addComponent(viewButton, layoutData)
|
||||||
|
addComponent(closeButton, layoutData)
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt.setComponent(contentPanel)
|
||||||
|
downloadButton.takeFocus()
|
||||||
|
textGUI.addWindowAndWait(prompt)
|
||||||
|
} else {
|
||||||
|
download(infoHash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void download(String infoHash) {
|
||||||
|
UIResultEvent result = model.rootToResult[infoHash]
|
||||||
|
def file = new File(core.muOptions.downloadLocation, result.name)
|
||||||
|
core.eventBus.publish(new UIDownloadEvent(result : [result], sources : result.sources,
|
||||||
|
target : file, sequential : false))
|
||||||
|
MessageDialog.showMessageDialog(textGUI, "Download started", "Started download of "+result.name, MessageDialogButton.OK)
|
||||||
|
}
|
||||||
|
|
||||||
|
private void viewComment(String infoHash) {
|
||||||
|
UIResultEvent result = model.rootToResult[infoHash]
|
||||||
|
ViewCommentView view = new ViewCommentView(result, terminalSize)
|
||||||
|
textGUI.addWindowAndWait(view)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,193 @@
|
|||||||
|
package com.muwire.clilanterna
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.util.concurrent.CountDownLatch
|
||||||
|
import java.util.logging.Level
|
||||||
|
import java.util.logging.LogManager
|
||||||
|
|
||||||
|
import com.googlecode.lanterna.gui2.BasicWindow
|
||||||
|
import com.googlecode.lanterna.gui2.Border
|
||||||
|
import com.googlecode.lanterna.gui2.BorderLayout
|
||||||
|
import com.googlecode.lanterna.gui2.Borders
|
||||||
|
import com.googlecode.lanterna.gui2.Button
|
||||||
|
import com.googlecode.lanterna.gui2.GridLayout
|
||||||
|
import com.googlecode.lanterna.gui2.Label
|
||||||
|
import com.googlecode.lanterna.gui2.MultiWindowTextGUI
|
||||||
|
import com.googlecode.lanterna.gui2.Panel
|
||||||
|
import com.googlecode.lanterna.gui2.SeparateTextGUIThread
|
||||||
|
import com.googlecode.lanterna.gui2.Window
|
||||||
|
import com.googlecode.lanterna.gui2.WindowBasedTextGUI
|
||||||
|
import com.googlecode.lanterna.gui2.GridLayout.Alignment
|
||||||
|
import com.googlecode.lanterna.gui2.dialogs.MessageDialog
|
||||||
|
import com.googlecode.lanterna.gui2.dialogs.MessageDialogButton
|
||||||
|
import com.googlecode.lanterna.gui2.dialogs.TextInputDialog
|
||||||
|
import com.googlecode.lanterna.gui2.dialogs.TextInputDialogBuilder
|
||||||
|
import com.googlecode.lanterna.gui2.dialogs.WaitingDialog
|
||||||
|
import com.googlecode.lanterna.screen.Screen
|
||||||
|
import com.googlecode.lanterna.terminal.DefaultTerminalFactory
|
||||||
|
import com.googlecode.lanterna.terminal.Terminal
|
||||||
|
import com.muwire.core.Core
|
||||||
|
import com.muwire.core.MuWireSettings
|
||||||
|
import com.muwire.core.UILoadedEvent
|
||||||
|
import com.muwire.core.files.AllFilesLoadedEvent
|
||||||
|
|
||||||
|
class CliLanterna {
|
||||||
|
private static final String MW_VERSION = "0.5.7"
|
||||||
|
|
||||||
|
private static volatile Core core
|
||||||
|
|
||||||
|
private static WindowBasedTextGUI textGUI
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
if (System.getProperty("java.util.logging.config.file") == null) {
|
||||||
|
def names = LogManager.getLogManager().getLoggerNames()
|
||||||
|
while(names.hasMoreElements()) {
|
||||||
|
def name = names.nextElement()
|
||||||
|
LogManager.getLogManager().getLogger(name).setLevel(Level.SEVERE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def home = System.getProperty("user.home") + File.separator + ".MuWire"
|
||||||
|
home = new File(home)
|
||||||
|
if (!home.exists())
|
||||||
|
home.mkdirs()
|
||||||
|
|
||||||
|
def propsFile = new File(home,"MuWire.properties")
|
||||||
|
|
||||||
|
|
||||||
|
DefaultTerminalFactory terminalFactory = new DefaultTerminalFactory()
|
||||||
|
Screen screen = terminalFactory.createScreen()
|
||||||
|
textGUI = new MultiWindowTextGUI( new SeparateTextGUIThread.Factory(), screen)
|
||||||
|
textGUI.getGUIThread().start()
|
||||||
|
screen.startScreen()
|
||||||
|
|
||||||
|
def props
|
||||||
|
if (!propsFile.exists()) {
|
||||||
|
String nickname = TextInputDialog.showDialog(textGUI, "Select a nickname", "", "")
|
||||||
|
String defaultDownloadLocation = System.getProperty("user.home")+File.separator+"Downloads"
|
||||||
|
String downloadLocation = TextInputDialog.showDialog(textGUI, "Select download location", "", defaultDownloadLocation)
|
||||||
|
String defaultIncompletesLocation = System.getProperty("user.home")+File.separator+".MuWire"+File.separator+"incompletes"
|
||||||
|
String incompletesLocation = TextInputDialog.showDialog(textGUI, "Select incompletes location", "", defaultIncompletesLocation)
|
||||||
|
|
||||||
|
|
||||||
|
File downloadLocationFile = new File(downloadLocation)
|
||||||
|
if (!downloadLocationFile.exists())
|
||||||
|
downloadLocationFile.mkdirs()
|
||||||
|
File incompletesLocationFile = new File(incompletesLocation)
|
||||||
|
if (!incompletesLocationFile.exists())
|
||||||
|
incompletesLocationFile.mkdirs()
|
||||||
|
|
||||||
|
props = new MuWireSettings()
|
||||||
|
props.setNickname(nickname)
|
||||||
|
props.setDownloadLocation(downloadLocationFile)
|
||||||
|
props.incompleteLocation = incompletesLocationFile
|
||||||
|
|
||||||
|
propsFile.withPrintWriter("UTF-8", {
|
||||||
|
props.write(it)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
props = new Properties()
|
||||||
|
propsFile.withReader("UTF-8", {
|
||||||
|
props.load(it)
|
||||||
|
})
|
||||||
|
props = new MuWireSettings(props)
|
||||||
|
}
|
||||||
|
props.updateType = "cli-lanterna"
|
||||||
|
|
||||||
|
def i2pPropsFile = new File(home, "i2p.properties")
|
||||||
|
if (!i2pPropsFile.exists()) {
|
||||||
|
String i2pHost = TextInputDialog.showDialog(textGUI, "I2P router host", "Specifiy the host I2P router is on", "127.0.0.1")
|
||||||
|
int i2pPort = TextInputDialog.showNumberDialog(textGUI, "I2CP port", "Specify the I2CP port", "7654").toInteger()
|
||||||
|
|
||||||
|
Properties i2pProps = new Properties()
|
||||||
|
i2pProps["i2cp.tcp.host"] = i2pHost
|
||||||
|
i2pProps["i2cp.tcp.port"] = String.valueOf(i2pPort)
|
||||||
|
i2pPropsFile.withOutputStream { i2pProps.store(it, "") }
|
||||||
|
}
|
||||||
|
|
||||||
|
def cliProps
|
||||||
|
def cliPropsFile = new File(home, "cli.properties")
|
||||||
|
if (cliPropsFile.exists()) {
|
||||||
|
Properties p = new Properties()
|
||||||
|
cliPropsFile.withInputStream {
|
||||||
|
p.load(it)
|
||||||
|
}
|
||||||
|
cliProps = new CliSettings(p)
|
||||||
|
} else
|
||||||
|
cliProps = new CliSettings(new Properties())
|
||||||
|
|
||||||
|
|
||||||
|
Window window = new BasicWindow("MuWire "+ MW_VERSION)
|
||||||
|
window.setHints([Window.Hint.CENTERED])
|
||||||
|
Panel contentPanel = new Panel()
|
||||||
|
contentPanel.withBorder(Borders.doubleLine())
|
||||||
|
BorderLayout layout = new BorderLayout()
|
||||||
|
contentPanel.setLayoutManager(layout)
|
||||||
|
|
||||||
|
Panel welcomeNamePanel = new Panel()
|
||||||
|
contentPanel.addComponent(welcomeNamePanel, BorderLayout.Location.CENTER)
|
||||||
|
welcomeNamePanel.setLayoutManager(new GridLayout(1))
|
||||||
|
Label welcomeLabel = new Label("Welcome to MuWire "+ props.nickname)
|
||||||
|
welcomeNamePanel.addComponent(welcomeLabel, GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER))
|
||||||
|
|
||||||
|
|
||||||
|
Panel connectButtonPanel = new Panel()
|
||||||
|
contentPanel.addComponent(connectButtonPanel, BorderLayout.Location.BOTTOM)
|
||||||
|
connectButtonPanel.setLayoutManager(new GridLayout(1))
|
||||||
|
Button connectButton = new Button("Connect", {
|
||||||
|
|
||||||
|
WaitingDialog waiting = new WaitingDialog("Connecting", "Please wait")
|
||||||
|
waiting.showDialog(textGUI, false)
|
||||||
|
|
||||||
|
CountDownLatch latch = new CountDownLatch(1)
|
||||||
|
Thread connector = new Thread({
|
||||||
|
try {
|
||||||
|
core = new Core(props, home, MW_VERSION)
|
||||||
|
} finally {
|
||||||
|
latch.countDown()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
connector.start()
|
||||||
|
while(latch.getCount() > 0) {
|
||||||
|
textGUI.updateScreen()
|
||||||
|
Thread.sleep(10)
|
||||||
|
}
|
||||||
|
waiting.close()
|
||||||
|
window.close()
|
||||||
|
} as Runnable)
|
||||||
|
welcomeNamePanel.addComponent(connectButton, GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER))
|
||||||
|
|
||||||
|
|
||||||
|
window.setComponent(contentPanel)
|
||||||
|
textGUI.addWindowAndWait(window)
|
||||||
|
|
||||||
|
if (core == null) {
|
||||||
|
MessageDialog.showMessageDialog(textGUI, "Failed", "MuWire failed to load", MessageDialogButton.Close)
|
||||||
|
System.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
window = new MainWindowView("MuWire "+MW_VERSION, core, textGUI, screen, cliProps)
|
||||||
|
core.startServices()
|
||||||
|
|
||||||
|
core.eventBus.publish(new UILoadedEvent())
|
||||||
|
textGUI.addWindowAndWait(window)
|
||||||
|
|
||||||
|
CountDownLatch latch = new CountDownLatch(1)
|
||||||
|
Thread stopper = new Thread({
|
||||||
|
core.shutdown()
|
||||||
|
latch.countDown()
|
||||||
|
} as Runnable)
|
||||||
|
WaitingDialog waitingForShutdown = new WaitingDialog("MuWire is shutting down","Please wait")
|
||||||
|
waitingForShutdown.setHints([Window.Hint.CENTERED])
|
||||||
|
waitingForShutdown.showDialog(textGUI, false)
|
||||||
|
stopper.start()
|
||||||
|
while(latch.getCount() > 0) {
|
||||||
|
textGUI.updateScreen()
|
||||||
|
Thread.sleep(10)
|
||||||
|
}
|
||||||
|
waitingForShutdown.close()
|
||||||
|
|
||||||
|
screen.stopScreen()
|
||||||
|
System.exit(0)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,25 @@
|
|||||||
|
package com.muwire.clilanterna
|
||||||
|
|
||||||
|
class CliSettings {
|
||||||
|
|
||||||
|
boolean clearCancelledDownloads
|
||||||
|
boolean clearFinishedDownloads
|
||||||
|
boolean clearUploads
|
||||||
|
|
||||||
|
CliSettings(Properties props) {
|
||||||
|
clearCancelledDownloads = Boolean.parseBoolean(props.getProperty("clearCancelledDownloads","true"))
|
||||||
|
clearFinishedDownloads = Boolean.parseBoolean(props.getProperty("clearFinishedDownloads", "false"))
|
||||||
|
clearUploads = Boolean.parseBoolean(props.getProperty("clearUploads", "false"))
|
||||||
|
}
|
||||||
|
|
||||||
|
void write(OutputStream os) {
|
||||||
|
Properties props = new Properties()
|
||||||
|
props.with {
|
||||||
|
setProperty("clearCancelledDownloads", String.valueOf(clearCancelledDownloads))
|
||||||
|
setProperty("clearFinishedDownloads", String.valueOf(clearFinishedDownloads))
|
||||||
|
setProperty("clearUploads", String.valueOf(clearUploads))
|
||||||
|
|
||||||
|
store(os, "CLI Properties")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,67 @@
|
|||||||
|
package com.muwire.clilanterna
|
||||||
|
|
||||||
|
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.Label
|
||||||
|
import com.googlecode.lanterna.gui2.LayoutData
|
||||||
|
import com.googlecode.lanterna.gui2.Panel
|
||||||
|
import com.googlecode.lanterna.gui2.TextGUIThread
|
||||||
|
import com.googlecode.lanterna.gui2.Window
|
||||||
|
import com.muwire.core.download.Downloader
|
||||||
|
|
||||||
|
|
||||||
|
class DownloadDetailsView extends BasicWindow {
|
||||||
|
private final Downloader downloader
|
||||||
|
private final LayoutData layoutData = GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER)
|
||||||
|
|
||||||
|
private Label knownSources, activeSources, donePieces
|
||||||
|
DownloadDetailsView(Downloader downloader) {
|
||||||
|
super("Download details for "+downloader.file.getName())
|
||||||
|
this.downloader = downloader
|
||||||
|
|
||||||
|
setHints([Window.Hint.CENTERED])
|
||||||
|
|
||||||
|
Panel contentPanel = new Panel()
|
||||||
|
contentPanel.setLayoutManager(new GridLayout(2))
|
||||||
|
|
||||||
|
knownSources = new Label("0")
|
||||||
|
activeSources = new Label("0")
|
||||||
|
donePieces = new Label("0")
|
||||||
|
refresh()
|
||||||
|
|
||||||
|
Button refreshButton = new Button("Refresh",{refresh()})
|
||||||
|
Button closeButton = new Button("Close", {close()})
|
||||||
|
|
||||||
|
contentPanel.with {
|
||||||
|
addComponent(new Label("Target Location"), layoutData)
|
||||||
|
addComponent(new Label(downloader.file.getAbsolutePath()), layoutData)
|
||||||
|
addComponent(new Label("Piece Size"), layoutData)
|
||||||
|
addComponent(new Label(String.valueOf(downloader.pieceSize)), layoutData)
|
||||||
|
addComponent(new Label("Total Pieces"), layoutData)
|
||||||
|
addComponent(new Label(String.valueOf(downloader.nPieces)), layoutData)
|
||||||
|
addComponent(new Label("Done Pieces"), layoutData)
|
||||||
|
addComponent(donePieces, layoutData)
|
||||||
|
addComponent(new Label("Known Sources"), layoutData)
|
||||||
|
addComponent(knownSources, layoutData)
|
||||||
|
addComponent(new Label("Active Sources"), layoutData)
|
||||||
|
addComponent(activeSources, layoutData)
|
||||||
|
addComponent(refreshButton, layoutData)
|
||||||
|
addComponent(closeButton, layoutData)
|
||||||
|
}
|
||||||
|
|
||||||
|
setComponent(contentPanel)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refresh() {
|
||||||
|
int done = downloader.donePieces()
|
||||||
|
int known = downloader.activeWorkers.size()
|
||||||
|
int active = downloader.activeWorkers()
|
||||||
|
|
||||||
|
knownSources.setText(String.valueOf(known))
|
||||||
|
activeSources.setText(String.valueOf(active))
|
||||||
|
donePieces.setText(String.valueOf(done))
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,15 @@
|
|||||||
|
package com.muwire.clilanterna
|
||||||
|
|
||||||
|
import com.muwire.core.download.Downloader
|
||||||
|
|
||||||
|
class DownloaderWrapper {
|
||||||
|
final Downloader downloader
|
||||||
|
DownloaderWrapper(Downloader downloader) {
|
||||||
|
this.downloader = downloader
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
downloader.file.getName()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,100 @@
|
|||||||
|
package com.muwire.clilanterna
|
||||||
|
|
||||||
|
import com.googlecode.lanterna.gui2.TextGUIThread
|
||||||
|
import com.googlecode.lanterna.gui2.table.TableModel
|
||||||
|
|
||||||
|
import com.muwire.core.Core
|
||||||
|
import com.muwire.core.download.DownloadStartedEvent
|
||||||
|
import com.muwire.core.download.Downloader
|
||||||
|
import com.muwire.core.files.FileDownloadedEvent
|
||||||
|
|
||||||
|
import net.i2p.data.DataHelper
|
||||||
|
|
||||||
|
class DownloadsModel {
|
||||||
|
private final TextGUIThread guiThread
|
||||||
|
private final Core core
|
||||||
|
private final CliSettings props
|
||||||
|
private final List<Downloader> downloaders = new ArrayList<>()
|
||||||
|
private final TableModel model = new TableModel("Name", "Status", "Progress", "Speed", "ETA")
|
||||||
|
|
||||||
|
|
||||||
|
private long lastRetryTime
|
||||||
|
|
||||||
|
DownloadsModel(TextGUIThread guiThread, Core core, CliSettings props) {
|
||||||
|
this.guiThread = guiThread
|
||||||
|
this.core = core
|
||||||
|
this.props = props
|
||||||
|
|
||||||
|
core.eventBus.register(DownloadStartedEvent.class, this)
|
||||||
|
Timer timer = new Timer(true)
|
||||||
|
Runnable guiRunnable = {
|
||||||
|
refreshModel()
|
||||||
|
resumeDownloads()
|
||||||
|
}
|
||||||
|
timer.schedule({
|
||||||
|
if (core.shutdown.get())
|
||||||
|
return
|
||||||
|
guiThread.invokeLater(guiRunnable)
|
||||||
|
} as TimerTask, 1000,1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
void onDownloadStartedEvent(DownloadStartedEvent e) {
|
||||||
|
guiThread.invokeLater({
|
||||||
|
downloaders.add(e.downloader)
|
||||||
|
refreshModel()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshModel() {
|
||||||
|
int rowCount = model.getRowCount()
|
||||||
|
rowCount.times { model.removeRow(0) }
|
||||||
|
|
||||||
|
if (props.clearCancelledDownloads) {
|
||||||
|
downloaders.removeAll { it.cancelled }
|
||||||
|
}
|
||||||
|
if (props.clearFinishedDownloads) {
|
||||||
|
downloaders.removeAll { it.getCurrentState() == Downloader.DownloadState.FINISHED }
|
||||||
|
}
|
||||||
|
|
||||||
|
downloaders.each {
|
||||||
|
String status = it.getCurrentState().toString()
|
||||||
|
int speedInt = it.speed()
|
||||||
|
String speed = DataHelper.formatSize2Decimal(speedInt, false) + "B/sec"
|
||||||
|
|
||||||
|
int pieces = it.nPieces
|
||||||
|
int done = it.donePieces()
|
||||||
|
int percent = -1
|
||||||
|
if (pieces != 0)
|
||||||
|
percent = (done * 100 / pieces)
|
||||||
|
String totalSize = DataHelper.formatSize2Decimal(it.length, false) + "B"
|
||||||
|
String progress = (String.format("%2d", percent) + "% of ${totalSize}".toString())
|
||||||
|
|
||||||
|
String ETA
|
||||||
|
if (speedInt == 0)
|
||||||
|
ETA = "Unknown"
|
||||||
|
else {
|
||||||
|
long remaining = (pieces - done) * it.pieceSize / speedInt
|
||||||
|
ETA = DataHelper.formatDuration(remaining * 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
model.addRow([new DownloaderWrapper(it), status, progress, speed, ETA])
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resumeDownloads() {
|
||||||
|
int retryInterval = core.muOptions.downloadRetryInterval
|
||||||
|
if (retryInterval == 0)
|
||||||
|
return
|
||||||
|
retryInterval *= 1000
|
||||||
|
long now = System.currentTimeMillis()
|
||||||
|
if (now - lastRetryTime > retryInterval) {
|
||||||
|
lastRetryTime = now
|
||||||
|
downloaders.each {
|
||||||
|
def state = it.getCurrentState()
|
||||||
|
if (state == Downloader.DownloadState.FAILED || state == Downloader.DownloadState.DOWNLOADING)
|
||||||
|
it.resume()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,94 @@
|
|||||||
|
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.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.gui2.table.Table
|
||||||
|
import com.muwire.core.Core
|
||||||
|
import com.muwire.core.download.Downloader
|
||||||
|
import com.muwire.core.download.UIDownloadCancelledEvent
|
||||||
|
|
||||||
|
class DownloadsView extends BasicWindow {
|
||||||
|
private final Core core
|
||||||
|
private final DownloadsModel model
|
||||||
|
private final TextGUI textGUI
|
||||||
|
private final Table table
|
||||||
|
|
||||||
|
DownloadsView(Core core, DownloadsModel model, TextGUI textGUI, TerminalSize terminalSize) {
|
||||||
|
this.core = core
|
||||||
|
this.model = model
|
||||||
|
this.textGUI = textGUI
|
||||||
|
|
||||||
|
setHints([Window.Hint.EXPANDED])
|
||||||
|
LayoutData layoutData = GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER, true, false)
|
||||||
|
|
||||||
|
Panel contentPanel = new Panel()
|
||||||
|
contentPanel.setLayoutManager(new GridLayout(1))
|
||||||
|
table = new Table("Name","Status","Progress","Speed","ETA")
|
||||||
|
table.setCellSelection(false)
|
||||||
|
table.setSelectAction({rowSelected()})
|
||||||
|
table.setTableModel(model.model)
|
||||||
|
table.setVisibleRows(terminalSize.getRows())
|
||||||
|
contentPanel.addComponent(table, layoutData)
|
||||||
|
|
||||||
|
Panel buttonsPanel = new Panel()
|
||||||
|
buttonsPanel.setLayoutManager(new GridLayout(2))
|
||||||
|
|
||||||
|
Button clearButton = new Button("Clear Done",{clearDone()})
|
||||||
|
buttonsPanel.addComponent(clearButton, layoutData)
|
||||||
|
|
||||||
|
Button closeButton = new Button("Close",{close()})
|
||||||
|
buttonsPanel.addComponent(closeButton, layoutData)
|
||||||
|
|
||||||
|
contentPanel.addComponent(buttonsPanel, layoutData)
|
||||||
|
|
||||||
|
setComponent(contentPanel)
|
||||||
|
closeButton.takeFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rowSelected() {
|
||||||
|
int selectedRow = table.getSelectedRow()
|
||||||
|
def row = model.model.getRow(selectedRow)
|
||||||
|
Downloader downloader = row[0].downloader
|
||||||
|
|
||||||
|
Window prompt = new BasicWindow("Kill Download?")
|
||||||
|
prompt.setHints([Window.Hint.CENTERED])
|
||||||
|
Panel contentPanel = new Panel()
|
||||||
|
contentPanel.setLayoutManager(new GridLayout(3))
|
||||||
|
LayoutData layoutData = GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER)
|
||||||
|
|
||||||
|
Button killDownload = new Button("Kill Download", {
|
||||||
|
downloader.cancel()
|
||||||
|
core.eventBus.publish(new UIDownloadCancelledEvent(downloader : downloader))
|
||||||
|
MessageDialog.showMessageDialog(textGUI, "Download Killed", downloader.file.getName()+ " has been killed", MessageDialogButton.OK)
|
||||||
|
})
|
||||||
|
Button viewDetails = new Button("View Details", {
|
||||||
|
textGUI.addWindowAndWait(new DownloadDetailsView(downloader))
|
||||||
|
})
|
||||||
|
Button close = new Button("Close", {
|
||||||
|
prompt.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
contentPanel.addComponent(killDownload,layoutData)
|
||||||
|
contentPanel.addComponent(viewDetails, layoutData)
|
||||||
|
contentPanel.addComponent(close, layoutData)
|
||||||
|
prompt.setComponent(contentPanel)
|
||||||
|
close.takeFocus()
|
||||||
|
textGUI.addWindowAndWait(prompt)
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearDone() {
|
||||||
|
model.downloaders.removeAll {
|
||||||
|
def state = it.getCurrentState()
|
||||||
|
state == Downloader.DownloadState.CANCELLED || state == Downloader.DownloadState.FINISHED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,80 @@
|
|||||||
|
package com.muwire.clilanterna
|
||||||
|
|
||||||
|
import com.googlecode.lanterna.gui2.TextGUIThread
|
||||||
|
import com.googlecode.lanterna.gui2.table.TableModel
|
||||||
|
import com.muwire.core.Core
|
||||||
|
import com.muwire.core.SharedFile
|
||||||
|
import com.muwire.core.files.AllFilesLoadedEvent
|
||||||
|
import com.muwire.core.files.DirectoryWatchedEvent
|
||||||
|
import com.muwire.core.files.FileHashedEvent
|
||||||
|
import com.muwire.core.files.FileLoadedEvent
|
||||||
|
import com.muwire.core.files.FileSharedEvent
|
||||||
|
import com.muwire.core.files.FileUnsharedEvent
|
||||||
|
import com.muwire.core.trust.TrustSubscriptionEvent
|
||||||
|
|
||||||
|
import net.i2p.data.DataHelper
|
||||||
|
|
||||||
|
class FilesModel {
|
||||||
|
private final TextGUIThread guiThread
|
||||||
|
private final Core core
|
||||||
|
private final List<SharedFile> sharedFiles = new ArrayList<>()
|
||||||
|
private final TableModel model = new TableModel("Name","Size","Comment","Search Hits","Downloaders")
|
||||||
|
|
||||||
|
FilesModel(TextGUIThread guiThread, Core core) {
|
||||||
|
this.guiThread = guiThread
|
||||||
|
this.core = core
|
||||||
|
|
||||||
|
core.eventBus.register(FileLoadedEvent.class, this)
|
||||||
|
core.eventBus.register(FileUnsharedEvent.class, this)
|
||||||
|
core.eventBus.register(FileHashedEvent.class, this)
|
||||||
|
core.eventBus.register(AllFilesLoadedEvent.class, this)
|
||||||
|
|
||||||
|
Runnable refreshModel = {refreshModel()}
|
||||||
|
Timer timer = new Timer(true)
|
||||||
|
timer.schedule({
|
||||||
|
guiThread.invokeLater(refreshModel)
|
||||||
|
} as TimerTask, 1000,1000)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void onAllFilesLoadedEvent(AllFilesLoadedEvent e) {
|
||||||
|
def eventBus = core.eventBus
|
||||||
|
guiThread.invokeLater {
|
||||||
|
core.muOptions.watchedDirectories.each {
|
||||||
|
eventBus.publish(new DirectoryWatchedEvent(directory : new File(it)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onFileLoadedEvent(FileLoadedEvent e) {
|
||||||
|
guiThread.invokeLater {
|
||||||
|
sharedFiles.add(e.loadedFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onFileHashedEvent(FileHashedEvent e) {
|
||||||
|
guiThread.invokeLater {
|
||||||
|
if (e.sharedFile != null)
|
||||||
|
sharedFiles.add(e.sharedFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onFileUnsharedEvent(FileUnsharedEvent e) {
|
||||||
|
guiThread.invokeLater {
|
||||||
|
sharedFiles.remove(e.unsharedFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshModel() {
|
||||||
|
int rowCount = model.getRowCount()
|
||||||
|
rowCount.times { model.removeRow(0) }
|
||||||
|
|
||||||
|
sharedFiles.each {
|
||||||
|
long size = it.getCachedLength()
|
||||||
|
boolean comment = it.comment != null
|
||||||
|
String hits = String.valueOf(it.getHits())
|
||||||
|
String downloaders = String.valueOf(it.getDownloaders().size())
|
||||||
|
model.addRow(new SharedFileWrapper(it), DataHelper.formatSize2(size, false)+"B", comment, hits, downloaders)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,131 @@
|
|||||||
|
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.TextGUI
|
||||||
|
import com.googlecode.lanterna.gui2.Window
|
||||||
|
import com.googlecode.lanterna.gui2.dialogs.FileDialog
|
||||||
|
import com.googlecode.lanterna.gui2.dialogs.MessageDialog
|
||||||
|
import com.googlecode.lanterna.gui2.dialogs.MessageDialogButton
|
||||||
|
import com.googlecode.lanterna.gui2.dialogs.TextInputDialog
|
||||||
|
import com.googlecode.lanterna.gui2.table.Table
|
||||||
|
import com.muwire.core.Core
|
||||||
|
import com.muwire.core.SharedFile
|
||||||
|
import com.muwire.core.files.DirectoryUnsharedEvent
|
||||||
|
import com.muwire.core.files.FileSharedEvent
|
||||||
|
import com.muwire.core.files.FileUnsharedEvent
|
||||||
|
import com.muwire.core.files.UIPersistFilesEvent
|
||||||
|
|
||||||
|
class FilesView extends BasicWindow {
|
||||||
|
private final FilesModel model
|
||||||
|
private final TextGUI textGUI
|
||||||
|
private final Core core
|
||||||
|
private final Table table
|
||||||
|
private final TerminalSize terminalSize
|
||||||
|
|
||||||
|
FilesView(FilesModel model, TextGUI textGUI, Core core, TerminalSize terminalSize) {
|
||||||
|
super("Shared Files")
|
||||||
|
this.model = model
|
||||||
|
this.core = core
|
||||||
|
this.textGUI = textGUI
|
||||||
|
this.terminalSize = terminalSize
|
||||||
|
|
||||||
|
setHints([Window.Hint.EXPANDED])
|
||||||
|
|
||||||
|
Panel contentPanel = new Panel()
|
||||||
|
contentPanel.setLayoutManager(new GridLayout(1))
|
||||||
|
LayoutData layoutData = GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER, true, false)
|
||||||
|
|
||||||
|
table = new Table("Name","Size","Comment","Search Hits","Downloaders")
|
||||||
|
table.setCellSelection(false)
|
||||||
|
table.setTableModel(model.model)
|
||||||
|
table.setSelectAction({rowSelected()})
|
||||||
|
table.setVisibleRows(terminalSize.getRows())
|
||||||
|
contentPanel.addComponent(table, layoutData)
|
||||||
|
|
||||||
|
Panel buttonsPanel = new Panel()
|
||||||
|
buttonsPanel.setLayoutManager(new GridLayout(4))
|
||||||
|
|
||||||
|
Button shareFile = new Button("Share File", {shareFile()})
|
||||||
|
Button shareDirectory = new Button("Share Directory", {shareDirectory()})
|
||||||
|
Button unshareDirectory = new Button("Unshare Directory",{unshareDirectory()})
|
||||||
|
Button close = new Button("Close", {close()})
|
||||||
|
|
||||||
|
buttonsPanel.with {
|
||||||
|
addComponent(shareFile, layoutData)
|
||||||
|
addComponent(shareDirectory, layoutData)
|
||||||
|
addComponent(unshareDirectory, layoutData)
|
||||||
|
addComponent(close, layoutData)
|
||||||
|
}
|
||||||
|
|
||||||
|
contentPanel.addComponent(buttonsPanel, layoutData)
|
||||||
|
setComponent(contentPanel)
|
||||||
|
close.takeFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rowSelected() {
|
||||||
|
int selectedRow = table.getSelectedRow()
|
||||||
|
def row = model.model.getRow(selectedRow)
|
||||||
|
SharedFile sf = row[0].sharedFile
|
||||||
|
|
||||||
|
Window prompt = new BasicWindow("Unshare or add comment to "+sf.getFile().getName()+" ?")
|
||||||
|
prompt.setHints([Window.Hint.CENTERED])
|
||||||
|
Panel contentPanel = new Panel()
|
||||||
|
contentPanel.setLayoutManager(new GridLayout(3))
|
||||||
|
|
||||||
|
Button unshareButton = new Button("Unshare", {
|
||||||
|
core.eventBus.publish(new FileUnsharedEvent(unsharedFile : sf))
|
||||||
|
core.eventBus.publish(new UIPersistFilesEvent())
|
||||||
|
MessageDialog.showMessageDialog(textGUI, "File Unshared", "Unshared "+sf.getFile().getName(), MessageDialogButton.OK)
|
||||||
|
} )
|
||||||
|
Button addCommentButton = new Button("Add Comment", {
|
||||||
|
AddCommentView view = new AddCommentView(textGUI, core, sf, terminalSize)
|
||||||
|
textGUI.addWindowAndWait(view)
|
||||||
|
})
|
||||||
|
Button closeButton = new Button("Close", {prompt.close()})
|
||||||
|
|
||||||
|
LayoutData layoutData = GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER)
|
||||||
|
contentPanel.addComponent(unshareButton, layoutData)
|
||||||
|
contentPanel.addComponent(addCommentButton, layoutData)
|
||||||
|
contentPanel.addComponent(closeButton, layoutData)
|
||||||
|
|
||||||
|
prompt.setComponent(contentPanel)
|
||||||
|
textGUI.addWindowAndWait(prompt)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void shareFile() {
|
||||||
|
TerminalSize terminalSize = new TerminalSize(terminalSize.getColumns() - 10, terminalSize.getRows() - 10)
|
||||||
|
FileDialog fileDialog = new FileDialog("Share File", "Select a file to share", "Share", terminalSize, false, null)
|
||||||
|
File f = fileDialog.showDialog(textGUI)
|
||||||
|
f = f.getCanonicalFile()
|
||||||
|
core.eventBus.publish(new FileSharedEvent(file : f))
|
||||||
|
MessageDialog.showMessageDialog(textGUI, "File Shared", f.getName()+" has been shared", MessageDialogButton.OK)
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shareDirectory() {
|
||||||
|
String directoryName = TextInputDialog.showDialog(textGUI, "Share a directory", "Enter the directory to share", "")
|
||||||
|
if (directoryName == null)
|
||||||
|
return
|
||||||
|
File directory = new File(directoryName)
|
||||||
|
directory = directory.getCanonicalFile()
|
||||||
|
core.eventBus.publish(new FileSharedEvent(file : directory))
|
||||||
|
MessageDialog.showMessageDialog(textGUI, "Directory Shared", directory.getName()+" has been shared", MessageDialogButton.OK)
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unshareDirectory() {
|
||||||
|
String directoryName = TextInputDialog.showDialog(textGUI, "Unshare a directory", "Enter the directory to unshare", "")
|
||||||
|
if (directoryName == null)
|
||||||
|
return
|
||||||
|
File directory = new File(directoryName)
|
||||||
|
directory = directory.getCanonicalFile()
|
||||||
|
core.eventBus.publish(new DirectoryUnsharedEvent(directory : directory))
|
||||||
|
MessageDialog.showMessageDialog(textGUI, "Directory Unshared", directory.getName()+" has been unshared", MessageDialogButton.OK)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,311 @@
|
|||||||
|
package com.muwire.clilanterna
|
||||||
|
|
||||||
|
import com.googlecode.lanterna.TerminalPosition
|
||||||
|
import com.googlecode.lanterna.TerminalSize
|
||||||
|
import com.googlecode.lanterna.gui2.BasicWindow
|
||||||
|
import com.googlecode.lanterna.gui2.BorderLayout
|
||||||
|
import com.googlecode.lanterna.gui2.Borders
|
||||||
|
import com.googlecode.lanterna.gui2.Button
|
||||||
|
import com.googlecode.lanterna.gui2.GridLayout
|
||||||
|
import com.googlecode.lanterna.gui2.GridLayout.Alignment
|
||||||
|
import com.googlecode.lanterna.gui2.dialogs.MessageDialog
|
||||||
|
import com.googlecode.lanterna.gui2.dialogs.MessageDialogButton
|
||||||
|
import com.googlecode.lanterna.gui2.Label
|
||||||
|
import com.googlecode.lanterna.gui2.LayoutData
|
||||||
|
import com.googlecode.lanterna.gui2.Panel
|
||||||
|
import com.googlecode.lanterna.gui2.Panels
|
||||||
|
import com.googlecode.lanterna.gui2.TextGUI
|
||||||
|
import com.googlecode.lanterna.gui2.Window
|
||||||
|
import com.googlecode.lanterna.screen.Screen
|
||||||
|
import com.googlecode.lanterna.gui2.TextBox
|
||||||
|
import com.muwire.core.Core
|
||||||
|
import com.muwire.core.DownloadedFile
|
||||||
|
import com.muwire.core.connection.ConnectionEvent
|
||||||
|
import com.muwire.core.files.FileDownloadedEvent
|
||||||
|
import com.muwire.core.files.FileHashedEvent
|
||||||
|
import com.muwire.core.files.FileLoadedEvent
|
||||||
|
import com.muwire.core.files.FileUnsharedEvent
|
||||||
|
import com.muwire.core.hostcache.HostDiscoveredEvent
|
||||||
|
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 {
|
||||||
|
|
||||||
|
private final Core core
|
||||||
|
private final TextGUI textGUI
|
||||||
|
private final Screen screen
|
||||||
|
|
||||||
|
private final TextBox searchTextBox
|
||||||
|
|
||||||
|
private final DownloadsModel downloadsModel
|
||||||
|
private final UploadsModel uploadsModel
|
||||||
|
private final FilesModel filesModel
|
||||||
|
private final TrustModel trustModel
|
||||||
|
|
||||||
|
private final Label connectionCount, incoming, outgoing
|
||||||
|
private final Label known, failing, hopeless
|
||||||
|
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);
|
||||||
|
|
||||||
|
this.core = core
|
||||||
|
this.textGUI = textGUI
|
||||||
|
this.screen = screen
|
||||||
|
|
||||||
|
downloadsModel = new DownloadsModel(textGUI.getGUIThread(),core, props)
|
||||||
|
uploadsModel = new UploadsModel(textGUI.getGUIThread(), core, props)
|
||||||
|
filesModel = new FilesModel(textGUI.getGUIThread(),core)
|
||||||
|
trustModel = new TrustModel(textGUI.getGUIThread(), core)
|
||||||
|
|
||||||
|
setHints([Window.Hint.EXPANDED])
|
||||||
|
Panel contentPanel = new Panel()
|
||||||
|
setComponent(contentPanel)
|
||||||
|
|
||||||
|
BorderLayout borderLayout = new BorderLayout()
|
||||||
|
contentPanel.setLayoutManager(borderLayout)
|
||||||
|
|
||||||
|
Panel buttonsPanel = new Panel()
|
||||||
|
contentPanel.addComponent(buttonsPanel, BorderLayout.Location.TOP)
|
||||||
|
|
||||||
|
GridLayout gridLayout = new GridLayout(7)
|
||||||
|
buttonsPanel.setLayoutManager(gridLayout)
|
||||||
|
|
||||||
|
searchTextBox = new TextBox(new TerminalSize(40, 1))
|
||||||
|
Button searchButton = new Button("Search", { search() })
|
||||||
|
Button downloadsButton = new Button("Downloads", {download()})
|
||||||
|
Button uploadsButton = new Button("Uploads", {upload()})
|
||||||
|
Button filesButton = new Button("Files", { files() })
|
||||||
|
Button trustButton = new Button("Trust", {trust()})
|
||||||
|
Button quitButton = new Button("Quit", {close()})
|
||||||
|
|
||||||
|
LayoutData layoutData = GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER)
|
||||||
|
|
||||||
|
buttonsPanel.with {
|
||||||
|
addComponent(searchTextBox, layoutData)
|
||||||
|
addComponent(searchButton, layoutData)
|
||||||
|
addComponent(downloadsButton, layoutData)
|
||||||
|
addComponent(uploadsButton, layoutData)
|
||||||
|
addComponent(filesButton, layoutData)
|
||||||
|
addComponent(trustButton, layoutData)
|
||||||
|
addComponent(quitButton, layoutData)
|
||||||
|
}
|
||||||
|
|
||||||
|
Panel bottomPanel = new Panel()
|
||||||
|
contentPanel.addComponent(bottomPanel, BorderLayout.Location.BOTTOM)
|
||||||
|
BorderLayout bottomLayout = new BorderLayout()
|
||||||
|
bottomPanel.setLayoutManager(bottomLayout)
|
||||||
|
|
||||||
|
Label persona = new Label(core.me.getHumanReadableName())
|
||||||
|
bottomPanel.addComponent(persona, BorderLayout.Location.LEFT)
|
||||||
|
|
||||||
|
|
||||||
|
Panel connectionsPanel = new Panel()
|
||||||
|
connectionsPanel.setLayoutManager(new GridLayout(2))
|
||||||
|
Label connections = new Label("Connections:")
|
||||||
|
connectionsPanel.addComponent(connections, GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER))
|
||||||
|
connectionCount = new Label("0")
|
||||||
|
connectionsPanel.addComponent(connectionCount, GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER))
|
||||||
|
|
||||||
|
bottomPanel.addComponent(connectionsPanel, BorderLayout.Location.RIGHT)
|
||||||
|
|
||||||
|
|
||||||
|
Panel centralPanel = new Panel()
|
||||||
|
centralPanel.setLayoutManager(new GridLayout(1))
|
||||||
|
contentPanel.addComponent(centralPanel, BorderLayout.Location.CENTER)
|
||||||
|
Panel statusPanel = new Panel()
|
||||||
|
statusPanel.setLayoutManager(new GridLayout(2))
|
||||||
|
statusPanel.withBorder(Borders.doubleLine("Stats"))
|
||||||
|
centralPanel.addComponent(statusPanel, GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER, true, true))
|
||||||
|
|
||||||
|
incoming = new Label("0")
|
||||||
|
outgoing = new Label("0")
|
||||||
|
known = new Label("0")
|
||||||
|
failing = new Label("0")
|
||||||
|
hopeless = new Label("0")
|
||||||
|
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)
|
||||||
|
addComponent(incoming, layoutData)
|
||||||
|
addComponent(new Label("Outgoing Connections: "), layoutData)
|
||||||
|
addComponent(outgoing, layoutData)
|
||||||
|
addComponent(new Label("Known Hosts: "), layoutData)
|
||||||
|
addComponent(known, layoutData)
|
||||||
|
addComponent(new Label("Failing Hosts: "), layoutData)
|
||||||
|
addComponent(failing, layoutData)
|
||||||
|
addComponent(new Label("Hopeless Hosts: "), layoutData)
|
||||||
|
addComponent(hopeless, layoutData)
|
||||||
|
addComponent(new Label("Shared Files: "), layoutData)
|
||||||
|
addComponent(sharedFiles, layoutData)
|
||||||
|
addComponent(new Label("Times Browsed: "), layoutData)
|
||||||
|
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()
|
||||||
|
|
||||||
|
searchButton.takeFocus()
|
||||||
|
core.eventBus.register(ConnectionEvent.class, this)
|
||||||
|
core.eventBus.register(HostDiscoveredEvent.class, this)
|
||||||
|
core.eventBus.register(FileLoadedEvent.class, this)
|
||||||
|
core.eventBus.register(FileHashedEvent.class, this)
|
||||||
|
core.eventBus.register(FileUnsharedEvent.class, this)
|
||||||
|
core.eventBus.register(FileDownloadedEvent.class, this)
|
||||||
|
core.eventBus.register(UpdateAvailableEvent.class, this)
|
||||||
|
core.eventBus.register(UpdateDownloadedEvent.class, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
void onConnectionEvent(ConnectionEvent e) {
|
||||||
|
textGUI.getGUIThread().invokeLater {
|
||||||
|
connectionCount.setText(String.valueOf(core.connectionManager.connections.size()))
|
||||||
|
refreshStats()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onHostDiscoveredEvent(HostDiscoveredEvent e) {
|
||||||
|
textGUI.getGUIThread().invokeLater {
|
||||||
|
refreshStats()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onFileLoadedEvent(FileLoadedEvent e) {
|
||||||
|
textGUI.getGUIThread().invokeLater {
|
||||||
|
refreshStats()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onFileHashedEvent(FileHashedEvent e) {
|
||||||
|
textGUI.getGUIThread().invokeLater {
|
||||||
|
refreshStats()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onFileUnsharedEvent(FileUnsharedEvent e) {
|
||||||
|
textGUI.getGUIThread().invokeLater {
|
||||||
|
refreshStats()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onFileDownloadedEvent(FileDownloadedEvent e) {
|
||||||
|
textGUI.getGUIThread().invokeLater {
|
||||||
|
refreshStats()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onUpdateAvailableEvent(UpdateAvailableEvent e) {
|
||||||
|
textGUI.getGUIThread().invokeLater {
|
||||||
|
String label = "$e.version is available with hash $e.infoHash"
|
||||||
|
updateStatus.setText(label)
|
||||||
|
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()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onUpdateDownloadedEvent(UpdateDownloadedEvent e) {
|
||||||
|
textGUI.getGUIThread().invokeLater {
|
||||||
|
String label = "$e.version downloaded"
|
||||||
|
updateStatus.setText(label)
|
||||||
|
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()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TerminalSize sizeForTables() {
|
||||||
|
TerminalSize full = screen.getTerminalSize()
|
||||||
|
return new TerminalSize(full.getColumns(), full.getRows() - 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
private void search() {
|
||||||
|
String query = searchTextBox.getText()
|
||||||
|
query = query.trim()
|
||||||
|
if (query.length() == 0)
|
||||||
|
return
|
||||||
|
if (query.length() > 128)
|
||||||
|
query = query.substring(0, 128)
|
||||||
|
|
||||||
|
SearchModel model = new SearchModel(query, core, textGUI.getGUIThread())
|
||||||
|
textGUI.addWindowAndWait(new SearchView(model,core, textGUI, sizeForTables()))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void download() {
|
||||||
|
textGUI.addWindowAndWait(new DownloadsView(core, downloadsModel, textGUI, sizeForTables()))
|
||||||
|
}
|
||||||
|
|
||||||
|
private void upload() {
|
||||||
|
textGUI.addWindowAndWait(new UploadsView(uploadsModel, sizeForTables()))
|
||||||
|
}
|
||||||
|
|
||||||
|
private void files() {
|
||||||
|
textGUI.addWindowAndWait(new FilesView(filesModel, textGUI, core, sizeForTables()))
|
||||||
|
}
|
||||||
|
|
||||||
|
private void trust() {
|
||||||
|
textGUI.addWindowAndWait(new TrustView(trustModel, textGUI, core, sizeForTables()))
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshStats() {
|
||||||
|
int inCon = 0
|
||||||
|
int outCon = 0
|
||||||
|
core.connectionManager.getConnections().each {
|
||||||
|
if (it.isIncoming())
|
||||||
|
inCon++
|
||||||
|
else
|
||||||
|
outCon++
|
||||||
|
}
|
||||||
|
int knownHosts = core.hostCache.hosts.size()
|
||||||
|
int failingHosts = core.hostCache.countFailingHosts()
|
||||||
|
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))
|
||||||
|
known.setText(String.valueOf(knownHosts))
|
||||||
|
failing.setText(String.valueOf(failingHosts))
|
||||||
|
hopeless.setText(String.valueOf(hopelessHosts))
|
||||||
|
sharedFiles.setText(String.valueOf(shared))
|
||||||
|
timesBrowsed.setText(String.valueOf(browsed))
|
||||||
|
usedRam.setText(usedMem)
|
||||||
|
totalRam.setText(totalMem)
|
||||||
|
maxRam.setText(maxMem)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,15 @@
|
|||||||
|
package com.muwire.clilanterna
|
||||||
|
|
||||||
|
import com.muwire.core.Persona
|
||||||
|
|
||||||
|
class PersonaWrapper {
|
||||||
|
private final Persona persona
|
||||||
|
PersonaWrapper(Persona persona) {
|
||||||
|
this.persona = persona
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
persona.getHumanReadableName()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,28 @@
|
|||||||
|
package com.muwire.clilanterna
|
||||||
|
|
||||||
|
import com.muwire.core.search.UIResultBatchEvent
|
||||||
|
import com.muwire.core.search.UIResultEvent
|
||||||
|
|
||||||
|
import net.i2p.data.Base64
|
||||||
|
import net.i2p.data.DataHelper
|
||||||
|
|
||||||
|
import com.googlecode.lanterna.gui2.table.TableModel
|
||||||
|
|
||||||
|
class ResultsModel {
|
||||||
|
private final UIResultBatchEvent results
|
||||||
|
final TableModel model
|
||||||
|
final Map<String, UIResultEvent> rootToResult = new HashMap<>()
|
||||||
|
|
||||||
|
ResultsModel(UIResultBatchEvent results) {
|
||||||
|
this.results = results
|
||||||
|
model = new TableModel("Name","Size","Hash","Sources","Comment")
|
||||||
|
results.results.each {
|
||||||
|
String size = DataHelper.formatSize2Decimal(it.size, false) + "B"
|
||||||
|
String infoHash = Base64.encode(it.infohash.getRoot())
|
||||||
|
String sources = String.valueOf(it.sources.size())
|
||||||
|
String comment = String.valueOf(it.comment != null)
|
||||||
|
model.addRow(it.name, size, infoHash, sources, comment)
|
||||||
|
rootToResult.put(infoHash, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,94 @@
|
|||||||
|
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.LayoutData
|
||||||
|
import com.googlecode.lanterna.gui2.GridLayout.Alignment
|
||||||
|
import com.googlecode.lanterna.gui2.Panel
|
||||||
|
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.gui2.table.Table
|
||||||
|
|
||||||
|
import com.muwire.core.Core
|
||||||
|
import com.muwire.core.download.UIDownloadEvent
|
||||||
|
import com.muwire.core.search.UIResultEvent
|
||||||
|
|
||||||
|
class ResultsView extends BasicWindow {
|
||||||
|
|
||||||
|
private final ResultsModel model
|
||||||
|
private final TextGUI textGUI
|
||||||
|
private final Core core
|
||||||
|
private final Table table
|
||||||
|
private final TerminalSize terminalSize
|
||||||
|
|
||||||
|
ResultsView(ResultsModel model, Core core, TextGUI textGUI, TerminalSize terminalSize) {
|
||||||
|
super(model.results.results[0].sender.getHumanReadableName() + " Results")
|
||||||
|
this.model = model
|
||||||
|
this.core = core
|
||||||
|
this.textGUI = textGUI
|
||||||
|
this.terminalSize = terminalSize
|
||||||
|
|
||||||
|
setHints([Window.Hint.EXPANDED])
|
||||||
|
|
||||||
|
Panel contentPanel = new Panel()
|
||||||
|
contentPanel.setLayoutManager(new GridLayout(1))
|
||||||
|
|
||||||
|
table = new Table("Name","Size","Hash","Sources","Comment")
|
||||||
|
table.setCellSelection(false)
|
||||||
|
table.setSelectAction({rowSelected()})
|
||||||
|
table.setTableModel(model.model)
|
||||||
|
table.setVisibleRows(terminalSize.getRows())
|
||||||
|
contentPanel.addComponent(table, GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER, true, false))
|
||||||
|
|
||||||
|
Button closeButton = new Button("Close", {close()})
|
||||||
|
contentPanel.addComponent(closeButton, GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER, true, false))
|
||||||
|
|
||||||
|
setComponent(contentPanel)
|
||||||
|
closeButton.takeFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rowSelected() {
|
||||||
|
int selectedRow = table.getSelectedRow()
|
||||||
|
def rows = model.model.getRow(selectedRow)
|
||||||
|
boolean comment = Boolean.parseBoolean(rows[4])
|
||||||
|
if (comment) {
|
||||||
|
Window prompt = new BasicWindow("Download Or View Comment")
|
||||||
|
prompt.setHints([Window.Hint.CENTERED])
|
||||||
|
Panel contentPanel = new Panel()
|
||||||
|
contentPanel.setLayoutManager(new GridLayout(3))
|
||||||
|
Button downloadButton = new Button("Download", {download(rows[2])})
|
||||||
|
Button viewButton = new Button("View Comment", {viewComment(rows[2])})
|
||||||
|
Button closeButton = new Button("Cancel", {prompt.close()})
|
||||||
|
|
||||||
|
LayoutData layoutData = GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER)
|
||||||
|
contentPanel.addComponent(downloadButton, layoutData)
|
||||||
|
contentPanel.addComponent(viewButton, layoutData)
|
||||||
|
contentPanel.addComponent(closeButton, layoutData)
|
||||||
|
prompt.setComponent(contentPanel)
|
||||||
|
downloadButton.takeFocus()
|
||||||
|
textGUI.addWindowAndWait(prompt)
|
||||||
|
} else {
|
||||||
|
download(rows[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void download(String infohash) {
|
||||||
|
UIResultEvent result = model.rootToResult[infohash]
|
||||||
|
def file = new File(core.muOptions.downloadLocation, result.name)
|
||||||
|
|
||||||
|
core.eventBus.publish(new UIDownloadEvent(result : [result], sources : result.sources,
|
||||||
|
target : file, sequential : false))
|
||||||
|
MessageDialog.showMessageDialog(textGUI, "Download Started", "Started download of "+result.name, MessageDialogButton.OK)
|
||||||
|
}
|
||||||
|
|
||||||
|
private void viewComment(String infohash) {
|
||||||
|
UIResultEvent result = model.rootToResult[infohash]
|
||||||
|
ViewCommentView view = new ViewCommentView(result, terminalSize)
|
||||||
|
textGUI.addWindowAndWait(view)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,86 @@
|
|||||||
|
package com.muwire.clilanterna
|
||||||
|
|
||||||
|
import com.muwire.core.Core
|
||||||
|
import com.muwire.core.Persona
|
||||||
|
import com.muwire.core.SplitPattern
|
||||||
|
import com.muwire.core.search.QueryEvent
|
||||||
|
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
|
||||||
|
class SearchModel {
|
||||||
|
private final TextGUIThread guiThread
|
||||||
|
private final String query
|
||||||
|
private final Core core
|
||||||
|
final TableModel model
|
||||||
|
|
||||||
|
private final Map<Persona, UIResultBatchEvent> resultsPerSender = new HashMap<>()
|
||||||
|
|
||||||
|
SearchModel(String query, Core core, TextGUIThread guiThread) {
|
||||||
|
this.query = query
|
||||||
|
this.core = core
|
||||||
|
this.guiThread = guiThread
|
||||||
|
this.model = new TableModel("Sender","Results","Browse","Trust")
|
||||||
|
core.eventBus.register(UIResultBatchEvent.class, this)
|
||||||
|
|
||||||
|
|
||||||
|
boolean hashSearch = false
|
||||||
|
byte [] root = null
|
||||||
|
if (query.length() == 44 && query.indexOf(" ") < 0) {
|
||||||
|
try {
|
||||||
|
root = Base64.decode(query)
|
||||||
|
hashSearch = true
|
||||||
|
} catch (Exception e) {
|
||||||
|
// not hash search
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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, sig: sig.data))
|
||||||
|
}
|
||||||
|
|
||||||
|
void unregister() {
|
||||||
|
core.eventBus.unregister(UIResultBatchEvent.class, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
void onUIResultBatchEvent(UIResultBatchEvent e) {
|
||||||
|
guiThread.invokeLater {
|
||||||
|
Persona sender = e.results[0].sender
|
||||||
|
|
||||||
|
resultsPerSender.put(sender, e)
|
||||||
|
|
||||||
|
String browse = String.valueOf(e.results[0].browse)
|
||||||
|
String results = String.valueOf(e.results.length)
|
||||||
|
String trust = core.trustService.getLevel(sender.destination).toString()
|
||||||
|
model.addRow([new PersonaWrapper(sender), results, browse, trust])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,113 @@
|
|||||||
|
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.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.gui2.table.Table
|
||||||
|
|
||||||
|
import com.muwire.core.Core
|
||||||
|
import com.muwire.core.Persona
|
||||||
|
import com.muwire.core.trust.TrustEvent
|
||||||
|
import com.muwire.core.trust.TrustLevel
|
||||||
|
|
||||||
|
class SearchView extends BasicWindow {
|
||||||
|
private final Core core
|
||||||
|
private final SearchModel model
|
||||||
|
private final Table table
|
||||||
|
private final TextGUI textGUI
|
||||||
|
private final TerminalSize terminalSize
|
||||||
|
|
||||||
|
SearchView(SearchModel model, Core core, TextGUI textGUI, TerminalSize terminalSize) {
|
||||||
|
super(model.query)
|
||||||
|
this.core = core
|
||||||
|
this.model = model
|
||||||
|
this.textGUI = textGUI
|
||||||
|
this.terminalSize = terminalSize
|
||||||
|
|
||||||
|
setHints([Window.Hint.EXPANDED])
|
||||||
|
|
||||||
|
Panel contentPanel = new Panel()
|
||||||
|
contentPanel.setLayoutManager(new GridLayout(1))
|
||||||
|
|
||||||
|
table = new Table("Sender","Results","Browse","Trust")
|
||||||
|
table.setCellSelection(false)
|
||||||
|
table.setSelectAction({rowSelected()})
|
||||||
|
table.setTableModel(model.model)
|
||||||
|
table.setVisibleRows(terminalSize.getRows())
|
||||||
|
contentPanel.addComponent(table, GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER, true, false))
|
||||||
|
|
||||||
|
Button closeButton = new Button("Close", {
|
||||||
|
model.unregister()
|
||||||
|
close()
|
||||||
|
})
|
||||||
|
contentPanel.addComponent(closeButton, GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER, true, false))
|
||||||
|
|
||||||
|
setComponent(contentPanel)
|
||||||
|
closeButton.takeFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rowSelected() {
|
||||||
|
int selectedRow = table.getSelectedRow()
|
||||||
|
def rows = model.model.getRow(selectedRow)
|
||||||
|
Persona persona = rows[0].persona
|
||||||
|
boolean browse = Boolean.parseBoolean(rows[2])
|
||||||
|
Window prompt = new BasicWindow("Show Or Browse "+rows[0]+"?")
|
||||||
|
prompt.setHints([Window.Hint.CENTERED])
|
||||||
|
Panel contentPanel = new Panel()
|
||||||
|
contentPanel.setLayoutManager(new GridLayout(6))
|
||||||
|
LayoutData layoutData = GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER)
|
||||||
|
Button showResults = new Button("Show Results", {
|
||||||
|
showResults(persona)
|
||||||
|
})
|
||||||
|
Button browseHost = new Button("Browse Host", {
|
||||||
|
BrowseModel model = new BrowseModel(persona, core, textGUI.getGUIThread())
|
||||||
|
BrowseView view = new BrowseView(model, textGUI, core, terminalSize)
|
||||||
|
textGUI.addWindowAndWait(view)
|
||||||
|
})
|
||||||
|
Button trustHost = new Button("Trust",{
|
||||||
|
core.eventBus.publish(new TrustEvent(persona : persona, level : TrustLevel.TRUSTED))
|
||||||
|
MessageDialog.showMessageDialog(textGUI, "Marked Trusted", persona.getHumanReadableName() + " has been marked trusted",
|
||||||
|
MessageDialogButton.OK)
|
||||||
|
})
|
||||||
|
Button neutralHost = new Button("Neutral",{
|
||||||
|
core.eventBus.publish(new TrustEvent(persona : persona, level : TrustLevel.NEUTRAL))
|
||||||
|
MessageDialog.showMessageDialog(textGUI, "Marked Neutral", persona.getHumanReadableName() + " has been marked neutral",
|
||||||
|
MessageDialogButton.OK)
|
||||||
|
})
|
||||||
|
Button distrustHost = new Button("Distrust", {
|
||||||
|
core.eventBus.publish(new TrustEvent(persona : persona, level : TrustLevel.DISTRUSTED))
|
||||||
|
MessageDialog.showMessageDialog(textGUI, "Marked Distrusted", persona.getHumanReadableName() + " has been marked distrusted",
|
||||||
|
MessageDialogButton.OK)
|
||||||
|
})
|
||||||
|
Button closePrompt = new Button("Close", {prompt.close()})
|
||||||
|
|
||||||
|
contentPanel.with {
|
||||||
|
addComponent(showResults, layoutData)
|
||||||
|
if (browse)
|
||||||
|
addComponent(browseHost, layoutData)
|
||||||
|
addComponent(trustHost, layoutData)
|
||||||
|
addComponent(neutralHost, layoutData)
|
||||||
|
addComponent(distrustHost, layoutData)
|
||||||
|
addComponent(closePrompt, layoutData)
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt.setComponent(contentPanel)
|
||||||
|
showResults.takeFocus()
|
||||||
|
textGUI.addWindowAndWait(prompt)
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showResults(Persona persona) {
|
||||||
|
def results = model.resultsPerSender.get(persona)
|
||||||
|
ResultsModel resultsModel = new ResultsModel(results)
|
||||||
|
ResultsView resultsView = new ResultsView(resultsModel, core, textGUI, terminalSize)
|
||||||
|
textGUI.addWindowAndWait(resultsView)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,16 @@
|
|||||||
|
package com.muwire.clilanterna
|
||||||
|
|
||||||
|
import com.muwire.core.SharedFile
|
||||||
|
|
||||||
|
class SharedFileWrapper {
|
||||||
|
private final SharedFile sharedFile
|
||||||
|
|
||||||
|
SharedFileWrapper(SharedFile sharedFile) {
|
||||||
|
this.sharedFile = sharedFile
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
sharedFile.getCachedPath()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,49 @@
|
|||||||
|
package com.muwire.clilanterna
|
||||||
|
|
||||||
|
import com.googlecode.lanterna.gui2.TextGUIThread
|
||||||
|
import com.googlecode.lanterna.gui2.table.TableModel
|
||||||
|
import com.muwire.core.Core
|
||||||
|
import com.muwire.core.trust.RemoteTrustList
|
||||||
|
import com.muwire.core.trust.TrustEvent
|
||||||
|
|
||||||
|
class TrustListModel {
|
||||||
|
private final TextGUIThread guiThread
|
||||||
|
private final RemoteTrustList trustList
|
||||||
|
private final Core core
|
||||||
|
private final TableModel trustedTableModel, distrustedTableModel
|
||||||
|
|
||||||
|
TrustListModel(RemoteTrustList trustList, Core core) {
|
||||||
|
this.trustList = trustList
|
||||||
|
this.core = core
|
||||||
|
|
||||||
|
trustedTableModel = new TableModel("Trusted User","Your Trust")
|
||||||
|
distrustedTableModel = new TableModel("Distrusted User", "Your Trust")
|
||||||
|
refreshModels()
|
||||||
|
|
||||||
|
core.eventBus.register(TrustEvent.class, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
void onTrustEvent(TrustEvent e) {
|
||||||
|
guiThread.invokeLater {
|
||||||
|
refreshModels()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshModels() {
|
||||||
|
int trustRows = trustedTableModel.getRowCount()
|
||||||
|
trustRows.times { trustedTableModel.removeRow(0) }
|
||||||
|
int distrustRows = distrustedTableModel.getRowCount()
|
||||||
|
distrustRows.times { distrustedTableModel.removeRow(0) }
|
||||||
|
|
||||||
|
trustList.good.each {
|
||||||
|
trustedTableModel.addRow(new PersonaWrapper(it), core.trustService.getLevel(it.destination))
|
||||||
|
}
|
||||||
|
trustList.bad.each {
|
||||||
|
distrustedTableModel.addRow(new PersonaWrapper(it), core.trustService.getLevel(it.destination))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void unregister() {
|
||||||
|
core.eventBus.unregister(TrustEvent.class, this)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,118 @@
|
|||||||
|
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.Label
|
||||||
|
import com.googlecode.lanterna.gui2.LayoutData
|
||||||
|
import com.googlecode.lanterna.gui2.Panel
|
||||||
|
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.gui2.table.Table
|
||||||
|
import com.muwire.core.Core
|
||||||
|
import com.muwire.core.Persona
|
||||||
|
import com.muwire.core.trust.TrustEvent
|
||||||
|
import com.muwire.core.trust.TrustLevel
|
||||||
|
|
||||||
|
class TrustListView extends BasicWindow {
|
||||||
|
private final TrustListModel model
|
||||||
|
private final TextGUI textGUI
|
||||||
|
private final Core core
|
||||||
|
private final TerminalSize terminalSize
|
||||||
|
private final Table trusted, distrusted
|
||||||
|
|
||||||
|
TrustListView(TrustListModel model, TextGUI textGUI, Core core, TerminalSize terminalSize) {
|
||||||
|
this.model = model
|
||||||
|
this.textGUI = textGUI
|
||||||
|
this.core = core
|
||||||
|
this.terminalSize = terminalSize
|
||||||
|
|
||||||
|
int tableSize = terminalSize.getRows() - 10
|
||||||
|
|
||||||
|
setHints([Window.Hint.EXPANDED])
|
||||||
|
|
||||||
|
Panel contentPanel = new Panel()
|
||||||
|
contentPanel.setLayoutManager(new GridLayout(1))
|
||||||
|
LayoutData layoutData = GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER, true, false)
|
||||||
|
|
||||||
|
Label nameLabel = new Label("Trust list for "+model.trustList.persona.getHumanReadableName())
|
||||||
|
Label lastUpdatedLabel = new Label("Last updated "+new Date(model.trustList.timestamp))
|
||||||
|
contentPanel.addComponent(nameLabel, layoutData)
|
||||||
|
contentPanel.addComponent(lastUpdatedLabel, layoutData)
|
||||||
|
|
||||||
|
|
||||||
|
Panel topPanel = new Panel()
|
||||||
|
topPanel.setLayoutManager(new GridLayout(2))
|
||||||
|
|
||||||
|
trusted = new Table("Trusted User","Your Trust")
|
||||||
|
trusted.with {
|
||||||
|
setCellSelection(false)
|
||||||
|
setTableModel(model.trustedTableModel)
|
||||||
|
setVisibleRows(tableSize)
|
||||||
|
}
|
||||||
|
trusted.setSelectAction({ actionsForUser(true) })
|
||||||
|
topPanel.addComponent(trusted, layoutData)
|
||||||
|
|
||||||
|
distrusted = new Table("Distrusted User", "Your Trust")
|
||||||
|
distrusted.with {
|
||||||
|
setCellSelection(false)
|
||||||
|
setTableModel(model.distrustedTableModel)
|
||||||
|
setVisibleRows(tableSize)
|
||||||
|
}
|
||||||
|
distrusted.setSelectAction({actionsForUser(false)})
|
||||||
|
topPanel.addComponent(distrusted, layoutData)
|
||||||
|
|
||||||
|
Button closeButton = new Button("Close",{close()})
|
||||||
|
|
||||||
|
contentPanel.addComponent(topPanel, layoutData)
|
||||||
|
contentPanel.addComponent(closeButton, layoutData)
|
||||||
|
|
||||||
|
setComponent(contentPanel)
|
||||||
|
}
|
||||||
|
|
||||||
|
private void actionsForUser(boolean trustedUser) {
|
||||||
|
def table = trustedUser ? trusted : distrusted
|
||||||
|
def model = trustedUser ? model.trustedTableModel : model.distrustedTableModel
|
||||||
|
|
||||||
|
int selectedRow = table.getSelectedRow()
|
||||||
|
def row = model.getRow(selectedRow)
|
||||||
|
|
||||||
|
Persona persona = row[0].persona
|
||||||
|
|
||||||
|
Window prompt = new BasicWindow("Actions for "+persona.getHumanReadableName())
|
||||||
|
prompt.setHints([Window.Hint.CENTERED])
|
||||||
|
Panel contentPanel = new Panel()
|
||||||
|
contentPanel.setLayoutManager(new GridLayout(4))
|
||||||
|
LayoutData layoutData = GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER)
|
||||||
|
|
||||||
|
Button trustButton = new Button("Trust",{
|
||||||
|
core.eventBus.publish(new TrustEvent(persona : persona, level : TrustLevel.TRUSTED))
|
||||||
|
MessageDialog.showMessageDialog(textGUI, "Marked Trusted", persona.getHumanReadableName() + "has been marked trusted",
|
||||||
|
MessageDialogButton.OK)
|
||||||
|
})
|
||||||
|
Button neutralButton = new Button("Neutral",{
|
||||||
|
core.eventBus.publish(new TrustEvent(persona : persona, level : TrustLevel.NEUTRAL))
|
||||||
|
MessageDialog.showMessageDialog(textGUI, "Marked Neutral", persona.getHumanReadableName() + "has been marked neutral",
|
||||||
|
MessageDialogButton.OK)
|
||||||
|
})
|
||||||
|
Button distrustButton = new Button("Distrust",{
|
||||||
|
core.eventBus.publish(new TrustEvent(persona : persona, level : TrustLevel.DISTRUSTED))
|
||||||
|
MessageDialog.showMessageDialog(textGUI, "Marked Distrusted", persona.getHumanReadableName() + "has been marked distrusted",
|
||||||
|
MessageDialogButton.OK)
|
||||||
|
})
|
||||||
|
Button closeButton = new Button("Close",{prompt.close()})
|
||||||
|
|
||||||
|
contentPanel.with {
|
||||||
|
addComponent(trustButton,layoutData)
|
||||||
|
addComponent(neutralButton, layoutData)
|
||||||
|
addComponent(distrustButton, layoutData)
|
||||||
|
addComponent(closeButton, layoutData)
|
||||||
|
}
|
||||||
|
prompt.setComponent(contentPanel)
|
||||||
|
textGUI.addWindowAndWait(prompt)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,15 @@
|
|||||||
|
package com.muwire.clilanterna
|
||||||
|
|
||||||
|
import com.muwire.core.trust.RemoteTrustList
|
||||||
|
|
||||||
|
class TrustListWrapper {
|
||||||
|
private final RemoteTrustList trustList
|
||||||
|
TrustListWrapper(RemoteTrustList trustList) {
|
||||||
|
this.trustList = trustList
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
trustList.persona.getHumanReadableName()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,78 @@
|
|||||||
|
package com.muwire.clilanterna
|
||||||
|
|
||||||
|
import com.googlecode.lanterna.gui2.TextGUIThread
|
||||||
|
import com.googlecode.lanterna.gui2.table.TableModel
|
||||||
|
import com.muwire.core.Core
|
||||||
|
import com.muwire.core.files.AllFilesLoadedEvent
|
||||||
|
import com.muwire.core.trust.TrustEvent
|
||||||
|
import com.muwire.core.trust.TrustSubscriptionEvent
|
||||||
|
import com.muwire.core.trust.TrustSubscriptionUpdatedEvent
|
||||||
|
|
||||||
|
class TrustModel {
|
||||||
|
private final TextGUIThread guiThread
|
||||||
|
private final Core core
|
||||||
|
private final TableModel modelTrusted, modelDistrusted, modelSubscriptions
|
||||||
|
|
||||||
|
TrustModel(TextGUIThread guiThread, Core core) {
|
||||||
|
this.guiThread = guiThread
|
||||||
|
this.core = core
|
||||||
|
|
||||||
|
modelTrusted = new TableModel("Trusted Users")
|
||||||
|
modelDistrusted = new TableModel("Distrusted Users")
|
||||||
|
modelSubscriptions = new TableModel("Name","Trusted","Distrusted","Status","Last Updated")
|
||||||
|
|
||||||
|
core.eventBus.register(TrustEvent.class, this)
|
||||||
|
core.eventBus.register(AllFilesLoadedEvent.class, this)
|
||||||
|
core.eventBus.register(TrustSubscriptionUpdatedEvent.class, this)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void onTrustEvent(TrustEvent e) {
|
||||||
|
guiThread.invokeLater {
|
||||||
|
refreshModels()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onTrustSubscriptionUpdatedEvent(TrustSubscriptionUpdatedEvent e) {
|
||||||
|
guiThread.invokeLater {
|
||||||
|
refreshModels()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onAllFilesLoadedEvent(AllFilesLoadedEvent e) {
|
||||||
|
guiThread.invokeLater {
|
||||||
|
refreshModels()
|
||||||
|
}
|
||||||
|
core.muOptions.trustSubscriptions.each {
|
||||||
|
core.eventBus.publish(new TrustSubscriptionEvent(persona : it, subscribe : true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshModels() {
|
||||||
|
int trustedRows = modelTrusted.getRowCount()
|
||||||
|
trustedRows.times { modelTrusted.removeRow(0) }
|
||||||
|
int distrustedRows = modelDistrusted.getRowCount()
|
||||||
|
distrustedRows.times { modelDistrusted.removeRow(0) }
|
||||||
|
int subsRows = modelSubscriptions.getRowCount()
|
||||||
|
subsRows.times { modelSubscriptions.removeRow(0) }
|
||||||
|
|
||||||
|
core.trustService.good.values().each {
|
||||||
|
modelTrusted.addRow(new PersonaWrapper(it))
|
||||||
|
}
|
||||||
|
|
||||||
|
core.trustService.bad.values().each {
|
||||||
|
modelDistrusted.addRow(new PersonaWrapper(it))
|
||||||
|
}
|
||||||
|
|
||||||
|
core.trustSubscriber.remoteTrustLists.values().each {
|
||||||
|
def name = new TrustListWrapper(it)
|
||||||
|
String trusted = String.valueOf(it.good.size())
|
||||||
|
String distrusted = String.valueOf(it.bad.size())
|
||||||
|
String status = it.status
|
||||||
|
String lastUpdated = it.timestamp == 0 ? "Never" : new Date(it.timestamp)
|
||||||
|
|
||||||
|
modelSubscriptions.addRow(name, trusted, distrusted, status, lastUpdated)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,204 @@
|
|||||||
|
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.LayoutData
|
||||||
|
import com.googlecode.lanterna.gui2.Panel
|
||||||
|
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.MessageDialogBuilder
|
||||||
|
import com.googlecode.lanterna.gui2.dialogs.MessageDialogButton
|
||||||
|
import com.googlecode.lanterna.gui2.GridLayout.Alignment
|
||||||
|
import com.googlecode.lanterna.gui2.Label
|
||||||
|
import com.googlecode.lanterna.gui2.table.Table
|
||||||
|
import com.muwire.core.Core
|
||||||
|
import com.muwire.core.Persona
|
||||||
|
import com.muwire.core.trust.TrustEvent
|
||||||
|
import com.muwire.core.trust.TrustLevel
|
||||||
|
import com.muwire.core.trust.TrustSubscriptionEvent
|
||||||
|
|
||||||
|
class TrustView extends BasicWindow {
|
||||||
|
private final TrustModel model
|
||||||
|
private final TextGUI textGUI
|
||||||
|
private final Core core
|
||||||
|
private final TerminalSize terminalSize
|
||||||
|
private final Table trusted, distrusted, subscriptions
|
||||||
|
|
||||||
|
TrustView(TrustModel model, TextGUI textGUI, Core core, TerminalSize terminalSize) {
|
||||||
|
this.model = model
|
||||||
|
this.textGUI = textGUI
|
||||||
|
this.core = core
|
||||||
|
this.terminalSize = terminalSize
|
||||||
|
|
||||||
|
int tableSize = (terminalSize.getRows() / 2 - 10).toInteger()
|
||||||
|
|
||||||
|
setHints([Window.Hint.EXPANDED])
|
||||||
|
|
||||||
|
Panel contentPanel = new Panel()
|
||||||
|
contentPanel.setLayoutManager(new GridLayout(1))
|
||||||
|
LayoutData layoutData = GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER, true, false)
|
||||||
|
|
||||||
|
Panel topPanel = new Panel()
|
||||||
|
topPanel.setLayoutManager(new GridLayout(2))
|
||||||
|
|
||||||
|
trusted = new Table("Trusted Users")
|
||||||
|
trusted.setCellSelection(false)
|
||||||
|
trusted.setSelectAction({trustedActions()})
|
||||||
|
trusted.setTableModel(model.modelTrusted)
|
||||||
|
trusted.setVisibleRows(tableSize)
|
||||||
|
topPanel.addComponent(trusted, layoutData)
|
||||||
|
|
||||||
|
distrusted = new Table("Distrusted users")
|
||||||
|
distrusted.setCellSelection(false)
|
||||||
|
distrusted.setSelectAction({distrustedActions()})
|
||||||
|
distrusted.setTableModel(model.modelDistrusted)
|
||||||
|
distrusted.setVisibleRows(tableSize)
|
||||||
|
topPanel.addComponent(distrusted, layoutData)
|
||||||
|
|
||||||
|
Panel bottomPanel = new Panel()
|
||||||
|
bottomPanel.setLayoutManager(new GridLayout(1))
|
||||||
|
|
||||||
|
Label tableName = new Label("Trust List Subscriptions")
|
||||||
|
bottomPanel.addComponent(tableName, layoutData)
|
||||||
|
|
||||||
|
subscriptions = new Table("Name","Trusted","Distrusted","Status","Last Updated")
|
||||||
|
subscriptions.setCellSelection(false)
|
||||||
|
subscriptions.setSelectAction({trustListActions()})
|
||||||
|
subscriptions.setTableModel(model.modelSubscriptions)
|
||||||
|
subscriptions.setVisibleRows(tableSize)
|
||||||
|
bottomPanel.addComponent(subscriptions, layoutData)
|
||||||
|
|
||||||
|
Button closeButton = new Button("Close", {close()})
|
||||||
|
|
||||||
|
contentPanel.addComponent(topPanel, layoutData)
|
||||||
|
contentPanel.addComponent(bottomPanel, layoutData)
|
||||||
|
contentPanel.addComponent(closeButton, layoutData)
|
||||||
|
|
||||||
|
setComponent(contentPanel)
|
||||||
|
}
|
||||||
|
|
||||||
|
private void trustedActions() {
|
||||||
|
int selectedRow = trusted.getSelectedRow()
|
||||||
|
def row = model.modelTrusted.getRow(selectedRow)
|
||||||
|
Persona persona = row[0].persona
|
||||||
|
|
||||||
|
Window prompt = new BasicWindow("Change Trust For "+persona.getHumanReadableName())
|
||||||
|
prompt.setHints([Window.Hint.CENTERED])
|
||||||
|
Panel contentPanel = new Panel()
|
||||||
|
contentPanel.setLayoutManager(new GridLayout(4))
|
||||||
|
LayoutData layoutData = GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER)
|
||||||
|
|
||||||
|
Button subscribe = new Button("Subscribe", {
|
||||||
|
core.muOptions.trustSubscriptions.add(persona)
|
||||||
|
saveMuSettings()
|
||||||
|
core.eventBus.publish(new TrustSubscriptionEvent(persona : persona, subscribe : true))
|
||||||
|
MessageDialog.showMessageDialog(textGUI, "Subscribed", "Subscribed from trust list of " + persona.getHumanReadableName(),
|
||||||
|
MessageDialogButton.OK)
|
||||||
|
model.refreshModels()
|
||||||
|
|
||||||
|
})
|
||||||
|
Button markNeutral = new Button("Mark Neutral", {
|
||||||
|
core.eventBus.publish(new TrustEvent(persona : persona, level : TrustLevel.NEUTRAL))
|
||||||
|
MessageDialog.showMessageDialog(textGUI, "Marked Neutral", persona.getHumanReadableName() + "has been marked neutral",
|
||||||
|
MessageDialogButton.OK)
|
||||||
|
})
|
||||||
|
Button markDistrusted = new Button("Mark Distrusted", {
|
||||||
|
core.eventBus.publish(new TrustEvent(persona : persona, level : TrustLevel.DISTRUSTED))
|
||||||
|
MessageDialog.showMessageDialog(textGUI, "Marked Distrusted", persona.getHumanReadableName() + "has been marked distrusted",
|
||||||
|
MessageDialogButton.OK)
|
||||||
|
})
|
||||||
|
Button closeButton = new Button("Close", {prompt.close()})
|
||||||
|
contentPanel.with {
|
||||||
|
addComponent(subscribe, layoutData)
|
||||||
|
addComponent(markNeutral, layoutData)
|
||||||
|
addComponent(markDistrusted, layoutData)
|
||||||
|
addComponent(closeButton, layoutData)
|
||||||
|
}
|
||||||
|
prompt.setComponent(contentPanel)
|
||||||
|
textGUI.addWindowAndWait(prompt)
|
||||||
|
}
|
||||||
|
|
||||||
|
private void distrustedActions() {
|
||||||
|
int selectedRow = trusted.getSelectedRow()
|
||||||
|
def row = model.modelDistrusted.getRow(selectedRow)
|
||||||
|
Persona persona = row[0].persona
|
||||||
|
|
||||||
|
Window prompt = new BasicWindow("Change Trust For "+persona.getHumanReadableName())
|
||||||
|
prompt.setHints([Window.Hint.CENTERED])
|
||||||
|
Panel contentPanel = new Panel()
|
||||||
|
contentPanel.setLayoutManager(new GridLayout(3))
|
||||||
|
LayoutData layoutData = GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER)
|
||||||
|
|
||||||
|
Button markNeutral = new Button("Mark Neutral", {
|
||||||
|
core.eventBus.publish(new TrustEvent(persona : persona, level : TrustLevel.NEUTRAL))
|
||||||
|
MessageDialog.showMessageDialog(textGUI, "Marked Neutral", persona.getHumanReadableName() + "has been marked neutral",
|
||||||
|
MessageDialogButton.OK)
|
||||||
|
})
|
||||||
|
Button markDistrusted = new Button("Mark Trusted", {
|
||||||
|
core.eventBus.publish(new TrustEvent(persona : persona, level : TrustLevel.TRUSTED))
|
||||||
|
MessageDialog.showMessageDialog(textGUI, "Marked Trusted", persona.getHumanReadableName() + "has been marked trusted",
|
||||||
|
MessageDialogButton.OK)
|
||||||
|
})
|
||||||
|
Button closeButton = new Button("Close", {prompt.close()})
|
||||||
|
contentPanel.with {
|
||||||
|
addComponent(markDistrusted, layoutData)
|
||||||
|
addComponent(markNeutral, layoutData)
|
||||||
|
addComponent(closeButton, layoutData)
|
||||||
|
}
|
||||||
|
prompt.setComponent(contentPanel)
|
||||||
|
textGUI.addWindowAndWait(prompt)
|
||||||
|
}
|
||||||
|
|
||||||
|
private void trustListActions() {
|
||||||
|
int selectedRow = subscriptions.getSelectedRow()
|
||||||
|
def row = model.modelSubscriptions.getRow(selectedRow)
|
||||||
|
|
||||||
|
def trustList = row[0].trustList
|
||||||
|
Persona persona = trustList.persona
|
||||||
|
|
||||||
|
Window prompt = new BasicWindow("Trust List Actions")
|
||||||
|
prompt.setHints([Window.Hint.CENTERED])
|
||||||
|
Panel contentPanel = new Panel()
|
||||||
|
contentPanel.setLayoutManager(new GridLayout(4))
|
||||||
|
LayoutData layoutData = GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER)
|
||||||
|
|
||||||
|
Button reviewButton = new Button("Review",{review(trustList)})
|
||||||
|
Button updateButton = new Button("Update",{
|
||||||
|
core.eventBus.publish(new TrustSubscriptionEvent(persona : persona, subscribe : true))
|
||||||
|
MessageDialog.showMessageDialog(textGUI, "Updating...", "Trust list will update soon", MessageDialogButton.OK)
|
||||||
|
})
|
||||||
|
Button unsubscribeButton = new Button("Unsubscribe", {
|
||||||
|
core.muOptions.trustSubscriptions.remove(persona)
|
||||||
|
saveMuSettings()
|
||||||
|
core.eventBus.publish(new TrustSubscriptionEvent(persona : persona, subscribe : false))
|
||||||
|
MessageDialog.showMessageDialog(textGUI, "Unsubscribed", "Unsubscribed from trust list of " + persona.getHumanReadableName(),
|
||||||
|
MessageDialogButton.OK)
|
||||||
|
model.refreshModels()
|
||||||
|
})
|
||||||
|
Button closeButton = new Button("Close", {prompt.close()})
|
||||||
|
|
||||||
|
contentPanel.with {
|
||||||
|
addComponent(reviewButton, layoutData)
|
||||||
|
addComponent(updateButton, layoutData)
|
||||||
|
addComponent(unsubscribeButton, layoutData)
|
||||||
|
addComponent(closeButton, layoutData)
|
||||||
|
}
|
||||||
|
prompt.setComponent(contentPanel)
|
||||||
|
textGUI.addWindowAndWait(prompt)
|
||||||
|
}
|
||||||
|
|
||||||
|
private void review(def trustList) {
|
||||||
|
TrustListModel model = new TrustListModel(trustList, core)
|
||||||
|
TrustListView view = new TrustListView(model, textGUI, core, terminalSize)
|
||||||
|
textGUI.addWindowAndWait(view)
|
||||||
|
model.unregister()
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveMuSettings() {
|
||||||
|
File settingsFile = new File(core.home,"MuWire.properties")
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,107 @@
|
|||||||
|
package com.muwire.clilanterna
|
||||||
|
|
||||||
|
import com.googlecode.lanterna.gui2.TextGUIThread
|
||||||
|
import com.googlecode.lanterna.gui2.table.TableModel
|
||||||
|
|
||||||
|
import com.muwire.core.Core
|
||||||
|
import com.muwire.core.upload.UploadEvent
|
||||||
|
import com.muwire.core.upload.UploadFinishedEvent
|
||||||
|
import com.muwire.core.upload.Uploader
|
||||||
|
|
||||||
|
import net.i2p.data.DataHelper
|
||||||
|
|
||||||
|
class UploadsModel {
|
||||||
|
private final TextGUIThread guiThread
|
||||||
|
private final Core core
|
||||||
|
private CliSettings props
|
||||||
|
private final List<UploaderWrapper> uploaders = new ArrayList<>()
|
||||||
|
private final TableModel model = new TableModel("Name","Progress","Downloader","Remote Pieces", "Speed")
|
||||||
|
|
||||||
|
UploadsModel(TextGUIThread guiThread, Core core, CliSettings props) {
|
||||||
|
this.guiThread = guiThread
|
||||||
|
this.core = core
|
||||||
|
this.props = props
|
||||||
|
|
||||||
|
core.eventBus.register(UploadEvent.class, this)
|
||||||
|
core.eventBus.register(UploadFinishedEvent.class, this)
|
||||||
|
|
||||||
|
Timer timer = new Timer(true)
|
||||||
|
Runnable refreshModel = {refreshModel()}
|
||||||
|
timer.schedule({
|
||||||
|
guiThread.invokeLater(refreshModel)
|
||||||
|
} as TimerTask, 1000, 1000)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void onUploadEvent(UploadEvent e) {
|
||||||
|
guiThread.invokeLater {
|
||||||
|
UploaderWrapper found = null
|
||||||
|
uploaders.each {
|
||||||
|
if (it.uploader == e.uploader) {
|
||||||
|
found = it
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found != null) {
|
||||||
|
found.uploader = e.uploader
|
||||||
|
found.finished = false
|
||||||
|
} else
|
||||||
|
uploaders << new UploaderWrapper(uploader : e.uploader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onUploadFinishedEvent(UploadFinishedEvent e) {
|
||||||
|
guiThread.invokeLater {
|
||||||
|
uploaders.each {
|
||||||
|
if (it.uploader == e.uploader) {
|
||||||
|
it.finished = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshModel() {
|
||||||
|
int uploadersSize = model.getRowCount()
|
||||||
|
uploadersSize.times { model.removeRow(0) }
|
||||||
|
|
||||||
|
if (props.clearUploads) {
|
||||||
|
uploaders.removeAll { it.finished }
|
||||||
|
}
|
||||||
|
|
||||||
|
uploaders.each {
|
||||||
|
String name = it.uploader.getName()
|
||||||
|
int percent = it.uploader.getProgress()
|
||||||
|
String percentString = "$percent% of piece".toString()
|
||||||
|
String downloader = it.uploader.getDownloader()
|
||||||
|
|
||||||
|
int pieces = it.uploader.getTotalPieces()
|
||||||
|
int done = it.uploader.getDonePieces()
|
||||||
|
if (percent == 100)
|
||||||
|
done++
|
||||||
|
int percentTotal = -1
|
||||||
|
if (pieces != 0)
|
||||||
|
percentTotal = (done * 100) / pieces
|
||||||
|
long size = it.uploader.getTotalSize()
|
||||||
|
String totalSize = ""
|
||||||
|
if (size > 0)
|
||||||
|
totalSize = " of " + DataHelper.formatSize2Decimal(size, false) + "B"
|
||||||
|
String remotePieces = String.format("%02d", percentTotal) + "% ${totalSize} ($done/$pieces) pcs".toString()
|
||||||
|
|
||||||
|
String speed = DataHelper.formatSize2Decimal(it.uploader.speed(), false) + "B/sec"
|
||||||
|
|
||||||
|
|
||||||
|
model.addRow([name, percentString, downloader, remotePieces, speed])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class UploaderWrapper {
|
||||||
|
Uploader uploader
|
||||||
|
boolean finished
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
uploader.getName()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,49 @@
|
|||||||
|
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.TextGUI
|
||||||
|
import com.googlecode.lanterna.gui2.Window
|
||||||
|
import com.googlecode.lanterna.gui2.table.Table
|
||||||
|
|
||||||
|
class UploadsView extends BasicWindow {
|
||||||
|
private final UploadsModel model
|
||||||
|
private final Table table
|
||||||
|
|
||||||
|
UploadsView(UploadsModel model, TerminalSize terminalSize) {
|
||||||
|
this.model = model
|
||||||
|
|
||||||
|
setHints([Window.Hint.EXPANDED])
|
||||||
|
|
||||||
|
Panel contentPanel = new Panel()
|
||||||
|
contentPanel.setLayoutManager(new GridLayout(1))
|
||||||
|
LayoutData layoutData = GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER, true, false)
|
||||||
|
|
||||||
|
table = new Table("Name","Progress","Downloader","Remote Pieces","Speed")
|
||||||
|
table.setCellSelection(false)
|
||||||
|
table.setTableModel(model.model)
|
||||||
|
table.setVisibleRows(terminalSize.getRows())
|
||||||
|
contentPanel.addComponent(table, layoutData)
|
||||||
|
|
||||||
|
Panel buttonsPanel = new Panel()
|
||||||
|
buttonsPanel.setLayoutManager(new GridLayout(2))
|
||||||
|
|
||||||
|
Button clearDoneButton = new Button("Clear Finished",{
|
||||||
|
model.uploaders.removeAll { it.finished }
|
||||||
|
})
|
||||||
|
Button closeButton = new Button("Close",{close()})
|
||||||
|
|
||||||
|
buttonsPanel.addComponent(clearDoneButton, layoutData)
|
||||||
|
buttonsPanel.addComponent(closeButton, layoutData)
|
||||||
|
|
||||||
|
contentPanel.addComponent(buttonsPanel, layoutData)
|
||||||
|
|
||||||
|
setComponent(contentPanel)
|
||||||
|
closeButton.takeFocus()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,39 @@
|
|||||||
|
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
|
||||||
|
import com.muwire.core.SharedFile
|
||||||
|
import com.muwire.core.search.UIResultEvent
|
||||||
|
import com.muwire.core.util.DataUtil
|
||||||
|
|
||||||
|
import net.i2p.data.Base64
|
||||||
|
|
||||||
|
class ViewCommentView extends BasicWindow {
|
||||||
|
private final TextBox textBox
|
||||||
|
private final LayoutData layoutData = GridLayout.createLayoutData(Alignment.CENTER, Alignment.CENTER)
|
||||||
|
|
||||||
|
ViewCommentView(UIResultEvent result, TerminalSize terminalSize) {
|
||||||
|
super("View Comments For "+result.getName())
|
||||||
|
|
||||||
|
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, result.comment, TextBox.Style.MULTI_LINE)
|
||||||
|
contentPanel.addComponent(textBox, layoutData)
|
||||||
|
|
||||||
|
Button closeButton = new Button("Close", {close()})
|
||||||
|
contentPanel.addComponent(closeButton, layoutData)
|
||||||
|
|
||||||
|
setComponent(contentPanel)
|
||||||
|
}
|
||||||
|
}
|
@@ -35,7 +35,7 @@ class Cli {
|
|||||||
|
|
||||||
Core core
|
Core core
|
||||||
try {
|
try {
|
||||||
core = new Core(props, home, "0.5.2")
|
core = new Core(props, home, "0.5.3")
|
||||||
} catch (Exception bad) {
|
} catch (Exception bad) {
|
||||||
bad.printStackTrace(System.out)
|
bad.printStackTrace(System.out)
|
||||||
println "Failed to initialize core, exiting"
|
println "Failed to initialize core, exiting"
|
||||||
|
@@ -53,7 +53,7 @@ class CliDownloader {
|
|||||||
|
|
||||||
Core core
|
Core core
|
||||||
try {
|
try {
|
||||||
core = new Core(props, home, "0.5.2")
|
core = new Core(props, home, "0.5.3")
|
||||||
} catch (Exception bad) {
|
} catch (Exception bad) {
|
||||||
bad.printStackTrace(System.out)
|
bad.printStackTrace(System.out)
|
||||||
println "Failed to initialize core, exiting"
|
println "Failed to initialize core, exiting"
|
||||||
|
@@ -28,10 +28,12 @@ import com.muwire.core.files.FileSharedEvent
|
|||||||
import com.muwire.core.files.FileUnsharedEvent
|
import com.muwire.core.files.FileUnsharedEvent
|
||||||
import com.muwire.core.files.HasherService
|
import com.muwire.core.files.HasherService
|
||||||
import com.muwire.core.files.PersisterService
|
import com.muwire.core.files.PersisterService
|
||||||
|
import com.muwire.core.files.SideCarFileEvent
|
||||||
import com.muwire.core.files.UICommentEvent
|
import com.muwire.core.files.UICommentEvent
|
||||||
import com.muwire.core.files.UIPersistFilesEvent
|
import com.muwire.core.files.UIPersistFilesEvent
|
||||||
import com.muwire.core.files.AllFilesLoadedEvent
|
import com.muwire.core.files.AllFilesLoadedEvent
|
||||||
import com.muwire.core.files.DirectoryUnsharedEvent
|
import com.muwire.core.files.DirectoryUnsharedEvent
|
||||||
|
import com.muwire.core.files.DirectoryWatchedEvent
|
||||||
import com.muwire.core.files.DirectoryWatcher
|
import com.muwire.core.files.DirectoryWatcher
|
||||||
import com.muwire.core.hostcache.CacheClient
|
import com.muwire.core.hostcache.CacheClient
|
||||||
import com.muwire.core.hostcache.HostCache
|
import com.muwire.core.hostcache.HostCache
|
||||||
@@ -102,6 +104,8 @@ public class Core {
|
|||||||
|
|
||||||
final AtomicBoolean shutdown = new AtomicBoolean()
|
final AtomicBoolean shutdown = new AtomicBoolean()
|
||||||
|
|
||||||
|
final SigningPrivateKey spk
|
||||||
|
|
||||||
public Core(MuWireSettings props, File home, String myVersion) {
|
public Core(MuWireSettings props, File home, String myVersion) {
|
||||||
this.home = home
|
this.home = home
|
||||||
this.muOptions = props
|
this.muOptions = props
|
||||||
@@ -178,7 +182,7 @@ public class Core {
|
|||||||
i2pSession = socketManager.getSession()
|
i2pSession = socketManager.getSession()
|
||||||
|
|
||||||
def destination = new Destination()
|
def destination = new Destination()
|
||||||
def spk = new SigningPrivateKey(Constants.SIG_TYPE)
|
spk = new SigningPrivateKey(Constants.SIG_TYPE)
|
||||||
keyDat.withInputStream {
|
keyDat.withInputStream {
|
||||||
destination.readBytes(it)
|
destination.readBytes(it)
|
||||||
def privateKey = new PrivateKey()
|
def privateKey = new PrivateKey()
|
||||||
@@ -189,8 +193,9 @@ public class Core {
|
|||||||
def baos = new ByteArrayOutputStream()
|
def baos = new ByteArrayOutputStream()
|
||||||
def daos = new DataOutputStream(baos)
|
def daos = new DataOutputStream(baos)
|
||||||
daos.write(Constants.PERSONA_VERSION)
|
daos.write(Constants.PERSONA_VERSION)
|
||||||
daos.writeShort((short)props.getNickname().length())
|
byte [] name = props.getNickname().getBytes(StandardCharsets.UTF_8)
|
||||||
daos.write(props.getNickname().getBytes(StandardCharsets.UTF_8))
|
daos.writeShort((short)name.length)
|
||||||
|
daos.write(name)
|
||||||
destination.writeBytes(daos)
|
destination.writeBytes(daos)
|
||||||
daos.flush()
|
daos.flush()
|
||||||
byte [] payload = baos.toByteArray()
|
byte [] payload = baos.toByteArray()
|
||||||
@@ -220,6 +225,7 @@ public class Core {
|
|||||||
eventBus.register(SearchEvent.class, fileManager)
|
eventBus.register(SearchEvent.class, fileManager)
|
||||||
eventBus.register(DirectoryUnsharedEvent.class, fileManager)
|
eventBus.register(DirectoryUnsharedEvent.class, fileManager)
|
||||||
eventBus.register(UICommentEvent.class, fileManager)
|
eventBus.register(UICommentEvent.class, fileManager)
|
||||||
|
eventBus.register(SideCarFileEvent.class, fileManager)
|
||||||
|
|
||||||
log.info("initializing mesh manager")
|
log.info("initializing mesh manager")
|
||||||
MeshManager meshManager = new MeshManager(fileManager, home, props)
|
MeshManager meshManager = new MeshManager(fileManager, home, props)
|
||||||
@@ -275,7 +281,7 @@ public class Core {
|
|||||||
eventBus.register(UIDownloadResumedEvent.class, downloadManager)
|
eventBus.register(UIDownloadResumedEvent.class, downloadManager)
|
||||||
|
|
||||||
log.info("initializing upload manager")
|
log.info("initializing upload manager")
|
||||||
uploadManager = new UploadManager(eventBus, fileManager, meshManager, downloadManager)
|
uploadManager = new UploadManager(eventBus, fileManager, meshManager, downloadManager, props)
|
||||||
|
|
||||||
log.info("initializing connection establisher")
|
log.info("initializing connection establisher")
|
||||||
connectionEstablisher = new ConnectionEstablisher(eventBus, i2pConnector, props, connectionManager, hostCache)
|
connectionEstablisher = new ConnectionEstablisher(eventBus, i2pConnector, props, connectionManager, hostCache)
|
||||||
@@ -287,7 +293,7 @@ public class Core {
|
|||||||
|
|
||||||
log.info("initializing directory watcher")
|
log.info("initializing directory watcher")
|
||||||
directoryWatcher = new DirectoryWatcher(eventBus, fileManager, home, props)
|
directoryWatcher = new DirectoryWatcher(eventBus, fileManager, home, props)
|
||||||
eventBus.register(FileSharedEvent.class, directoryWatcher)
|
eventBus.register(DirectoryWatchedEvent.class, directoryWatcher)
|
||||||
eventBus.register(AllFilesLoadedEvent.class, directoryWatcher)
|
eventBus.register(AllFilesLoadedEvent.class, directoryWatcher)
|
||||||
eventBus.register(DirectoryUnsharedEvent.class, directoryWatcher)
|
eventBus.register(DirectoryUnsharedEvent.class, directoryWatcher)
|
||||||
|
|
||||||
@@ -331,6 +337,8 @@ public class Core {
|
|||||||
log.info("already shutting down")
|
log.info("already shutting down")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
log.info("saving settings")
|
||||||
|
saveMuSettings()
|
||||||
log.info("shutting down trust subscriber")
|
log.info("shutting down trust subscriber")
|
||||||
trustSubscriber.stop()
|
trustSubscriber.stop()
|
||||||
log.info("shutting down download manageer")
|
log.info("shutting down download manageer")
|
||||||
@@ -349,6 +357,12 @@ public class Core {
|
|||||||
log.info("shutting down embedded router")
|
log.info("shutting down embedded router")
|
||||||
router.shutdown(0)
|
router.shutdown(0)
|
||||||
}
|
}
|
||||||
|
log.info("shutdown complete")
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveMuSettings() {
|
||||||
|
File f = new File(home, "MuWire.properties")
|
||||||
|
f.withPrintWriter("UTF-8", { muOptions.write(it) })
|
||||||
}
|
}
|
||||||
|
|
||||||
static main(args) {
|
static main(args) {
|
||||||
@@ -375,7 +389,7 @@ public class Core {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Core core = new Core(props, home, "0.5.2")
|
Core core = new Core(props, home, "0.5.7")
|
||||||
core.startServices()
|
core.startServices()
|
||||||
|
|
||||||
// ... at the end, sleep or execute script
|
// ... at the end, sleep or execute script
|
||||||
|
@@ -17,6 +17,8 @@ class MuWireSettings {
|
|||||||
int trustListInterval
|
int trustListInterval
|
||||||
Set<Persona> trustSubscriptions
|
Set<Persona> trustSubscriptions
|
||||||
int downloadRetryInterval
|
int downloadRetryInterval
|
||||||
|
int totalUploadSlots
|
||||||
|
int uploadSlotsPerUser
|
||||||
int updateCheckInterval
|
int updateCheckInterval
|
||||||
boolean autoDownloadUpdate
|
boolean autoDownloadUpdate
|
||||||
String updateType
|
String updateType
|
||||||
@@ -37,6 +39,7 @@ class MuWireSettings {
|
|||||||
int inBw, outBw
|
int inBw, outBw
|
||||||
Set<String> watchedKeywords
|
Set<String> watchedKeywords
|
||||||
Set<String> watchedRegexes
|
Set<String> watchedRegexes
|
||||||
|
Set<String> negativeFileTree
|
||||||
|
|
||||||
MuWireSettings() {
|
MuWireSettings() {
|
||||||
this(new Properties())
|
this(new Properties())
|
||||||
@@ -72,10 +75,13 @@ class MuWireSettings {
|
|||||||
searchComments = Boolean.valueOf(props.getProperty("searchComments","true"))
|
searchComments = Boolean.valueOf(props.getProperty("searchComments","true"))
|
||||||
browseFiles = Boolean.valueOf(props.getProperty("browseFiles","true"))
|
browseFiles = Boolean.valueOf(props.getProperty("browseFiles","true"))
|
||||||
speedSmoothSeconds = Integer.valueOf(props.getProperty("speedSmoothSeconds","60"))
|
speedSmoothSeconds = Integer.valueOf(props.getProperty("speedSmoothSeconds","60"))
|
||||||
|
totalUploadSlots = Integer.valueOf(props.getProperty("totalUploadSlots","-1"))
|
||||||
|
uploadSlotsPerUser = Integer.valueOf(props.getProperty("uploadSlotsPerUser","-1"))
|
||||||
|
|
||||||
watchedDirectories = readEncodedSet(props, "watchedDirectories")
|
watchedDirectories = DataUtil.readEncodedSet(props, "watchedDirectories")
|
||||||
watchedKeywords = readEncodedSet(props, "watchedKeywords")
|
watchedKeywords = DataUtil.readEncodedSet(props, "watchedKeywords")
|
||||||
watchedRegexes = readEncodedSet(props, "watchedRegexes")
|
watchedRegexes = DataUtil.readEncodedSet(props, "watchedRegexes")
|
||||||
|
negativeFileTree = DataUtil.readEncodedSet(props, "negativeFileTree")
|
||||||
|
|
||||||
trustSubscriptions = new HashSet<>()
|
trustSubscriptions = new HashSet<>()
|
||||||
if (props.containsKey("trustSubscriptions")) {
|
if (props.containsKey("trustSubscriptions")) {
|
||||||
@@ -87,7 +93,7 @@ class MuWireSettings {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void write(OutputStream out) throws IOException {
|
void write(Writer out) throws IOException {
|
||||||
Properties props = new Properties()
|
Properties props = new Properties()
|
||||||
props.setProperty("leaf", isLeaf.toString())
|
props.setProperty("leaf", isLeaf.toString())
|
||||||
props.setProperty("allowUntrusted", allowUntrusted.toString())
|
props.setProperty("allowUntrusted", allowUntrusted.toString())
|
||||||
@@ -116,10 +122,13 @@ class MuWireSettings {
|
|||||||
props.setProperty("searchComments", String.valueOf(searchComments))
|
props.setProperty("searchComments", String.valueOf(searchComments))
|
||||||
props.setProperty("browseFiles", String.valueOf(browseFiles))
|
props.setProperty("browseFiles", String.valueOf(browseFiles))
|
||||||
props.setProperty("speedSmoothSeconds", String.valueOf(speedSmoothSeconds))
|
props.setProperty("speedSmoothSeconds", String.valueOf(speedSmoothSeconds))
|
||||||
|
props.setProperty("totalUploadSlots", String.valueOf(totalUploadSlots))
|
||||||
|
props.setProperty("uploadSlotsPerUser", String.valueOf(uploadSlotsPerUser))
|
||||||
|
|
||||||
writeEncodedSet(watchedDirectories, "watchedDirectories", props)
|
DataUtil.writeEncodedSet(watchedDirectories, "watchedDirectories", props)
|
||||||
writeEncodedSet(watchedKeywords, "watchedKeywords", props)
|
DataUtil.writeEncodedSet(watchedKeywords, "watchedKeywords", props)
|
||||||
writeEncodedSet(watchedRegexes, "watchedRegexes", props)
|
DataUtil.writeEncodedSet(watchedRegexes, "watchedRegexes", props)
|
||||||
|
DataUtil.writeEncodedSet(negativeFileTree, "negativeFileTree", props)
|
||||||
|
|
||||||
if (!trustSubscriptions.isEmpty()) {
|
if (!trustSubscriptions.isEmpty()) {
|
||||||
String encoded = trustSubscriptions.stream().
|
String encoded = trustSubscriptions.stream().
|
||||||
@@ -128,25 +137,7 @@ class MuWireSettings {
|
|||||||
props.setProperty("trustSubscriptions", encoded)
|
props.setProperty("trustSubscriptions", encoded)
|
||||||
}
|
}
|
||||||
|
|
||||||
props.store(out, "")
|
props.store(out, "This file is UTF-8")
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isLeaf() {
|
boolean isLeaf() {
|
||||||
|
@@ -22,8 +22,9 @@ public class Name {
|
|||||||
|
|
||||||
public void write(OutputStream out) throws IOException {
|
public void write(OutputStream out) throws IOException {
|
||||||
DataOutputStream dos = new DataOutputStream(out)
|
DataOutputStream dos = new DataOutputStream(out)
|
||||||
dos.writeShort(name.length())
|
byte [] bytes = name.getBytes(StandardCharsets.UTF_8)
|
||||||
dos.write(name.getBytes(StandardCharsets.UTF_8))
|
dos.writeShort(bytes.length)
|
||||||
|
dos.write(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
public getName() {
|
public getName() {
|
||||||
|
@@ -1,10 +1,17 @@
|
|||||||
package com.muwire.core.connection
|
package com.muwire.core.connection
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
import java.util.concurrent.BlockingQueue
|
import java.util.concurrent.BlockingQueue
|
||||||
|
import java.util.concurrent.CountDownLatch
|
||||||
|
import java.util.concurrent.ExecutorService
|
||||||
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.LinkedBlockingQueue
|
import java.util.concurrent.LinkedBlockingQueue
|
||||||
|
import java.util.concurrent.ThreadFactory
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import java.util.logging.Level
|
import java.util.logging.Level
|
||||||
|
|
||||||
|
import com.muwire.core.Constants
|
||||||
import com.muwire.core.EventBus
|
import com.muwire.core.EventBus
|
||||||
import com.muwire.core.MuWireSettings
|
import com.muwire.core.MuWireSettings
|
||||||
import com.muwire.core.Persona
|
import com.muwire.core.Persona
|
||||||
@@ -16,8 +23,10 @@ import com.muwire.core.trust.TrustLevel
|
|||||||
import com.muwire.core.trust.TrustService
|
import com.muwire.core.trust.TrustService
|
||||||
|
|
||||||
import groovy.util.logging.Log
|
import groovy.util.logging.Log
|
||||||
|
import net.i2p.crypto.DSAEngine
|
||||||
import net.i2p.data.Base64
|
import net.i2p.data.Base64
|
||||||
import net.i2p.data.Destination
|
import net.i2p.data.Destination
|
||||||
|
import net.i2p.data.Signature
|
||||||
|
|
||||||
@Log
|
@Log
|
||||||
abstract class Connection implements Closeable {
|
abstract class Connection implements Closeable {
|
||||||
@@ -83,6 +92,7 @@ abstract class Connection implements Closeable {
|
|||||||
reader.interrupt()
|
reader.interrupt()
|
||||||
writer.interrupt()
|
writer.interrupt()
|
||||||
endpoint.close()
|
endpoint.close()
|
||||||
|
log.info("closed $name")
|
||||||
eventBus.publish(new DisconnectionEvent(destination: endpoint.destination))
|
eventBus.publish(new DisconnectionEvent(destination: endpoint.destination))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,6 +101,7 @@ abstract class Connection implements Closeable {
|
|||||||
while(running.get()) {
|
while(running.get()) {
|
||||||
read()
|
read()
|
||||||
}
|
}
|
||||||
|
} catch (InterruptedException ok) {
|
||||||
} catch (SocketTimeoutException e) {
|
} catch (SocketTimeoutException e) {
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.log(Level.WARNING,"unhandled exception in reader",e)
|
log.log(Level.WARNING,"unhandled exception in reader",e)
|
||||||
@@ -107,6 +118,7 @@ abstract class Connection implements Closeable {
|
|||||||
def message = messages.take()
|
def message = messages.take()
|
||||||
write(message)
|
write(message)
|
||||||
}
|
}
|
||||||
|
} catch (InterruptedException ok) {
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.log(Level.WARNING, "unhandled exception in writer",e)
|
log.log(Level.WARNING, "unhandled exception in writer",e)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -139,6 +151,8 @@ abstract class Connection implements Closeable {
|
|||||||
query.replyTo = e.replyTo.toBase64()
|
query.replyTo = e.replyTo.toBase64()
|
||||||
if (e.originator != null)
|
if (e.originator != null)
|
||||||
query.originator = e.originator.toBase64()
|
query.originator = e.originator.toBase64()
|
||||||
|
if (e.sig != null)
|
||||||
|
query.sig = Base64.encode(e.sig)
|
||||||
messages.put(query)
|
messages.put(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,6 +231,24 @@ abstract class Connection implements Closeable {
|
|||||||
boolean compressedResults = false
|
boolean compressedResults = false
|
||||||
if (search.compressedResults != null)
|
if (search.compressedResults != null)
|
||||||
compressedResults = search.compressedResults
|
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,
|
SearchEvent searchEvent = new SearchEvent(searchTerms : search.keywords,
|
||||||
searchHash : infohash,
|
searchHash : infohash,
|
||||||
@@ -228,7 +260,8 @@ abstract class Connection implements Closeable {
|
|||||||
replyTo : replyTo,
|
replyTo : replyTo,
|
||||||
originator : originator,
|
originator : originator,
|
||||||
receivedOn : endpoint.destination,
|
receivedOn : endpoint.destination,
|
||||||
firstHop : search.firstHop )
|
firstHop : search.firstHop,
|
||||||
|
sig : sig )
|
||||||
eventBus.publish(event)
|
eventBus.publish(event)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -51,6 +51,8 @@ class ConnectionAcceptor {
|
|||||||
|
|
||||||
private volatile shutdown
|
private volatile shutdown
|
||||||
|
|
||||||
|
private volatile int browsed
|
||||||
|
|
||||||
ConnectionAcceptor(EventBus eventBus, UltrapeerConnectionManager manager,
|
ConnectionAcceptor(EventBus eventBus, UltrapeerConnectionManager manager,
|
||||||
MuWireSettings settings, I2PAcceptor acceptor, HostCache hostCache,
|
MuWireSettings settings, I2PAcceptor acceptor, HostCache hostCache,
|
||||||
TrustService trustService, SearchManager searchManager, UploadManager uploadManager,
|
TrustService trustService, SearchManager searchManager, UploadManager uploadManager,
|
||||||
@@ -339,6 +341,7 @@ class ConnectionAcceptor {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
browsed++
|
||||||
|
|
||||||
os.write("200 OK\r\n".getBytes(StandardCharsets.US_ASCII))
|
os.write("200 OK\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
|
|
||||||
@@ -349,6 +352,7 @@ class ConnectionAcceptor {
|
|||||||
DataOutputStream dos = new DataOutputStream(new GZIPOutputStream(os))
|
DataOutputStream dos = new DataOutputStream(new GZIPOutputStream(os))
|
||||||
JsonOutput jsonOutput = new JsonOutput()
|
JsonOutput jsonOutput = new JsonOutput()
|
||||||
sharedFiles.each {
|
sharedFiles.each {
|
||||||
|
it.hit()
|
||||||
def obj = ResultsSender.sharedFileToObj(it, false)
|
def obj = ResultsSender.sharedFileToObj(it, false)
|
||||||
def json = jsonOutput.toJson(obj)
|
def json = jsonOutput.toJson(obj)
|
||||||
dos.writeShort((short)json.length())
|
dos.writeShort((short)json.length())
|
||||||
|
@@ -62,7 +62,7 @@ class ConnectionEstablisher {
|
|||||||
void stop() {
|
void stop() {
|
||||||
timer.cancel()
|
timer.cancel()
|
||||||
executor.shutdownNow()
|
executor.shutdownNow()
|
||||||
closer.shutdown()
|
closer.shutdownNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
private void connectIfNeeded() {
|
private void connectIfNeeded() {
|
||||||
@@ -123,10 +123,12 @@ class ConnectionEstablisher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void fail(Endpoint endpoint) {
|
private void fail(Endpoint endpoint) {
|
||||||
closer.execute {
|
if (!closer.isShutdown()) {
|
||||||
endpoint.close()
|
closer.execute {
|
||||||
eventBus.publish(new ConnectionEvent(endpoint: endpoint, incoming: false, leaf: false, status: ConnectionAttemptStatus.FAILED))
|
endpoint.close()
|
||||||
} as Runnable
|
eventBus.publish(new ConnectionEvent(endpoint: endpoint, incoming: false, leaf: false, status: ConnectionAttemptStatus.FAILED))
|
||||||
|
} as Runnable
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readK(Endpoint e) {
|
private void readK(Endpoint e) {
|
||||||
|
@@ -36,9 +36,8 @@ abstract class ConnectionManager {
|
|||||||
timer.schedule({sendPings()} as TimerTask, 1000,1000)
|
timer.schedule({sendPings()} as TimerTask, 1000,1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() {
|
void shutdown() {
|
||||||
timer.cancel()
|
timer.cancel()
|
||||||
getConnections().each { it.close() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onTrustEvent(TrustEvent e) {
|
void onTrustEvent(TrustEvent e) {
|
||||||
@@ -62,8 +61,6 @@ abstract class ConnectionManager {
|
|||||||
|
|
||||||
abstract void onDisconnectionEvent(DisconnectionEvent e)
|
abstract void onDisconnectionEvent(DisconnectionEvent e)
|
||||||
|
|
||||||
abstract void shutdown()
|
|
||||||
|
|
||||||
protected void sendPings() {
|
protected void sendPings() {
|
||||||
final long now = System.currentTimeMillis()
|
final long now = System.currentTimeMillis()
|
||||||
getConnections().each {
|
getConnections().each {
|
||||||
|
@@ -31,9 +31,6 @@ class Endpoint implements Closeable {
|
|||||||
if (inputStream != null) {
|
if (inputStream != null) {
|
||||||
try {inputStream.close()} catch (Exception ignore) {}
|
try {inputStream.close()} catch (Exception ignore) {}
|
||||||
}
|
}
|
||||||
if (outputStream != null) {
|
|
||||||
try {outputStream.close()} catch (Exception ignore) {}
|
|
||||||
}
|
|
||||||
if (toClose != null) {
|
if (toClose != null) {
|
||||||
try {toClose.reset()} catch (Exception ignore) {}
|
try {toClose.reset()} catch (Exception ignore) {}
|
||||||
}
|
}
|
||||||
|
@@ -104,6 +104,7 @@ class UltrapeerConnectionManager extends ConnectionManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
void shutdown() {
|
void shutdown() {
|
||||||
|
super.shutdown()
|
||||||
peerConnections.values().stream().parallel().forEach({v -> v.close()})
|
peerConnections.values().stream().parallel().forEach({v -> v.close()})
|
||||||
leafConnections.values().stream().parallel().forEach({v -> v.close()})
|
leafConnections.values().stream().parallel().forEach({v -> v.close()})
|
||||||
peerConnections.clear()
|
peerConnections.clear()
|
||||||
|
@@ -12,6 +12,7 @@ import com.muwire.core.util.DataUtil
|
|||||||
import groovy.json.JsonBuilder
|
import groovy.json.JsonBuilder
|
||||||
import groovy.json.JsonOutput
|
import groovy.json.JsonOutput
|
||||||
import groovy.json.JsonSlurper
|
import groovy.json.JsonSlurper
|
||||||
|
import groovy.util.logging.Log
|
||||||
import net.i2p.data.Base64
|
import net.i2p.data.Base64
|
||||||
import net.i2p.data.Destination
|
import net.i2p.data.Destination
|
||||||
import net.i2p.util.ConcurrentHashSet
|
import net.i2p.util.ConcurrentHashSet
|
||||||
@@ -25,7 +26,9 @@ import com.muwire.core.UILoadedEvent
|
|||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.Executor
|
import java.util.concurrent.Executor
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
import java.util.logging.Level
|
||||||
|
|
||||||
|
@Log
|
||||||
public class DownloadManager {
|
public class DownloadManager {
|
||||||
|
|
||||||
private final EventBus eventBus
|
private final EventBus eventBus
|
||||||
@@ -135,22 +138,33 @@ public class DownloadManager {
|
|||||||
else
|
else
|
||||||
incompletes = new File(home, "incompletes")
|
incompletes = new File(home, "incompletes")
|
||||||
|
|
||||||
|
if (json.pieceSizePow2 == null || json.pieceSizePow2 == 0) {
|
||||||
|
log.warning("Skipping $file because pieceSizePow2=$json.pieceSizePow2")
|
||||||
|
return // skip this download as it's corrupt anyway
|
||||||
|
}
|
||||||
|
|
||||||
Pieces pieces = getPieces(infoHash, (long)json.length, json.pieceSizePow2, sequential)
|
Pieces pieces = getPieces(infoHash, (long)json.length, json.pieceSizePow2, sequential)
|
||||||
|
|
||||||
def downloader = new Downloader(eventBus, this, me, file, (long)json.length,
|
def downloader = new Downloader(eventBus, this, me, file, (long)json.length,
|
||||||
infoHash, json.pieceSizePow2, connector, destinations, incompletes, pieces)
|
infoHash, json.pieceSizePow2, connector, destinations, incompletes, pieces)
|
||||||
if (json.paused != null)
|
if (json.paused != null)
|
||||||
downloader.paused = json.paused
|
downloader.paused = json.paused
|
||||||
downloaders.put(infoHash, downloader)
|
|
||||||
downloader.readPieces()
|
try {
|
||||||
if (!downloader.paused)
|
downloader.readPieces()
|
||||||
downloader.download()
|
if (!downloader.paused)
|
||||||
eventBus.publish(new DownloadStartedEvent(downloader : downloader))
|
downloader.download()
|
||||||
|
downloaders.put(infoHash, downloader)
|
||||||
|
eventBus.publish(new DownloadStartedEvent(downloader : downloader))
|
||||||
|
} catch (IllegalArgumentException bad) {
|
||||||
|
log.log(Level.WARNING,"cannot start downloader, skipping", bad)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Pieces getPieces(InfoHash infoHash, long length, int pieceSizePow2, boolean sequential) {
|
private Pieces getPieces(InfoHash infoHash, long length, int pieceSizePow2, boolean sequential) {
|
||||||
int pieceSize = 0x1 << pieceSizePow2
|
long pieceSize = 0x1L << pieceSizePow2
|
||||||
int nPieces = (int)(length / pieceSize)
|
int nPieces = (int)(length / pieceSize)
|
||||||
if (length % pieceSize != 0)
|
if (length % pieceSize != 0)
|
||||||
nPieces++
|
nPieces++
|
||||||
|
@@ -21,6 +21,7 @@ import java.nio.file.Files
|
|||||||
import java.nio.file.StandardOpenOption
|
import java.nio.file.StandardOpenOption
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.security.NoSuchAlgorithmException
|
import java.security.NoSuchAlgorithmException
|
||||||
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
import java.util.logging.Level
|
import java.util.logging.Level
|
||||||
|
|
||||||
@Log
|
@Log
|
||||||
@@ -37,13 +38,12 @@ class DownloadSession {
|
|||||||
private final Set<Integer> available
|
private final Set<Integer> available
|
||||||
private final MessageDigest digest
|
private final MessageDigest digest
|
||||||
|
|
||||||
private long lastSpeedRead = System.currentTimeMillis()
|
private final AtomicLong dataSinceLastRead
|
||||||
private long dataSinceLastRead
|
|
||||||
|
|
||||||
private MappedByteBuffer mapped
|
private MappedByteBuffer mapped
|
||||||
|
|
||||||
DownloadSession(EventBus eventBus, String meB64, Pieces pieces, InfoHash infoHash, Endpoint endpoint, File file,
|
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.eventBus = eventBus
|
||||||
this.meB64 = meB64
|
this.meB64 = meB64
|
||||||
this.pieces = pieces
|
this.pieces = pieces
|
||||||
@@ -53,6 +53,7 @@ class DownloadSession {
|
|||||||
this.pieceSize = pieceSize
|
this.pieceSize = pieceSize
|
||||||
this.fileLength = fileLength
|
this.fileLength = fileLength
|
||||||
this.available = available
|
this.available = available
|
||||||
|
this.dataSinceLastRead = dataSinceLastRead
|
||||||
try {
|
try {
|
||||||
digest = MessageDigest.getInstance("SHA-256")
|
digest = MessageDigest.getInstance("SHA-256")
|
||||||
} catch (NoSuchAlgorithmException impossible) {
|
} catch (NoSuchAlgorithmException impossible) {
|
||||||
@@ -141,6 +142,8 @@ class DownloadSession {
|
|||||||
// parse X-Have if present
|
// parse X-Have if present
|
||||||
if (headers.containsKey("X-Have")) {
|
if (headers.containsKey("X-Have")) {
|
||||||
DataUtil.decodeXHave(headers["X-Have"]).each {
|
DataUtil.decodeXHave(headers["X-Have"]).each {
|
||||||
|
if (it >= pieces.nPieces)
|
||||||
|
throw new IOException("Invalid X-Have header, available piece $it/$pieces.nPieces")
|
||||||
available.add(it)
|
available.add(it)
|
||||||
}
|
}
|
||||||
if (!available.contains(piece))
|
if (!available.contains(piece))
|
||||||
@@ -188,7 +191,7 @@ class DownloadSession {
|
|||||||
throw new IOException()
|
throw new IOException()
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
mapped.put(tmp, 0, read)
|
mapped.put(tmp, 0, read)
|
||||||
dataSinceLastRead += read
|
dataSinceLastRead.addAndGet(read)
|
||||||
pieces.markPartial(piece, mapped.position())
|
pieces.markPartial(piece, mapped.position())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -220,13 +223,4 @@ class DownloadSession {
|
|||||||
return 0
|
return 0
|
||||||
mapped.position()
|
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.ExecutorService
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
import java.util.logging.Level
|
import java.util.logging.Level
|
||||||
|
|
||||||
import com.muwire.core.Constants
|
import com.muwire.core.Constants
|
||||||
@@ -61,10 +62,11 @@ public class Downloader {
|
|||||||
private final AtomicBoolean eventFired = new AtomicBoolean()
|
private final AtomicBoolean eventFired = new AtomicBoolean()
|
||||||
private boolean piecesFileClosed
|
private boolean piecesFileClosed
|
||||||
|
|
||||||
|
private final AtomicLong dataSinceLastRead = new AtomicLong(0)
|
||||||
|
private volatile long lastSpeedRead = System.currentTimeMillis()
|
||||||
private ArrayList speedArr = new ArrayList<Integer>()
|
private ArrayList speedArr = new ArrayList<Integer>()
|
||||||
private int speedPos = 0
|
private int speedPos = 0
|
||||||
private int speedAvg = 0
|
private int speedAvg = 0
|
||||||
private long timestamp = Instant.now().toEpochMilli()
|
|
||||||
|
|
||||||
public Downloader(EventBus eventBus, DownloadManager downloadManager,
|
public Downloader(EventBus eventBus, DownloadManager downloadManager,
|
||||||
Persona me, File file, long length, InfoHash infoHash,
|
Persona me, File file, long length, InfoHash infoHash,
|
||||||
@@ -139,10 +141,11 @@ public class Downloader {
|
|||||||
public int speed() {
|
public int speed() {
|
||||||
int currSpeed = 0
|
int currSpeed = 0
|
||||||
if (getCurrentState() == DownloadState.DOWNLOADING) {
|
if (getCurrentState() == DownloadState.DOWNLOADING) {
|
||||||
activeWorkers.values().each {
|
long dataRead = dataSinceLastRead.getAndSet(0)
|
||||||
if (it.currentState == WorkerState.DOWNLOADING)
|
long now = System.currentTimeMillis()
|
||||||
currSpeed += it.speed()
|
if (now > lastSpeedRead)
|
||||||
}
|
currSpeed = (int) (dataRead * 1000.0 / (now - lastSpeedRead))
|
||||||
|
lastSpeedRead = now
|
||||||
}
|
}
|
||||||
|
|
||||||
if (speedArr.size() != downloadManager.muSettings.speedSmoothSeconds) {
|
if (speedArr.size() != downloadManager.muSettings.speedSmoothSeconds) {
|
||||||
@@ -302,7 +305,7 @@ public class Downloader {
|
|||||||
boolean requestPerformed
|
boolean requestPerformed
|
||||||
while(!pieces.isComplete()) {
|
while(!pieces.isComplete()) {
|
||||||
currentSession = new DownloadSession(eventBus, me.toBase64(), pieces, getInfoHash(),
|
currentSession = new DownloadSession(eventBus, me.toBase64(), pieces, getInfoHash(),
|
||||||
endpoint, incompleteFile, pieceSize, length, available)
|
endpoint, incompleteFile, pieceSize, length, available, dataSinceLastRead)
|
||||||
requestPerformed = currentSession.request()
|
requestPerformed = currentSession.request()
|
||||||
if (!requestPerformed)
|
if (!requestPerformed)
|
||||||
break
|
break
|
||||||
@@ -339,12 +342,6 @@ public class Downloader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int speed() {
|
|
||||||
if (currentSession == null)
|
|
||||||
return 0
|
|
||||||
currentSession.speed()
|
|
||||||
}
|
|
||||||
|
|
||||||
void cancel() {
|
void cancel() {
|
||||||
downloadThread?.interrupt()
|
downloadThread?.interrupt()
|
||||||
}
|
}
|
||||||
|
@@ -75,6 +75,8 @@ class Pieces {
|
|||||||
}
|
}
|
||||||
|
|
||||||
synchronized void markDownloaded(int piece) {
|
synchronized void markDownloaded(int piece) {
|
||||||
|
if (piece >= nPieces)
|
||||||
|
throw new IllegalArgumentException("invalid piece marked as downloaded? $piece/$nPieces")
|
||||||
done.set(piece)
|
done.set(piece)
|
||||||
claimed.set(piece)
|
claimed.set(piece)
|
||||||
partials.remove(piece)
|
partials.remove(piece)
|
||||||
|
@@ -4,4 +4,8 @@ import com.muwire.core.Event
|
|||||||
|
|
||||||
class DirectoryUnsharedEvent extends Event {
|
class DirectoryUnsharedEvent extends Event {
|
||||||
File directory
|
File directory
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
super.toString() + " unshared directory "+ directory.toString()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,7 @@
|
|||||||
|
package com.muwire.core.files
|
||||||
|
|
||||||
|
import com.muwire.core.Event
|
||||||
|
|
||||||
|
class DirectoryWatchedEvent extends Event {
|
||||||
|
File directory
|
||||||
|
}
|
@@ -66,10 +66,8 @@ class DirectoryWatcher {
|
|||||||
watchService?.close()
|
watchService?.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFileSharedEvent(FileSharedEvent e) {
|
void onDirectoryWatchedEvent(DirectoryWatchedEvent e) {
|
||||||
if (!e.file.isDirectory())
|
File canonical = e.directory.getCanonicalFile()
|
||||||
return
|
|
||||||
File canonical = e.file.getCanonicalFile()
|
|
||||||
Path path = canonical.toPath()
|
Path path = canonical.toPath()
|
||||||
WatchKey wk = path.register(watchService, kinds)
|
WatchKey wk = path.register(watchService, kinds)
|
||||||
watchedDirectories.put(canonical, wk)
|
watchedDirectories.put(canonical, wk)
|
||||||
@@ -88,9 +86,9 @@ class DirectoryWatcher {
|
|||||||
|
|
||||||
private void saveMuSettings() {
|
private void saveMuSettings() {
|
||||||
File muSettingsFile = new File(home, "MuWire.properties")
|
File muSettingsFile = new File(home, "MuWire.properties")
|
||||||
muSettingsFile.withOutputStream {
|
muSettingsFile.withPrintWriter("UTF-8", {
|
||||||
muOptions.write(it)
|
muOptions.write(it)
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private void watch() {
|
private void watch() {
|
||||||
@@ -125,7 +123,8 @@ class DirectoryWatcher {
|
|||||||
private void processModified(Path parent, Path path) {
|
private void processModified(Path parent, Path path) {
|
||||||
File f = join(parent, path)
|
File f = join(parent, path)
|
||||||
log.fine("modified entry $f")
|
log.fine("modified entry $f")
|
||||||
waitingFiles.put(f, System.currentTimeMillis())
|
if (!fileManager.getNegativeTree().fileToNode.containsKey(f))
|
||||||
|
waitingFiles.put(f, System.currentTimeMillis())
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processDeleted(Path parent, Path path) {
|
private void processDeleted(Path parent, Path path) {
|
||||||
@@ -133,7 +132,7 @@ class DirectoryWatcher {
|
|||||||
log.fine("deleted entry $f")
|
log.fine("deleted entry $f")
|
||||||
SharedFile sf = fileManager.fileToSharedFile.get(f)
|
SharedFile sf = fileManager.fileToSharedFile.get(f)
|
||||||
if (sf != null)
|
if (sf != null)
|
||||||
eventBus.publish(new FileUnsharedEvent(unsharedFile : sf))
|
eventBus.publish(new FileUnsharedEvent(unsharedFile : sf, deleted : true))
|
||||||
}
|
}
|
||||||
|
|
||||||
private static File join(Path parent, Path path) {
|
private static File join(Path parent, Path path) {
|
||||||
|
@@ -13,8 +13,10 @@ import java.security.NoSuchAlgorithmException
|
|||||||
|
|
||||||
class FileHasher {
|
class FileHasher {
|
||||||
|
|
||||||
|
public static final int MIN_PIECE_SIZE_POW2 = 17
|
||||||
|
public static final int MAX_PIECE_SIZE_POW2 = 37
|
||||||
/** max size of shared file is 128 GB */
|
/** max size of shared file is 128 GB */
|
||||||
public static final long MAX_SIZE = 0x1L << 37
|
public static final long MAX_SIZE = 0x1L << MAX_PIECE_SIZE_POW2
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param size of the file to be shared
|
* @param size of the file to be shared
|
||||||
@@ -24,9 +26,9 @@ class FileHasher {
|
|||||||
*/
|
*/
|
||||||
static int getPieceSize(long size) {
|
static int getPieceSize(long size) {
|
||||||
if (size <= 0x1 << 30)
|
if (size <= 0x1 << 30)
|
||||||
return 17
|
return MIN_PIECE_SIZE_POW2
|
||||||
|
|
||||||
for (int i = 31; i <= 37; i++) {
|
for (int i = 31; i <= MAX_PIECE_SIZE_POW2; i++) {
|
||||||
if (size <= 0x1L << i) {
|
if (size <= 0x1L << i) {
|
||||||
return i-13
|
return i-13
|
||||||
}
|
}
|
||||||
@@ -48,27 +50,28 @@ class FileHasher {
|
|||||||
|
|
||||||
InfoHash hashFile(File file) {
|
InfoHash hashFile(File file) {
|
||||||
final long length = file.length()
|
final long length = file.length()
|
||||||
final int size = 0x1 << getPieceSize(length)
|
final long size = 0x1L << getPieceSize(length)
|
||||||
int numPieces = (int) (length / size)
|
int numPieces = (length / size).toInteger()
|
||||||
if (numPieces * size < length)
|
if (numPieces * size < length)
|
||||||
numPieces++
|
numPieces++
|
||||||
|
|
||||||
def output = new ByteArrayOutputStream()
|
def output = new ByteArrayOutputStream()
|
||||||
RandomAccessFile raf = new RandomAccessFile(file, "r")
|
RandomAccessFile raf = new RandomAccessFile(file, "r")
|
||||||
|
MappedByteBuffer buf = null
|
||||||
try {
|
try {
|
||||||
MappedByteBuffer buf
|
|
||||||
for (int i = 0; i < numPieces - 1; i++) {
|
for (int i = 0; i < numPieces - 1; i++) {
|
||||||
buf = raf.getChannel().map(MapMode.READ_ONLY, ((long)size) * i, size)
|
buf = raf.getChannel().map(MapMode.READ_ONLY, size * i, size.toInteger())
|
||||||
digest.update buf
|
digest.update buf
|
||||||
DataUtil.tryUnmap(buf)
|
DataUtil.tryUnmap(buf)
|
||||||
output.write(digest.digest(), 0, 32)
|
output.write(digest.digest(), 0, 32)
|
||||||
}
|
}
|
||||||
def lastPieceLength = length - (numPieces - 1) * ((long)size)
|
long lastPieceLength = length - (numPieces - 1) * size
|
||||||
buf = raf.getChannel().map(MapMode.READ_ONLY, length - lastPieceLength, lastPieceLength)
|
buf = raf.getChannel().map(MapMode.READ_ONLY, length - lastPieceLength, lastPieceLength.toInteger())
|
||||||
digest.update buf
|
digest.update buf
|
||||||
output.write(digest.digest(), 0, 32)
|
output.write(digest.digest(), 0, 32)
|
||||||
} finally {
|
} finally {
|
||||||
raf.close()
|
raf.close()
|
||||||
|
DataUtil.tryUnmap(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
byte [] hashList = output.toByteArray()
|
byte [] hashList = output.toByteArray()
|
||||||
|
@@ -24,15 +24,28 @@ class FileManager {
|
|||||||
final Map<String, Set<File>> nameToFiles = new HashMap<>()
|
final Map<String, Set<File>> nameToFiles = new HashMap<>()
|
||||||
final Map<String, Set<File>> commentToFile = new HashMap<>()
|
final Map<String, Set<File>> commentToFile = new HashMap<>()
|
||||||
final SearchIndex index = new SearchIndex()
|
final SearchIndex index = new SearchIndex()
|
||||||
|
final FileTree negativeTree = new FileTree()
|
||||||
|
final Set<File> sideCarFiles = new HashSet<>()
|
||||||
|
|
||||||
FileManager(EventBus eventBus, MuWireSettings settings) {
|
FileManager(EventBus eventBus, MuWireSettings settings) {
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
|
|
||||||
|
for (String negative : settings.negativeFileTree) {
|
||||||
|
negativeTree.add(new File(negative))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFileHashedEvent(FileHashedEvent e) {
|
void onFileHashedEvent(FileHashedEvent e) {
|
||||||
if (e.sharedFile != null)
|
if (e.sharedFile == null)
|
||||||
addToIndex(e.sharedFile)
|
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) {
|
void onFileLoadedEvent(FileLoadedEvent e) {
|
||||||
@@ -45,6 +58,21 @@ class FileManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
private void addToIndex(SharedFile sf) {
|
||||||
log.info("Adding shared file " + sf.getFile())
|
log.info("Adding shared file " + sf.getFile())
|
||||||
InfoHash infoHash = sf.getInfoHash()
|
InfoHash infoHash = sf.getInfoHash()
|
||||||
@@ -57,6 +85,13 @@ class FileManager {
|
|||||||
existing.add(sf)
|
existing.add(sf)
|
||||||
fileToSharedFile.put(sf.file, sf)
|
fileToSharedFile.put(sf.file, sf)
|
||||||
|
|
||||||
|
negativeTree.remove(sf.file)
|
||||||
|
String parent = sf.getFile().getParent()
|
||||||
|
if (parent != null && settings.watchedDirectories.contains(parent)) {
|
||||||
|
negativeTree.add(sf.file.getParentFile())
|
||||||
|
}
|
||||||
|
saveNegativeTree()
|
||||||
|
|
||||||
String name = sf.getFile().getName()
|
String name = sf.getFile().getName()
|
||||||
Set<File> existingFiles = nameToFiles.get(name)
|
Set<File> existingFiles = nameToFiles.get(name)
|
||||||
if (existingFiles == null) {
|
if (existingFiles == null) {
|
||||||
@@ -92,6 +127,10 @@ class FileManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fileToSharedFile.remove(sf.file)
|
fileToSharedFile.remove(sf.file)
|
||||||
|
if (!e.deleted && negativeTree.fileToNode.containsKey(sf.file.getParentFile())) {
|
||||||
|
negativeTree.add(sf.file)
|
||||||
|
saveNegativeTree()
|
||||||
|
}
|
||||||
|
|
||||||
String name = sf.getFile().getName()
|
String name = sf.getFile().getName()
|
||||||
Set<File> existingFiles = nameToFiles.get(name)
|
Set<File> existingFiles = nameToFiles.get(name)
|
||||||
@@ -158,8 +197,10 @@ class FileManager {
|
|||||||
Set<SharedFile> found
|
Set<SharedFile> found
|
||||||
found = rootToFiles.get new InfoHash(e.searchHash)
|
found = rootToFiles.get new InfoHash(e.searchHash)
|
||||||
found = filter(found, e.oobInfohash)
|
found = filter(found, e.oobInfohash)
|
||||||
if (found != null && !found.isEmpty())
|
if (found != null && !found.isEmpty()) {
|
||||||
|
found.each { it.hit() }
|
||||||
re = new ResultsEvent(results: found.asList(), uuid: e.uuid, searchEvent: e)
|
re = new ResultsEvent(results: found.asList(), uuid: e.uuid, searchEvent: e)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
def names = index.search e.searchTerms
|
def names = index.search e.searchTerms
|
||||||
Set<File> files = new HashSet<>()
|
Set<File> files = new HashSet<>()
|
||||||
@@ -172,8 +213,10 @@ class FileManager {
|
|||||||
files.each { sharedFiles.add fileToSharedFile[it] }
|
files.each { sharedFiles.add fileToSharedFile[it] }
|
||||||
files = filter(sharedFiles, e.oobInfohash)
|
files = filter(sharedFiles, e.oobInfohash)
|
||||||
|
|
||||||
if (!sharedFiles.isEmpty())
|
if (!sharedFiles.isEmpty()) {
|
||||||
|
sharedFiles.each { it.hit() }
|
||||||
re = new ResultsEvent(results: sharedFiles.asList(), uuid: e.uuid, searchEvent: e)
|
re = new ResultsEvent(results: sharedFiles.asList(), uuid: e.uuid, searchEvent: e)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,6 +236,8 @@ class FileManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onDirectoryUnsharedEvent(DirectoryUnsharedEvent e) {
|
void onDirectoryUnsharedEvent(DirectoryUnsharedEvent e) {
|
||||||
|
negativeTree.remove(e.directory)
|
||||||
|
saveNegativeTree()
|
||||||
e.directory.listFiles().each {
|
e.directory.listFiles().each {
|
||||||
if (it.isDirectory())
|
if (it.isDirectory())
|
||||||
eventBus.publish(new DirectoryUnsharedEvent(directory : it))
|
eventBus.publish(new DirectoryUnsharedEvent(directory : it))
|
||||||
@@ -203,4 +248,9 @@ class FileManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void saveNegativeTree() {
|
||||||
|
settings.negativeFileTree.clear()
|
||||||
|
settings.negativeFileTree.addAll(negativeTree.fileToNode.keySet().collect { it.getAbsolutePath() })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
64
core/src/main/groovy/com/muwire/core/files/FileTree.groovy
Normal file
64
core/src/main/groovy/com/muwire/core/files/FileTree.groovy
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package com.muwire.core.files
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
|
class FileTree {
|
||||||
|
|
||||||
|
private final TreeNode root = new TreeNode()
|
||||||
|
private final Map<File, TreeNode> fileToNode = new ConcurrentHashMap<>()
|
||||||
|
|
||||||
|
void add(File file) {
|
||||||
|
List<File> path = new ArrayList<>()
|
||||||
|
path.add(file)
|
||||||
|
while (file.getParentFile() != null) {
|
||||||
|
path.add(file.getParentFile())
|
||||||
|
file = file.getParentFile()
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.reverse(path)
|
||||||
|
|
||||||
|
TreeNode current = root
|
||||||
|
for (File element : path) {
|
||||||
|
TreeNode existing = fileToNode.get(element)
|
||||||
|
if (existing == null) {
|
||||||
|
existing = new TreeNode()
|
||||||
|
existing.file = element
|
||||||
|
existing.parent = current
|
||||||
|
fileToNode.put(element, existing)
|
||||||
|
current.children.add(existing)
|
||||||
|
}
|
||||||
|
current = existing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean remove(File file) {
|
||||||
|
TreeNode node = fileToNode.remove(file)
|
||||||
|
if (node == null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
node.parent.children.remove(node)
|
||||||
|
if (node.parent.children.isEmpty() && node.parent != root)
|
||||||
|
remove(node.parent.file)
|
||||||
|
def copy = new ArrayList(node.children)
|
||||||
|
for (TreeNode child : copy)
|
||||||
|
remove(child.file)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TreeNode {
|
||||||
|
TreeNode parent
|
||||||
|
File file
|
||||||
|
final Set<TreeNode> children = new HashSet<>()
|
||||||
|
|
||||||
|
public int hashCode() {
|
||||||
|
file.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (!(o instanceof TreeNode))
|
||||||
|
return false
|
||||||
|
TreeNode other = (TreeNode)o
|
||||||
|
file == other.file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -5,4 +5,5 @@ import com.muwire.core.SharedFile
|
|||||||
|
|
||||||
class FileUnsharedEvent extends Event {
|
class FileUnsharedEvent extends Event {
|
||||||
SharedFile unsharedFile
|
SharedFile unsharedFile
|
||||||
|
boolean deleted
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@ package com.muwire.core.files
|
|||||||
import java.util.concurrent.Executor
|
import java.util.concurrent.Executor
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
|
import com.muwire.core.Constants
|
||||||
import com.muwire.core.EventBus
|
import com.muwire.core.EventBus
|
||||||
import com.muwire.core.MuWireSettings
|
import com.muwire.core.MuWireSettings
|
||||||
import com.muwire.core.SharedFile
|
import com.muwire.core.SharedFile
|
||||||
@@ -33,7 +34,12 @@ class HasherService {
|
|||||||
return
|
return
|
||||||
if (fileManager.fileToSharedFile.containsKey(canonical))
|
if (fileManager.fileToSharedFile.containsKey(canonical))
|
||||||
return
|
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)
|
executor.execute( { -> process(canonical) } as Runnable)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +53,10 @@ class HasherService {
|
|||||||
|
|
||||||
private void process(File f) {
|
private void process(File f) {
|
||||||
if (f.isDirectory()) {
|
if (f.isDirectory()) {
|
||||||
f.listFiles().each {eventBus.publish new FileSharedEvent(file: it) }
|
eventBus.publish(new DirectoryWatchedEvent(directory : f))
|
||||||
|
f.listFiles().each {
|
||||||
|
eventBus.publish new FileSharedEvent(file: it)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (f.length() == 0) {
|
if (f.length() == 0) {
|
||||||
eventBus.publish new FileHashedEvent(error: "Not sharing empty file $f")
|
eventBus.publish new FileHashedEvent(error: "Not sharing empty file $f")
|
||||||
|
@@ -82,7 +82,7 @@ class PersisterService extends Service {
|
|||||||
} else {
|
} else {
|
||||||
listener.publish(new AllFilesLoadedEvent())
|
listener.publish(new AllFilesLoadedEvent())
|
||||||
}
|
}
|
||||||
timer.schedule({persistFiles()} as TimerTask, 0, interval)
|
timer.schedule({persistFiles()} as TimerTask, 1000, interval)
|
||||||
loaded = true
|
loaded = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,9 +129,15 @@ class PersisterService extends Service {
|
|||||||
return new FileLoadedEvent(loadedFile : df)
|
return new FileLoadedEvent(loadedFile : df)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int hits = 0
|
||||||
|
if (json.hits != null)
|
||||||
|
hits = json.hits
|
||||||
|
|
||||||
SharedFile sf = new SharedFile(file, ih, pieceSize)
|
SharedFile sf = new SharedFile(file, ih, pieceSize)
|
||||||
sf.setComment(json.comment)
|
sf.setComment(json.comment)
|
||||||
|
sf.hits = hits
|
||||||
|
if (json.downloaders != null)
|
||||||
|
sf.getDownloaders().addAll(json.downloaders)
|
||||||
return new FileLoadedEvent(loadedFile: sf)
|
return new FileLoadedEvent(loadedFile: sf)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -163,6 +169,8 @@ class PersisterService extends Service {
|
|||||||
json.pieceSize = sf.getPieceSize()
|
json.pieceSize = sf.getPieceSize()
|
||||||
json.hashList = sf.getB64EncodedHashList()
|
json.hashList = sf.getB64EncodedHashList()
|
||||||
json.comment = sf.getComment()
|
json.comment = sf.getComment()
|
||||||
|
json.hits = sf.getHits()
|
||||||
|
json.downloaders = sf.getDownloaders()
|
||||||
|
|
||||||
if (sf instanceof DownloadedFile) {
|
if (sf instanceof DownloadedFile) {
|
||||||
json.sources = sf.sources.stream().map( {d -> d.toBase64()}).collect(Collectors.toList())
|
json.sources = sf.sources.stream().map( {d -> d.toBase64()}).collect(Collectors.toList())
|
||||||
|
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
@@ -1,5 +1,6 @@
|
|||||||
package com.muwire.core.mesh
|
package com.muwire.core.mesh
|
||||||
|
|
||||||
|
import java.util.logging.Level
|
||||||
import java.util.stream.Collectors
|
import java.util.stream.Collectors
|
||||||
|
|
||||||
import com.muwire.core.Constants
|
import com.muwire.core.Constants
|
||||||
@@ -13,8 +14,10 @@ import com.muwire.core.util.DataUtil
|
|||||||
|
|
||||||
import groovy.json.JsonOutput
|
import groovy.json.JsonOutput
|
||||||
import groovy.json.JsonSlurper
|
import groovy.json.JsonSlurper
|
||||||
|
import groovy.util.logging.Log
|
||||||
import net.i2p.data.Base64
|
import net.i2p.data.Base64
|
||||||
|
|
||||||
|
@Log
|
||||||
class MeshManager {
|
class MeshManager {
|
||||||
|
|
||||||
private final Map<InfoHash, Mesh> meshes = Collections.synchronizedMap(new HashMap<>())
|
private final Map<InfoHash, Mesh> meshes = Collections.synchronizedMap(new HashMap<>())
|
||||||
@@ -67,7 +70,10 @@ class MeshManager {
|
|||||||
json.infoHash = Base64.encode(mesh.infoHash.getRoot())
|
json.infoHash = Base64.encode(mesh.infoHash.getRoot())
|
||||||
json.sources = mesh.sources.stream().map({it.toBase64()}).collect(Collectors.toList())
|
json.sources = mesh.sources.stream().map({it.toBase64()}).collect(Collectors.toList())
|
||||||
json.nPieces = mesh.pieces.nPieces
|
json.nPieces = mesh.pieces.nPieces
|
||||||
json.xHave = DataUtil.encodeXHave(mesh.pieces.downloaded, mesh.pieces.nPieces)
|
List<Integer> downloaded = mesh.pieces.getDownloaded()
|
||||||
|
if( downloaded.size() > mesh.pieces.nPieces)
|
||||||
|
return
|
||||||
|
json.xHave = DataUtil.encodeXHave(downloaded, mesh.pieces.nPieces)
|
||||||
writer.println(JsonOutput.toJson(json))
|
writer.println(JsonOutput.toJson(json))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,6 +88,9 @@ class MeshManager {
|
|||||||
JsonSlurper slurper = new JsonSlurper()
|
JsonSlurper slurper = new JsonSlurper()
|
||||||
meshFile.eachLine {
|
meshFile.eachLine {
|
||||||
def json = slurper.parseText(it)
|
def json = slurper.parseText(it)
|
||||||
|
if (json.nPieces == null || json.nPieces == 0)
|
||||||
|
return // skip it, invalid
|
||||||
|
|
||||||
if (now - json.timestamp > settings.meshExpiration * 60 * 1000)
|
if (now - json.timestamp > settings.meshExpiration * 60 * 1000)
|
||||||
return
|
return
|
||||||
InfoHash infoHash = new InfoHash(Base64.decode(json.infoHash))
|
InfoHash infoHash = new InfoHash(Base64.decode(json.infoHash))
|
||||||
@@ -93,8 +102,13 @@ class MeshManager {
|
|||||||
mesh.sources.add(persona)
|
mesh.sources.add(persona)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (json.xHave != null)
|
if (json.xHave != null) {
|
||||||
DataUtil.decodeXHave(json.xHave).each { pieces.markDownloaded(it) }
|
try {
|
||||||
|
DataUtil.decodeXHave(json.xHave).each { pieces.markDownloaded(it) }
|
||||||
|
} catch (IllegalArgumentException bad) {
|
||||||
|
log.log(Level.WARNING, "couldn't parse XHave", bad)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!mesh.sources.isEmpty())
|
if (!mesh.sources.isEmpty())
|
||||||
meshes.put(infoHash, mesh)
|
meshes.put(infoHash, mesh)
|
||||||
|
@@ -12,6 +12,7 @@ class QueryEvent extends Event {
|
|||||||
Destination replyTo
|
Destination replyTo
|
||||||
Persona originator
|
Persona originator
|
||||||
Destination receivedOn
|
Destination receivedOn
|
||||||
|
byte[] sig
|
||||||
|
|
||||||
String toString() {
|
String toString() {
|
||||||
"searchEvent: $searchEvent firstHop:$firstHop, replyTo:${replyTo.toBase32()}" +
|
"searchEvent: $searchEvent firstHop:$firstHop, replyTo:${replyTo.toBase32()}" +
|
||||||
|
@@ -6,6 +6,7 @@ import javax.naming.directory.InvalidSearchControlsException
|
|||||||
|
|
||||||
import com.muwire.core.InfoHash
|
import com.muwire.core.InfoHash
|
||||||
import com.muwire.core.Persona
|
import com.muwire.core.Persona
|
||||||
|
import com.muwire.core.files.FileHasher
|
||||||
import com.muwire.core.util.DataUtil
|
import com.muwire.core.util.DataUtil
|
||||||
|
|
||||||
import net.i2p.data.Base64
|
import net.i2p.data.Base64
|
||||||
@@ -30,12 +31,12 @@ class ResultsParser {
|
|||||||
private static parseV1(Persona p, UUID uuid, def json) {
|
private static parseV1(Persona p, UUID uuid, def json) {
|
||||||
if (json.name == null)
|
if (json.name == null)
|
||||||
throw new InvalidSearchResultException("name missing")
|
throw new InvalidSearchResultException("name missing")
|
||||||
if (json.size == null)
|
if (json.size == null || json.size <= 0 || json.size > FileHasher.MAX_SIZE)
|
||||||
throw new InvalidSearchResultException("length missing")
|
throw new InvalidSearchResultException("length missing or invalid, $json.size")
|
||||||
if (json.infohash == null)
|
if (json.infohash == null)
|
||||||
throw new InvalidSearchResultException("infohash missing")
|
throw new InvalidSearchResultException("infohash missing")
|
||||||
if (json.pieceSize == null)
|
if (json.pieceSize == null || json.pieceSize < FileHasher.MIN_PIECE_SIZE_POW2 || json.pieceSize > FileHasher.MAX_PIECE_SIZE_POW2)
|
||||||
throw new InvalidSearchResultException("pieceSize missing")
|
throw new InvalidSearchResultException("pieceSize missing or invalid, $json.pieceSize")
|
||||||
if (!(json.hashList instanceof List))
|
if (!(json.hashList instanceof List))
|
||||||
throw new InvalidSearchResultException("hashlist not a list")
|
throw new InvalidSearchResultException("hashlist not a list")
|
||||||
try {
|
try {
|
||||||
@@ -71,12 +72,12 @@ class ResultsParser {
|
|||||||
private static UIResultEvent parseV2(Persona p, UUID uuid, def json) {
|
private static UIResultEvent parseV2(Persona p, UUID uuid, def json) {
|
||||||
if (json.name == null)
|
if (json.name == null)
|
||||||
throw new InvalidSearchResultException("name missing")
|
throw new InvalidSearchResultException("name missing")
|
||||||
if (json.size == null)
|
if (json.size == null || json.size <= 0 || json.size > FileHasher.MAX_SIZE)
|
||||||
throw new InvalidSearchResultException("length missing")
|
throw new InvalidSearchResultException("length missing or invalid $json.size")
|
||||||
if (json.infohash == null)
|
if (json.infohash == null)
|
||||||
throw new InvalidSearchResultException("infohash missing")
|
throw new InvalidSearchResultException("infohash missing")
|
||||||
if (json.pieceSize == null)
|
if (json.pieceSize == null || json.pieceSize < FileHasher.MIN_PIECE_SIZE_POW2 || json.pieceSize > FileHasher.MAX_PIECE_SIZE_POW2)
|
||||||
throw new InvalidSearchResultException("pieceSize missing")
|
throw new InvalidSearchResultException("pieceSize missing or invalid, $json.pieceSize")
|
||||||
if (json.hashList != null)
|
if (json.hashList != null)
|
||||||
throw new InvalidSearchResultException("V2 result with hashlist")
|
throw new InvalidSearchResultException("V2 result with hashlist")
|
||||||
try {
|
try {
|
||||||
|
@@ -7,4 +7,5 @@ class UpdateAvailableEvent extends Event {
|
|||||||
String version
|
String version
|
||||||
String signer
|
String signer
|
||||||
String infoHash
|
String infoHash
|
||||||
|
String text
|
||||||
}
|
}
|
||||||
|
@@ -41,6 +41,8 @@ class UpdateClient {
|
|||||||
private volatile String version, signer
|
private volatile String version, signer
|
||||||
private volatile boolean updateDownloading
|
private volatile boolean updateDownloading
|
||||||
|
|
||||||
|
private volatile String text
|
||||||
|
|
||||||
UpdateClient(EventBus eventBus, I2PSession session, String myVersion, MuWireSettings settings, FileManager fileManager, Persona me) {
|
UpdateClient(EventBus eventBus, I2PSession session, String myVersion, MuWireSettings settings, FileManager fileManager, Persona me) {
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.session = session
|
this.session = session
|
||||||
@@ -75,7 +77,7 @@ class UpdateClient {
|
|||||||
if (e.downloadedFile.infoHash != updateInfoHash)
|
if (e.downloadedFile.infoHash != updateInfoHash)
|
||||||
return
|
return
|
||||||
updateDownloading = false
|
updateDownloading = false
|
||||||
eventBus.publish(new UpdateDownloadedEvent(version : version, signer : signer))
|
eventBus.publish(new UpdateDownloadedEvent(version : version, signer : signer, text : text))
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkUpdate() {
|
private void checkUpdate() {
|
||||||
@@ -147,15 +149,16 @@ class UpdateClient {
|
|||||||
} else
|
} else
|
||||||
infoHash = payload[settings.updateType]
|
infoHash = payload[settings.updateType]
|
||||||
|
|
||||||
|
text = payload.text
|
||||||
|
|
||||||
if (!settings.autoDownloadUpdate) {
|
if (!settings.autoDownloadUpdate) {
|
||||||
log.info("new version $payload.version available, publishing event")
|
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 {
|
} else {
|
||||||
log.info("new version $payload.version available")
|
log.info("new version $payload.version available")
|
||||||
updateInfoHash = new InfoHash(Base64.decode(infoHash))
|
updateInfoHash = new InfoHash(Base64.decode(infoHash))
|
||||||
if (fileManager.rootToFiles.containsKey(updateInfoHash))
|
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 {
|
else {
|
||||||
updateDownloading = false
|
updateDownloading = false
|
||||||
version = payload.version
|
version = payload.version
|
||||||
|
@@ -5,4 +5,5 @@ import com.muwire.core.Event
|
|||||||
class UpdateDownloadedEvent extends Event {
|
class UpdateDownloadedEvent extends Event {
|
||||||
String version
|
String version
|
||||||
String signer
|
String signer
|
||||||
|
String text
|
||||||
}
|
}
|
||||||
|
@@ -21,6 +21,8 @@ class ContentUploader extends Uploader {
|
|||||||
private final Mesh mesh
|
private final Mesh mesh
|
||||||
private final int pieceSize
|
private final int pieceSize
|
||||||
|
|
||||||
|
private volatile boolean done
|
||||||
|
|
||||||
ContentUploader(File file, ContentRequest request, Endpoint endpoint, Mesh mesh, int pieceSize) {
|
ContentUploader(File file, ContentRequest request, Endpoint endpoint, Mesh mesh, int pieceSize) {
|
||||||
super(endpoint)
|
super(endpoint)
|
||||||
this.file = file
|
this.file = file
|
||||||
@@ -62,20 +64,23 @@ class ContentUploader extends Uploader {
|
|||||||
mapped = channel.map(FileChannel.MapMode.READ_ONLY, range.start, range.end - range.start + 1)
|
mapped = channel.map(FileChannel.MapMode.READ_ONLY, range.start, range.end - range.start + 1)
|
||||||
byte [] tmp = new byte[0x1 << 13]
|
byte [] tmp = new byte[0x1 << 13]
|
||||||
while(mapped.hasRemaining()) {
|
while(mapped.hasRemaining()) {
|
||||||
int start = mapped.position()
|
int read
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
|
int start = mapped.position()
|
||||||
mapped.get(tmp, 0, Math.min(tmp.length, mapped.remaining()))
|
mapped.get(tmp, 0, Math.min(tmp.length, mapped.remaining()))
|
||||||
|
read = mapped.position() - start
|
||||||
|
dataSinceLastRead += read
|
||||||
}
|
}
|
||||||
int read = mapped.position() - start
|
|
||||||
endpoint.getOutputStream().write(tmp, 0, read)
|
endpoint.getOutputStream().write(tmp, 0, read)
|
||||||
}
|
}
|
||||||
|
done = true
|
||||||
} finally {
|
} finally {
|
||||||
try {channel?.close() } catch (IOException ignored) {}
|
try {channel?.close() } catch (IOException ignored) {}
|
||||||
endpoint.getOutputStream().flush()
|
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
DataUtil.tryUnmap(mapped)
|
DataUtil.tryUnmap(mapped)
|
||||||
mapped = null
|
mapped = null
|
||||||
}
|
}
|
||||||
|
endpoint.getOutputStream().flush()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +103,7 @@ class ContentUploader extends Uploader {
|
|||||||
@Override
|
@Override
|
||||||
public synchronized int getProgress() {
|
public synchronized int getProgress() {
|
||||||
if (mapped == null)
|
if (mapped == null)
|
||||||
return 0
|
return done ? 100 : 0
|
||||||
int position = mapped.position()
|
int position = mapped.position()
|
||||||
int total = request.getRange().end - request.getRange().start
|
int total = request.getRange().end - request.getRange().start
|
||||||
(int)(position * 100.0 / total)
|
(int)(position * 100.0 / total)
|
||||||
@@ -123,4 +128,13 @@ class ContentUploader extends Uploader {
|
|||||||
public long getTotalSize() {
|
public long getTotalSize() {
|
||||||
return file.length();
|
return file.length();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (!(o instanceof ContentUploader))
|
||||||
|
return false
|
||||||
|
ContentUploader other = (ContentUploader)o
|
||||||
|
request.infoHash == other.request.infoHash &&
|
||||||
|
request.getDownloader() == other.request.getDownloader()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -26,11 +26,13 @@ class HashListUploader extends Uploader {
|
|||||||
|
|
||||||
byte[]tmp = new byte[0x1 << 13]
|
byte[]tmp = new byte[0x1 << 13]
|
||||||
while(mapped.hasRemaining()) {
|
while(mapped.hasRemaining()) {
|
||||||
int start = mapped.position()
|
int read
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
|
int start = mapped.position()
|
||||||
mapped.get(tmp, 0, Math.min(tmp.length, mapped.remaining()))
|
mapped.get(tmp, 0, Math.min(tmp.length, mapped.remaining()))
|
||||||
|
read = mapped.position() - start
|
||||||
|
dataSinceLastRead += read
|
||||||
}
|
}
|
||||||
int read = mapped.position() - start
|
|
||||||
endpoint.getOutputStream().write(tmp, 0, read)
|
endpoint.getOutputStream().write(tmp, 0, read)
|
||||||
}
|
}
|
||||||
endpoint.getOutputStream().flush()
|
endpoint.getOutputStream().flush()
|
||||||
@@ -65,4 +67,12 @@ class HashListUploader extends Uploader {
|
|||||||
public long getTotalSize() {
|
public long getTotalSize() {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (!(o instanceof HashListUploader))
|
||||||
|
return false
|
||||||
|
HashListUploader other = (HashListUploader)o
|
||||||
|
infoHash == other.infoHash && request.downloader == other.request.downloader
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,8 @@ import java.nio.charset.StandardCharsets
|
|||||||
|
|
||||||
import com.muwire.core.EventBus
|
import com.muwire.core.EventBus
|
||||||
import com.muwire.core.InfoHash
|
import com.muwire.core.InfoHash
|
||||||
|
import com.muwire.core.MuWireSettings
|
||||||
|
import com.muwire.core.Persona
|
||||||
import com.muwire.core.SharedFile
|
import com.muwire.core.SharedFile
|
||||||
import com.muwire.core.connection.Endpoint
|
import com.muwire.core.connection.Endpoint
|
||||||
import com.muwire.core.download.DownloadManager
|
import com.muwire.core.download.DownloadManager
|
||||||
@@ -22,15 +24,22 @@ public class UploadManager {
|
|||||||
private final FileManager fileManager
|
private final FileManager fileManager
|
||||||
private final MeshManager meshManager
|
private final MeshManager meshManager
|
||||||
private final DownloadManager downloadManager
|
private final DownloadManager downloadManager
|
||||||
|
private final MuWireSettings props
|
||||||
|
|
||||||
|
/** LOCKING: this on both structures */
|
||||||
|
private int totalUploads
|
||||||
|
private final Map<Persona, Integer> uploadsPerUser = new HashMap<>()
|
||||||
|
|
||||||
public UploadManager() {}
|
public UploadManager() {}
|
||||||
|
|
||||||
public UploadManager(EventBus eventBus, FileManager fileManager,
|
public UploadManager(EventBus eventBus, FileManager fileManager,
|
||||||
MeshManager meshManager, DownloadManager downloadManager) {
|
MeshManager meshManager, DownloadManager downloadManager,
|
||||||
|
MuWireSettings props) {
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.fileManager = fileManager
|
this.fileManager = fileManager
|
||||||
this.meshManager = meshManager
|
this.meshManager = meshManager
|
||||||
this.downloadManager = downloadManager
|
this.downloadManager = downloadManager
|
||||||
|
this.props = props
|
||||||
}
|
}
|
||||||
|
|
||||||
public void processGET(Endpoint e) throws IOException {
|
public void processGET(Endpoint e) throws IOException {
|
||||||
@@ -83,6 +92,14 @@ public class UploadManager {
|
|||||||
if (request.have > 0)
|
if (request.have > 0)
|
||||||
eventBus.publish(new SourceDiscoveredEvent(infoHash : request.infoHash, source : request.downloader))
|
eventBus.publish(new SourceDiscoveredEvent(infoHash : request.infoHash, source : request.downloader))
|
||||||
|
|
||||||
|
if (!incrementUploads(request.downloader)) {
|
||||||
|
log.info("rejecting due to slot limit")
|
||||||
|
e.getOutputStream().write("429 Too Many Requests\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
|
e.getOutputStream().flush()
|
||||||
|
e.close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
Mesh mesh
|
Mesh mesh
|
||||||
File file
|
File file
|
||||||
int pieceSize
|
int pieceSize
|
||||||
@@ -91,6 +108,7 @@ public class UploadManager {
|
|||||||
file = downloader.incompleteFile
|
file = downloader.incompleteFile
|
||||||
pieceSize = downloader.pieceSizePow2
|
pieceSize = downloader.pieceSizePow2
|
||||||
} else {
|
} else {
|
||||||
|
sharedFiles.each { it.getDownloaders().add(request.downloader.getHumanReadableName()) }
|
||||||
SharedFile sharedFile = sharedFiles.iterator().next();
|
SharedFile sharedFile = sharedFiles.iterator().next();
|
||||||
mesh = meshManager.getOrCreate(request.infoHash, sharedFile.NPieces, false)
|
mesh = meshManager.getOrCreate(request.infoHash, sharedFile.NPieces, false)
|
||||||
file = sharedFile.file
|
file = sharedFile.file
|
||||||
@@ -102,6 +120,7 @@ public class UploadManager {
|
|||||||
try {
|
try {
|
||||||
uploader.respond()
|
uploader.respond()
|
||||||
} finally {
|
} finally {
|
||||||
|
decrementUploads(request.downloader)
|
||||||
eventBus.publish(new UploadFinishedEvent(uploader : uploader))
|
eventBus.publish(new UploadFinishedEvent(uploader : uploader))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,11 +176,20 @@ public class UploadManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!incrementUploads(request.downloader)) {
|
||||||
|
log.info("rejecting due to slot limit")
|
||||||
|
e.getOutputStream().write("429 Too Many Requests\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
|
e.getOutputStream().flush()
|
||||||
|
e.close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
Uploader uploader = new HashListUploader(e, fullInfoHash, request)
|
Uploader uploader = new HashListUploader(e, fullInfoHash, request)
|
||||||
eventBus.publish(new UploadEvent(uploader : uploader))
|
eventBus.publish(new UploadEvent(uploader : uploader))
|
||||||
try {
|
try {
|
||||||
uploader.respond()
|
uploader.respond()
|
||||||
} finally {
|
} finally {
|
||||||
|
decrementUploads(request.downloader)
|
||||||
eventBus.publish(new UploadFinishedEvent(uploader : uploader))
|
eventBus.publish(new UploadFinishedEvent(uploader : uploader))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,6 +244,7 @@ public class UploadManager {
|
|||||||
file = downloader.incompleteFile
|
file = downloader.incompleteFile
|
||||||
pieceSize = downloader.pieceSizePow2
|
pieceSize = downloader.pieceSizePow2
|
||||||
} else {
|
} else {
|
||||||
|
sharedFiles.each { it.getDownloaders().add(request.downloader.getHumanReadableName()) }
|
||||||
SharedFile sharedFile = sharedFiles.iterator().next();
|
SharedFile sharedFile = sharedFiles.iterator().next();
|
||||||
mesh = meshManager.getOrCreate(request.infoHash, sharedFile.NPieces, false)
|
mesh = meshManager.getOrCreate(request.infoHash, sharedFile.NPieces, false)
|
||||||
file = sharedFile.file
|
file = sharedFile.file
|
||||||
@@ -231,5 +260,37 @@ public class UploadManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param p downloader
|
||||||
|
* @return true if this upload hasn't hit any slot limits
|
||||||
|
*/
|
||||||
|
private synchronized boolean incrementUploads(Persona p) {
|
||||||
|
if (props.totalUploadSlots >= 0 && totalUploads >= props.totalUploadSlots)
|
||||||
|
return false
|
||||||
|
if (props.uploadSlotsPerUser == 0)
|
||||||
|
return false
|
||||||
|
|
||||||
|
Integer currentUploads = uploadsPerUser.get(p)
|
||||||
|
if (currentUploads == null)
|
||||||
|
currentUploads = 0
|
||||||
|
if (props.uploadSlotsPerUser > 0 && currentUploads >= props.uploadSlotsPerUser)
|
||||||
|
return false
|
||||||
|
uploadsPerUser.put(p, ++currentUploads)
|
||||||
|
totalUploads++
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void decrementUploads(Persona p) {
|
||||||
|
totalUploads--
|
||||||
|
Integer currentUploads = uploadsPerUser.get(p)
|
||||||
|
if (currentUploads == null || currentUploads == 0)
|
||||||
|
throw new IllegalStateException()
|
||||||
|
currentUploads--
|
||||||
|
if (currentUploads == 0)
|
||||||
|
uploadsPerUser.remove(p)
|
||||||
|
else
|
||||||
|
uploadsPerUser.put(p, currentUploads)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -12,6 +12,12 @@ abstract class Uploader {
|
|||||||
protected final Endpoint endpoint
|
protected final Endpoint endpoint
|
||||||
protected ByteBuffer mapped
|
protected ByteBuffer mapped
|
||||||
|
|
||||||
|
private long lastSpeedRead
|
||||||
|
protected int dataSinceLastRead
|
||||||
|
|
||||||
|
private final ArrayList<Integer> speedArr = [0,0,0,0,0]
|
||||||
|
private int speedPos, speedAvg
|
||||||
|
|
||||||
Uploader(Endpoint endpoint) {
|
Uploader(Endpoint endpoint) {
|
||||||
this.endpoint = endpoint
|
this.endpoint = endpoint
|
||||||
}
|
}
|
||||||
@@ -38,4 +44,34 @@ abstract class Uploader {
|
|||||||
abstract int getTotalPieces();
|
abstract int getTotalPieces();
|
||||||
|
|
||||||
abstract long getTotalSize();
|
abstract long getTotalSize();
|
||||||
|
|
||||||
|
synchronized int speed() {
|
||||||
|
final long now = System.currentTimeMillis()
|
||||||
|
long interval = Math.max(1000, now - lastSpeedRead)
|
||||||
|
lastSpeedRead = now;
|
||||||
|
int currSpeed = (int) (dataSinceLastRead * 1000.0 / interval)
|
||||||
|
dataSinceLastRead = 0
|
||||||
|
|
||||||
|
// normalize to speedArr.size
|
||||||
|
currSpeed /= speedArr.size()
|
||||||
|
|
||||||
|
// compute new speedAvg and update speedArr
|
||||||
|
if ( speedArr[speedPos] > speedAvg ) {
|
||||||
|
speedAvg = 0
|
||||||
|
} else {
|
||||||
|
speedAvg -= speedArr[speedPos]
|
||||||
|
}
|
||||||
|
speedAvg += currSpeed
|
||||||
|
speedArr[speedPos] = currSpeed
|
||||||
|
// this might be necessary due to rounding errors
|
||||||
|
if (speedAvg < 0)
|
||||||
|
speedAvg = 0
|
||||||
|
|
||||||
|
// rolling index over the speedArr
|
||||||
|
speedPos++
|
||||||
|
if (speedPos >= speedArr.size())
|
||||||
|
speedPos=0
|
||||||
|
|
||||||
|
speedAvg
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,4 +10,6 @@ public class Constants {
|
|||||||
public static final int MAX_HEADERS = 16;
|
public static final int MAX_HEADERS = 16;
|
||||||
|
|
||||||
public static final int MAX_RESULTS = 0x1 << 16;
|
public static final int MAX_RESULTS = 0x1 << 16;
|
||||||
|
|
||||||
|
public static final int MAX_COMMENT_LENGTH = 0x1 << 15;
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,10 @@ package com.muwire.core;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import com.muwire.core.util.DataUtil;
|
import com.muwire.core.util.DataUtil;
|
||||||
|
|
||||||
@@ -23,6 +26,8 @@ public class SharedFile {
|
|||||||
private final List<String> b64EncodedHashList;
|
private final List<String> b64EncodedHashList;
|
||||||
|
|
||||||
private volatile String comment;
|
private volatile String comment;
|
||||||
|
private volatile int hits;
|
||||||
|
private final Set<String> downloaders = Collections.synchronizedSet(new HashSet<>());
|
||||||
|
|
||||||
public SharedFile(File file, InfoHash infoHash, int pieceSize) throws IOException {
|
public SharedFile(File file, InfoHash infoHash, int pieceSize) throws IOException {
|
||||||
this.file = file;
|
this.file = file;
|
||||||
@@ -91,6 +96,22 @@ public class SharedFile {
|
|||||||
return comment;
|
return comment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getHits() {
|
||||||
|
return hits;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void hit() {
|
||||||
|
hits++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getDownloaders() {
|
||||||
|
return downloaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addDownloader(String name) {
|
||||||
|
downloaders.add(name);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return file.hashCode() ^ infoHash.hashCode();
|
return file.hashCode() ^ infoHash.hashCode();
|
||||||
|
@@ -11,10 +11,14 @@ import java.nio.ByteBuffer;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import com.muwire.core.Constants;
|
import com.muwire.core.Constants;
|
||||||
|
|
||||||
import net.i2p.data.Base64;
|
import net.i2p.data.Base64;
|
||||||
|
import net.i2p.util.ConcurrentHashSet;
|
||||||
|
|
||||||
public class DataUtil {
|
public class DataUtil {
|
||||||
|
|
||||||
@@ -165,4 +169,22 @@ public class DataUtil {
|
|||||||
} catch(Exception ex) { }
|
} catch(Exception ex) { }
|
||||||
cb = null;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -95,7 +95,7 @@ class ConnectionAcceptorTest {
|
|||||||
connectionEstablisher = connectionEstablisherMock.proxyInstance()
|
connectionEstablisher = connectionEstablisherMock.proxyInstance()
|
||||||
|
|
||||||
acceptor = new ConnectionAcceptor(eventBus, connectionManager, settings, i2pAcceptor,
|
acceptor = new ConnectionAcceptor(eventBus, connectionManager, settings, i2pAcceptor,
|
||||||
hostCache, trustService, searchManager, uploadManager, connectionEstablisher)
|
hostCache, trustService, searchManager, uploadManager, null, connectionEstablisher)
|
||||||
acceptor.start()
|
acceptor.start()
|
||||||
Thread.sleep(100)
|
Thread.sleep(100)
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,8 @@ package com.muwire.core.download
|
|||||||
|
|
||||||
import static org.junit.Assert.fail
|
import static org.junit.Assert.fail
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
|
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Ignore
|
import org.junit.Ignore
|
||||||
@@ -76,7 +78,7 @@ class DownloadSessionTest {
|
|||||||
toUploader = new PipedOutputStream(fromDownloader)
|
toUploader = new PipedOutputStream(fromDownloader)
|
||||||
endpoint = new Endpoint(null, fromUploader, toUploader, null)
|
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 = new Thread( { perform() } as Runnable)
|
||||||
downloadThread.setDaemon(true)
|
downloadThread.setDaemon(true)
|
||||||
downloadThread.start()
|
downloadThread.start()
|
||||||
|
@@ -0,0 +1,42 @@
|
|||||||
|
package com.muwire.core.files
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class FileTreeTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRemoveEmtpyDirs() {
|
||||||
|
File a = new File("a")
|
||||||
|
File b = new File(a, "b")
|
||||||
|
File c = new File(b, "c")
|
||||||
|
|
||||||
|
FileTree tree = new FileTree()
|
||||||
|
tree.add(c)
|
||||||
|
|
||||||
|
assert tree.root.children.size() == 1
|
||||||
|
assert tree.fileToNode.size() == 3
|
||||||
|
|
||||||
|
tree.remove(b)
|
||||||
|
assert tree.root.children.size() == 0
|
||||||
|
assert tree.fileToNode.isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRemoveFileFromNonEmptyDir() {
|
||||||
|
File a = new File("a")
|
||||||
|
File b = new File(a,"b")
|
||||||
|
File c = new File(b, "c")
|
||||||
|
File d = new File(b, "d")
|
||||||
|
|
||||||
|
FileTree tree = new FileTree()
|
||||||
|
tree.add(c)
|
||||||
|
|
||||||
|
assert tree.fileToNode.size() == 3
|
||||||
|
|
||||||
|
tree.add(d)
|
||||||
|
assert tree.fileToNode.size() == 4
|
||||||
|
|
||||||
|
tree.remove(d)
|
||||||
|
assert tree.fileToNode.size() == 3
|
||||||
|
}
|
||||||
|
}
|
@@ -1,11 +1,12 @@
|
|||||||
group = com.muwire
|
group = com.muwire
|
||||||
version = 0.5.2
|
version = 0.5.7
|
||||||
i2pVersion = 0.9.42
|
i2pVersion = 0.9.43
|
||||||
groovyVersion = 2.4.15
|
groovyVersion = 2.4.15
|
||||||
slf4jVersion = 1.7.25
|
slf4jVersion = 1.7.25
|
||||||
spockVersion = 1.1-groovy-2.4
|
spockVersion = 1.1-groovy-2.4
|
||||||
grailsVersion=4.0.0
|
grailsVersion=4.0.0
|
||||||
gorm.version=7.0.2.RELEASE
|
gorm.version=7.0.2.RELEASE
|
||||||
|
griffonEnv=prod
|
||||||
|
|
||||||
sourceCompatibility=1.8
|
sourceCompatibility=1.8
|
||||||
targetCompatibility=1.8
|
targetCompatibility=1.8
|
||||||
|
@@ -40,8 +40,11 @@ griffon {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
mainClassName = 'com.muwire.gui.Launcher'
|
application {
|
||||||
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties']
|
mainClassName = 'com.muwire.gui.Launcher'
|
||||||
|
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties','-Xmx256M']
|
||||||
|
applicationName = 'MuWire'
|
||||||
|
}
|
||||||
|
|
||||||
apply from: 'gradle/publishing.gradle'
|
apply from: 'gradle/publishing.gradle'
|
||||||
// apply from: 'gradle/code-coverage.gradle'
|
// apply from: 'gradle/code-coverage.gradle'
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
|
|
||||||
application {
|
application {
|
||||||
title = 'MuWire'
|
title = 'MuWire'
|
||||||
startupGroups = ['EventList', 'MainFrame']
|
startupGroups = ['EventList', 'MainFrame', 'ShutdownWindow']
|
||||||
autoShutdown = true
|
autoShutdown = false
|
||||||
}
|
}
|
||||||
|
|
||||||
mvcGroups {
|
mvcGroups {
|
||||||
@@ -16,6 +16,11 @@ mvcGroups {
|
|||||||
view = 'com.muwire.gui.MainFrameView'
|
view = 'com.muwire.gui.MainFrameView'
|
||||||
controller = 'com.muwire.gui.MainFrameController'
|
controller = 'com.muwire.gui.MainFrameController'
|
||||||
}
|
}
|
||||||
|
'ShutdownWindow' {
|
||||||
|
model = 'com.muwire.gui.ShutdownWindowModel'
|
||||||
|
view = 'com.muwire.gui.ShutdownWindowView'
|
||||||
|
controller = 'com.muwire.gui.ShutdownWindowController'
|
||||||
|
}
|
||||||
'SearchTab' {
|
'SearchTab' {
|
||||||
model = 'com.muwire.gui.SearchTabModel'
|
model = 'com.muwire.gui.SearchTabModel'
|
||||||
view = 'com.muwire.gui.SearchTabView'
|
view = 'com.muwire.gui.SearchTabView'
|
||||||
@@ -36,6 +41,11 @@ mvcGroups {
|
|||||||
view = 'com.muwire.gui.I2PStatusView'
|
view = 'com.muwire.gui.I2PStatusView'
|
||||||
controller = 'com.muwire.gui.I2PStatusController'
|
controller = 'com.muwire.gui.I2PStatusController'
|
||||||
}
|
}
|
||||||
|
'system-status' {
|
||||||
|
model = 'com.muwire.gui.SystemStatusModel'
|
||||||
|
view = 'com.muwire.gui.SystemStatusView'
|
||||||
|
controller = 'com.muwire.gui.SystemStatusController'
|
||||||
|
}
|
||||||
'trust-list' {
|
'trust-list' {
|
||||||
model = 'com.muwire.gui.TrustListModel'
|
model = 'com.muwire.gui.TrustListModel'
|
||||||
view = 'com.muwire.gui.TrustListView'
|
view = 'com.muwire.gui.TrustListView'
|
||||||
@@ -61,4 +71,19 @@ mvcGroups {
|
|||||||
view = 'com.muwire.gui.BrowseView'
|
view = 'com.muwire.gui.BrowseView'
|
||||||
controller = 'com.muwire.gui.BrowseController'
|
controller = 'com.muwire.gui.BrowseController'
|
||||||
}
|
}
|
||||||
|
'close-warning' {
|
||||||
|
model = 'com.muwire.gui.CloseWarningModel'
|
||||||
|
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 net.i2p.data.Base64
|
||||||
|
|
||||||
import javax.annotation.Nonnull
|
import javax.annotation.Nonnull
|
||||||
|
import javax.swing.JOptionPane
|
||||||
|
|
||||||
|
import com.muwire.core.Constants
|
||||||
import com.muwire.core.Core
|
import com.muwire.core.Core
|
||||||
import com.muwire.core.files.UICommentEvent
|
import com.muwire.core.files.UICommentEvent
|
||||||
import com.muwire.core.util.DataUtil
|
import com.muwire.core.util.DataUtil
|
||||||
@@ -24,6 +26,11 @@ class AddCommentController {
|
|||||||
@ControllerAction
|
@ControllerAction
|
||||||
void save() {
|
void save() {
|
||||||
String comment = view.textarea.getText()
|
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)
|
if (comment.trim().length() == 0)
|
||||||
comment = null
|
comment = null
|
||||||
else
|
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
|
||||||
|
}
|
@@ -0,0 +1,60 @@
|
|||||||
|
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 CloseWarningController {
|
||||||
|
@MVCMember @Nonnull
|
||||||
|
CloseWarningModel model
|
||||||
|
@MVCMember @Nonnull
|
||||||
|
CloseWarningView view
|
||||||
|
|
||||||
|
UISettings settings
|
||||||
|
File home
|
||||||
|
|
||||||
|
|
||||||
|
void mvcGroupInit(Map<String, String> args) {
|
||||||
|
model.closeWarning = settings.closeWarning
|
||||||
|
}
|
||||||
|
|
||||||
|
@ControllerAction
|
||||||
|
void close() {
|
||||||
|
boolean rememberDecision = view.checkbox.model.isSelected()
|
||||||
|
if (rememberDecision) {
|
||||||
|
settings.exitOnClose = false
|
||||||
|
settings.closeWarning = false
|
||||||
|
saveMuSettings()
|
||||||
|
}
|
||||||
|
|
||||||
|
view.dialog.setVisible(false)
|
||||||
|
view.mainFrame.setVisible(false)
|
||||||
|
mvcGroup.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
@ControllerAction
|
||||||
|
void exit() {
|
||||||
|
boolean rememberDecision = view.checkbox.model.isSelected()
|
||||||
|
if (rememberDecision) {
|
||||||
|
settings.exitOnClose = true
|
||||||
|
settings.closeWarning = false
|
||||||
|
saveMuSettings()
|
||||||
|
}
|
||||||
|
view.dialog.setVisible(false)
|
||||||
|
view.mainFrame.setVisible(false)
|
||||||
|
def parentView = mvcGroup.parentGroup.view
|
||||||
|
mvcGroup.destroy()
|
||||||
|
parentView.closeApplication()
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveMuSettings() {
|
||||||
|
|
||||||
|
File props = new File(home, "gui.properties")
|
||||||
|
props.withOutputStream {
|
||||||
|
settings.write(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -97,9 +97,6 @@ class ContentPanelController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void saveMuWireSettings() {
|
void saveMuWireSettings() {
|
||||||
File f = new File(core.home, "MuWire.properties")
|
core.saveMuSettings()
|
||||||
f.withOutputStream {
|
|
||||||
core.muOptions.write(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -7,7 +7,12 @@ import griffon.core.mvc.MVCGroup
|
|||||||
import griffon.core.mvc.MVCGroupConfiguration
|
import griffon.core.mvc.MVCGroupConfiguration
|
||||||
import griffon.inject.MVCMember
|
import griffon.inject.MVCMember
|
||||||
import griffon.metadata.ArtifactProviderFor
|
import griffon.metadata.ArtifactProviderFor
|
||||||
|
import net.i2p.crypto.DSAEngine
|
||||||
import net.i2p.data.Base64
|
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.annotation.Nonnull
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -47,11 +52,29 @@ class MainFrameController {
|
|||||||
private volatile Core core
|
private volatile Core core
|
||||||
|
|
||||||
@ControllerAction
|
@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")
|
def cardsPanel = builder.getVariable("cards-panel")
|
||||||
cardsPanel.getLayout().show(cardsPanel, "search window")
|
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()
|
search = search.trim()
|
||||||
if (search.length() == 0)
|
if (search.length() == 0)
|
||||||
return
|
return
|
||||||
@@ -77,21 +100,28 @@ class MainFrameController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def searchEvent
|
def searchEvent
|
||||||
|
byte [] payload
|
||||||
if (hashSearch) {
|
if (hashSearch) {
|
||||||
searchEvent = new SearchEvent(searchHash : root, uuid : uuid, oobInfohash: true, compressedResults : true)
|
searchEvent = new SearchEvent(searchHash : root, uuid : uuid, oobInfohash: true, compressedResults : true)
|
||||||
|
payload = root
|
||||||
} else {
|
} else {
|
||||||
// this can be improved a lot
|
// this can be improved a lot
|
||||||
def replaced = search.toLowerCase().trim().replaceAll(SplitPattern.SPLIT_PATTERN, " ")
|
def replaced = search.toLowerCase().trim().replaceAll(SplitPattern.SPLIT_PATTERN, " ")
|
||||||
def terms = replaced.split(" ")
|
def terms = replaced.split(" ")
|
||||||
def nonEmpty = []
|
def nonEmpty = []
|
||||||
terms.each { if (it.length() > 0) nonEmpty << it }
|
terms.each { if (it.length() > 0) nonEmpty << it }
|
||||||
|
payload = String.join(" ",nonEmpty).getBytes(StandardCharsets.UTF_8)
|
||||||
searchEvent = new SearchEvent(searchTerms : nonEmpty, uuid : uuid, oobInfohash: true,
|
searchEvent = new SearchEvent(searchTerms : nonEmpty, uuid : uuid, oobInfohash: true,
|
||||||
searchComments : core.muOptions.searchComments, compressedResults : true)
|
searchComments : core.muOptions.searchComments, compressedResults : true)
|
||||||
}
|
}
|
||||||
boolean firstHop = core.muOptions.allowUntrusted || core.muOptions.searchExtraHop
|
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,
|
core.eventBus.publish(new QueryEvent(searchEvent : searchEvent, firstHop : firstHop,
|
||||||
replyTo: core.me.destination, receivedOn: core.me.destination,
|
replyTo: core.me.destination, receivedOn: core.me.destination,
|
||||||
originator : core.me))
|
originator : core.me, sig : sig.data))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void search(String infoHash, String tabTitle) {
|
void search(String infoHash, String tabTitle) {
|
||||||
@@ -293,13 +323,23 @@ class MainFrameController {
|
|||||||
mvcGroup.createMVCGroup("add-comment", "Add Comment", params)
|
mvcGroup.createMVCGroup("add-comment", "Add Comment", params)
|
||||||
}
|
}
|
||||||
|
|
||||||
void saveMuWireSettings() {
|
@ControllerAction
|
||||||
File f = new File(core.home, "MuWire.properties")
|
void clearUploads() {
|
||||||
f.withOutputStream {
|
model.uploads.removeAll { it.finished }
|
||||||
core.muOptions.write(it)
|
}
|
||||||
|
|
||||||
|
@ControllerAction
|
||||||
|
void restoreSession() {
|
||||||
|
model.sessionRestored = true
|
||||||
|
view.settings.openTabs.each {
|
||||||
|
performSearch(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void saveMuWireSettings() {
|
||||||
|
core.saveMuSettings()
|
||||||
|
}
|
||||||
|
|
||||||
void mvcGroupInit(Map<String, String> args) {
|
void mvcGroupInit(Map<String, String> args) {
|
||||||
application.addPropertyChangeListener("core", {e->
|
application.addPropertyChangeListener("core", {e->
|
||||||
core = e.getNewValue()
|
core = e.getNewValue()
|
||||||
|
@@ -36,8 +36,8 @@ class MuWireStatusController {
|
|||||||
|
|
||||||
|
|
||||||
model.sharedFiles = core.fileManager.fileToSharedFile.size()
|
model.sharedFiles = core.fileManager.fileToSharedFile.size()
|
||||||
|
|
||||||
model.downloads = core.downloadManager.downloaders.size()
|
model.downloads = core.downloadManager.downloaders.size()
|
||||||
|
model.browsed = core.connectionAcceptor.browsed
|
||||||
}
|
}
|
||||||
|
|
||||||
@ControllerAction
|
@ControllerAction
|
||||||
|
@@ -10,6 +10,7 @@ import java.util.logging.Level
|
|||||||
|
|
||||||
import javax.annotation.Nonnull
|
import javax.annotation.Nonnull
|
||||||
import javax.swing.JFileChooser
|
import javax.swing.JFileChooser
|
||||||
|
import javax.swing.JOptionPane
|
||||||
|
|
||||||
import com.muwire.core.Core
|
import com.muwire.core.Core
|
||||||
import com.muwire.core.MuWireSettings
|
import com.muwire.core.MuWireSettings
|
||||||
@@ -21,11 +22,13 @@ class OptionsController {
|
|||||||
@MVCMember @Nonnull
|
@MVCMember @Nonnull
|
||||||
OptionsView view
|
OptionsView view
|
||||||
|
|
||||||
|
Core core
|
||||||
|
MuWireSettings settings
|
||||||
|
UISettings uiSettings
|
||||||
|
|
||||||
@ControllerAction
|
@ControllerAction
|
||||||
void save() {
|
void save() {
|
||||||
String text
|
String text
|
||||||
Core core = application.context.get("core")
|
|
||||||
MuWireSettings settings = application.context.get("muwire-settings")
|
|
||||||
|
|
||||||
def i2pProps = core.i2pOptions
|
def i2pProps = core.i2pOptions
|
||||||
|
|
||||||
@@ -70,6 +73,16 @@ class OptionsController {
|
|||||||
model.updateCheckInterval = text
|
model.updateCheckInterval = text
|
||||||
settings.updateCheckInterval = Integer.valueOf(text)
|
settings.updateCheckInterval = Integer.valueOf(text)
|
||||||
|
|
||||||
|
text = view.totalUploadSlotsField.text
|
||||||
|
int totalUploadSlots = Integer.valueOf(text)
|
||||||
|
model.totalUploadSlots = totalUploadSlots
|
||||||
|
settings.totalUploadSlots = totalUploadSlots
|
||||||
|
|
||||||
|
text = view.uploadSlotsPerUserField.text
|
||||||
|
int uploadSlotsPerUser = Integer.valueOf(text)
|
||||||
|
model.uploadSlotsPerUser = uploadSlotsPerUser
|
||||||
|
settings.uploadSlotsPerUser = uploadSlotsPerUser
|
||||||
|
|
||||||
boolean searchComments = view.searchCommentsCheckbox.model.isSelected()
|
boolean searchComments = view.searchCommentsCheckbox.model.isSelected()
|
||||||
model.searchComments = searchComments
|
model.searchComments = searchComments
|
||||||
settings.searchComments = searchComments
|
settings.searchComments = searchComments
|
||||||
@@ -127,14 +140,10 @@ class OptionsController {
|
|||||||
model.trustListInterval = trustListInterval
|
model.trustListInterval = trustListInterval
|
||||||
settings.trustListInterval = Integer.parseInt(trustListInterval)
|
settings.trustListInterval = Integer.parseInt(trustListInterval)
|
||||||
|
|
||||||
File settingsFile = new File(core.home, "MuWire.properties")
|
core.saveMuSettings()
|
||||||
settingsFile.withOutputStream {
|
|
||||||
settings.write(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UI Setttings
|
// UI Setttings
|
||||||
|
|
||||||
UISettings uiSettings = application.context.get("ui-settings")
|
|
||||||
text = view.lnfField.text
|
text = view.lnfField.text
|
||||||
model.lnf = text
|
model.lnf = text
|
||||||
uiSettings.lnf = text
|
uiSettings.lnf = text
|
||||||
@@ -158,12 +167,28 @@ class OptionsController {
|
|||||||
model.excludeLocalResult = excludeLocalResult
|
model.excludeLocalResult = excludeLocalResult
|
||||||
uiSettings.excludeLocalResult = excludeLocalResult
|
uiSettings.excludeLocalResult = excludeLocalResult
|
||||||
|
|
||||||
|
boolean clearUploads = view.clearUploadsCheckbox.model.isSelected()
|
||||||
|
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")
|
File uiSettingsFile = new File(core.home, "gui.properties")
|
||||||
uiSettingsFile.withOutputStream {
|
uiSettingsFile.withOutputStream {
|
||||||
uiSettings.write(it)
|
uiSettings.write(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ControllerAction
|
@ControllerAction
|
||||||
@@ -195,13 +220,32 @@ class OptionsController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ControllerAction
|
@ControllerAction
|
||||||
void automaticFontAction() {
|
void automaticFont() {
|
||||||
model.automaticFontSize = true
|
model.automaticFontSize = true
|
||||||
model.customFontSize = 12
|
model.customFontSize = 12
|
||||||
}
|
}
|
||||||
|
|
||||||
@ControllerAction
|
@ControllerAction
|
||||||
void customFontAction() {
|
void customFont() {
|
||||||
model.automaticFontSize = false
|
model.automaticFontSize = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ControllerAction
|
||||||
|
void exitOnClose() {
|
||||||
|
model.exitOnClose = true
|
||||||
|
model.closeDecisionMade = true
|
||||||
|
}
|
||||||
|
|
||||||
|
@ControllerAction
|
||||||
|
void minimizeOnClose() {
|
||||||
|
model.exitOnClose = false
|
||||||
|
model.closeDecisionMade = true
|
||||||
|
}
|
||||||
|
|
||||||
|
@ControllerAction
|
||||||
|
void clearHistory() {
|
||||||
|
uiSettings.searchHistory.clear()
|
||||||
|
saveUISettings()
|
||||||
|
JOptionPane.showMessageDialog(null, "Search history has been cleared")
|
||||||
|
}
|
||||||
}
|
}
|
@@ -0,0 +1,11 @@
|
|||||||
|
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 ShutdownWindowController {
|
||||||
|
}
|
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
@@ -10,7 +10,9 @@ import com.muwire.gui.UISettings
|
|||||||
|
|
||||||
import javax.annotation.Nonnull
|
import javax.annotation.Nonnull
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.swing.ImageIcon
|
||||||
import javax.swing.JLabel
|
import javax.swing.JLabel
|
||||||
|
import javax.swing.JPopupMenu
|
||||||
import javax.swing.JTable
|
import javax.swing.JTable
|
||||||
import javax.swing.LookAndFeel
|
import javax.swing.LookAndFeel
|
||||||
import javax.swing.UIManager
|
import javax.swing.UIManager
|
||||||
@@ -20,7 +22,11 @@ import static griffon.util.GriffonApplicationUtils.isMacOSX
|
|||||||
import static groovy.swing.SwingBuilder.lookAndFeel
|
import static groovy.swing.SwingBuilder.lookAndFeel
|
||||||
|
|
||||||
import java.awt.Font
|
import java.awt.Font
|
||||||
|
import java.awt.MenuItem
|
||||||
|
import java.awt.PopupMenu
|
||||||
|
import java.awt.SystemTray
|
||||||
import java.awt.Toolkit
|
import java.awt.Toolkit
|
||||||
|
import java.awt.TrayIcon
|
||||||
import java.util.logging.Level
|
import java.util.logging.Level
|
||||||
import java.util.logging.LogManager
|
import java.util.logging.LogManager
|
||||||
|
|
||||||
@@ -43,6 +49,56 @@ class Initialize extends AbstractLifecycleHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
System.setProperty("apple.eawt.quitStrategy", "CLOSE_ALL_WINDOWS");
|
||||||
|
|
||||||
|
if (SystemTray.isSupported() && (SystemVersion.isMac() || SystemVersion.isWindows())) {
|
||||||
|
try {
|
||||||
|
def tray = SystemTray.getSystemTray()
|
||||||
|
def url = Initialize.class.getResource("/MuWire-16x16.png")
|
||||||
|
def image = new ImageIcon(url, "tray icon").getImage()
|
||||||
|
def popupMenu = new PopupMenu()
|
||||||
|
def trayIcon = new TrayIcon(image, "MuWire", popupMenu)
|
||||||
|
|
||||||
|
|
||||||
|
def exit = new MenuItem("Exit")
|
||||||
|
exit.addActionListener({
|
||||||
|
application.getWindowManager().findWindow("main-frame").setVisible(false)
|
||||||
|
application.getWindowManager().findWindow("shutdown-window").setVisible(true)
|
||||||
|
Core core = application.getContext().get("core")
|
||||||
|
if (core != null) {
|
||||||
|
Thread t = new Thread({
|
||||||
|
core.shutdown()
|
||||||
|
application.shutdown()
|
||||||
|
}as Runnable)
|
||||||
|
t.start()
|
||||||
|
} else
|
||||||
|
application.shutdown()
|
||||||
|
tray.remove(trayIcon)
|
||||||
|
})
|
||||||
|
|
||||||
|
def showMW = {e ->
|
||||||
|
def mainFrame = application.getWindowManager().findWindow("main-frame")
|
||||||
|
if (mainFrame != null) {
|
||||||
|
Core core = application.getContext().get("core")
|
||||||
|
if (core != null)
|
||||||
|
mainFrame.setVisible(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def show = new MenuItem("Open MuWire")
|
||||||
|
show.addActionListener(showMW)
|
||||||
|
popupMenu.add(show)
|
||||||
|
popupMenu.add(exit)
|
||||||
|
tray.add(trayIcon)
|
||||||
|
|
||||||
|
|
||||||
|
trayIcon.addActionListener(showMW)
|
||||||
|
application.getContext().put("tray-icon", true)
|
||||||
|
} catch (Exception bad) {
|
||||||
|
log.log(Level.WARNING,"couldn't set tray icon",bad)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log.info "Loading home dir"
|
log.info "Loading home dir"
|
||||||
def portableHome = System.getProperty("portable.home")
|
def portableHome = System.getProperty("portable.home")
|
||||||
def home = portableHome == null ?
|
def home = portableHome == null ?
|
||||||
@@ -61,6 +117,7 @@ class Initialize extends AbstractLifecycleHandler {
|
|||||||
|
|
||||||
def guiPropsFile = new File(home, "gui.properties")
|
def guiPropsFile = new File(home, "gui.properties")
|
||||||
UISettings uiSettings
|
UISettings uiSettings
|
||||||
|
int rowHeight = 15
|
||||||
if (guiPropsFile.exists()) {
|
if (guiPropsFile.exists()) {
|
||||||
Properties props = new Properties()
|
Properties props = new Properties()
|
||||||
guiPropsFile.withInputStream { props.load(it) }
|
guiPropsFile.withInputStream { props.load(it) }
|
||||||
@@ -93,15 +150,17 @@ class Initialize extends AbstractLifecycleHandler {
|
|||||||
} else {
|
} else {
|
||||||
fontSize = uiSettings.fontSize
|
fontSize = uiSettings.fontSize
|
||||||
}
|
}
|
||||||
|
rowHeight = fontSize + 3
|
||||||
FontUIResource font = new FontUIResource(fontName, Font.PLAIN, fontSize)
|
FontUIResource font = new FontUIResource(fontName, Font.PLAIN, fontSize)
|
||||||
|
|
||||||
def keys = lnf.getDefaults().keys()
|
def keys = lnf.getDefaults().keys()
|
||||||
while(keys.hasMoreElements()) {
|
while(keys.hasMoreElements()) {
|
||||||
def key = keys.nextElement()
|
def key = keys.nextElement()
|
||||||
def value = lnf.getDefaults().get(key)
|
def value = lnf.getDefaults().get(key)
|
||||||
if (value instanceof FontUIResource)
|
if (value instanceof FontUIResource) {
|
||||||
lnf.getDefaults().put(key, font)
|
lnf.getDefaults().put(key, font)
|
||||||
|
UIManager.put(key, font)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -125,6 +184,7 @@ class Initialize extends AbstractLifecycleHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
application.context.put("row-height", rowHeight)
|
||||||
application.context.put("ui-settings", uiSettings)
|
application.context.put("ui-settings", uiSettings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -41,9 +41,9 @@ class Ready extends AbstractLifecycleHandler {
|
|||||||
def propsFile = new File(home, "MuWire.properties")
|
def propsFile = new File(home, "MuWire.properties")
|
||||||
if (propsFile.exists()) {
|
if (propsFile.exists()) {
|
||||||
log.info("loading existing props file")
|
log.info("loading existing props file")
|
||||||
propsFile.withInputStream {
|
propsFile.withReader("UTF-8", {
|
||||||
props.load(it)
|
props.load(it)
|
||||||
}
|
})
|
||||||
props = new MuWireSettings(props)
|
props = new MuWireSettings(props)
|
||||||
if (props.incompleteLocation == null)
|
if (props.incompleteLocation == null)
|
||||||
props.incompleteLocation = new File(home, "incompletes")
|
props.incompleteLocation = new File(home, "incompletes")
|
||||||
@@ -90,9 +90,9 @@ class Ready extends AbstractLifecycleHandler {
|
|||||||
props.downloadLocation = chooser.getSelectedFile()
|
props.downloadLocation = chooser.getSelectedFile()
|
||||||
}
|
}
|
||||||
|
|
||||||
propsFile.withOutputStream {
|
propsFile.withPrintWriter("UTF-8", {
|
||||||
props.write(it)
|
props.write(it)
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
Core core
|
Core core
|
||||||
|
@@ -18,8 +18,8 @@ class Shutdown extends AbstractLifecycleHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
void execute() {
|
void execute() {
|
||||||
log.info("shutting down")
|
log.info("shutting down from lifecycle")
|
||||||
Core core = application.context.get("core")
|
Core core = application.context.get("core")
|
||||||
core.shutdown()
|
core?.shutdown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,18 @@
|
|||||||
|
package com.muwire.gui
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull
|
||||||
|
|
||||||
|
import griffon.core.artifact.GriffonController
|
||||||
|
import griffon.core.artifact.GriffonModel
|
||||||
|
import griffon.core.controller.ControllerAction
|
||||||
|
import griffon.inject.MVCMember
|
||||||
|
import griffon.metadata.ArtifactProviderFor
|
||||||
|
import griffon.transform.Observable
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull
|
||||||
|
|
||||||
|
@ArtifactProviderFor(GriffonModel)
|
||||||
|
class CloseWarningModel {
|
||||||
|
@Observable boolean closeWarning
|
||||||
|
@Observable boolean exitOnClose
|
||||||
|
}
|
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
package com.muwire.gui
|
package com.muwire.gui
|
||||||
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
@@ -28,13 +29,16 @@ import com.muwire.core.download.DownloadStartedEvent
|
|||||||
import com.muwire.core.download.Downloader
|
import com.muwire.core.download.Downloader
|
||||||
import com.muwire.core.files.AllFilesLoadedEvent
|
import com.muwire.core.files.AllFilesLoadedEvent
|
||||||
import com.muwire.core.files.DirectoryUnsharedEvent
|
import com.muwire.core.files.DirectoryUnsharedEvent
|
||||||
|
import com.muwire.core.files.DirectoryWatchedEvent
|
||||||
import com.muwire.core.files.FileDownloadedEvent
|
import com.muwire.core.files.FileDownloadedEvent
|
||||||
import com.muwire.core.files.FileHashedEvent
|
import com.muwire.core.files.FileHashedEvent
|
||||||
import com.muwire.core.files.FileHashingEvent
|
import com.muwire.core.files.FileHashingEvent
|
||||||
import com.muwire.core.files.FileLoadedEvent
|
import com.muwire.core.files.FileLoadedEvent
|
||||||
import com.muwire.core.files.FileSharedEvent
|
import com.muwire.core.files.FileSharedEvent
|
||||||
import com.muwire.core.files.FileUnsharedEvent
|
import com.muwire.core.files.FileUnsharedEvent
|
||||||
|
import com.muwire.core.files.SideCarFileEvent
|
||||||
import com.muwire.core.search.QueryEvent
|
import com.muwire.core.search.QueryEvent
|
||||||
|
import com.muwire.core.search.SearchEvent
|
||||||
import com.muwire.core.search.UIResultBatchEvent
|
import com.muwire.core.search.UIResultBatchEvent
|
||||||
import com.muwire.core.search.UIResultEvent
|
import com.muwire.core.search.UIResultEvent
|
||||||
import com.muwire.core.trust.TrustEvent
|
import com.muwire.core.trust.TrustEvent
|
||||||
@@ -45,6 +49,7 @@ import com.muwire.core.update.UpdateAvailableEvent
|
|||||||
import com.muwire.core.update.UpdateDownloadedEvent
|
import com.muwire.core.update.UpdateDownloadedEvent
|
||||||
import com.muwire.core.upload.UploadEvent
|
import com.muwire.core.upload.UploadEvent
|
||||||
import com.muwire.core.upload.UploadFinishedEvent
|
import com.muwire.core.upload.UploadFinishedEvent
|
||||||
|
import com.muwire.core.upload.Uploader
|
||||||
|
|
||||||
import griffon.core.GriffonApplication
|
import griffon.core.GriffonApplication
|
||||||
import griffon.core.artifact.GriffonModel
|
import griffon.core.artifact.GriffonModel
|
||||||
@@ -84,6 +89,8 @@ class MainFrameModel {
|
|||||||
def distrusted = []
|
def distrusted = []
|
||||||
def subscriptions = []
|
def subscriptions = []
|
||||||
|
|
||||||
|
boolean sessionRestored
|
||||||
|
|
||||||
@Observable int connections
|
@Observable int connections
|
||||||
@Observable String me
|
@Observable String me
|
||||||
@Observable int loadedFiles
|
@Observable int loadedFiles
|
||||||
@@ -186,6 +193,7 @@ class MainFrameModel {
|
|||||||
core.eventBus.register(ConnectionEvent.class, this)
|
core.eventBus.register(ConnectionEvent.class, this)
|
||||||
core.eventBus.register(DisconnectionEvent.class, this)
|
core.eventBus.register(DisconnectionEvent.class, this)
|
||||||
core.eventBus.register(FileHashedEvent.class, this)
|
core.eventBus.register(FileHashedEvent.class, this)
|
||||||
|
core.eventBus.register(SideCarFileEvent.class, this)
|
||||||
core.eventBus.register(FileHashingEvent.class, this)
|
core.eventBus.register(FileHashingEvent.class, this)
|
||||||
core.eventBus.register(FileLoadedEvent.class, this)
|
core.eventBus.register(FileLoadedEvent.class, this)
|
||||||
core.eventBus.register(UploadEvent.class, this)
|
core.eventBus.register(UploadEvent.class, this)
|
||||||
@@ -199,6 +207,7 @@ class MainFrameModel {
|
|||||||
core.eventBus.register(AllFilesLoadedEvent.class, this)
|
core.eventBus.register(AllFilesLoadedEvent.class, this)
|
||||||
core.eventBus.register(UpdateDownloadedEvent.class, this)
|
core.eventBus.register(UpdateDownloadedEvent.class, this)
|
||||||
core.eventBus.register(TrustSubscriptionUpdatedEvent.class, this)
|
core.eventBus.register(TrustSubscriptionUpdatedEvent.class, this)
|
||||||
|
core.eventBus.register(SearchEvent.class, this)
|
||||||
|
|
||||||
core.muOptions.watchedKeywords.each {
|
core.muOptions.watchedKeywords.each {
|
||||||
core.eventBus.publish(new ContentControlEvent(term : it, regex: false, add: true))
|
core.eventBus.publish(new ContentControlEvent(term : it, regex: false, add: true))
|
||||||
@@ -258,8 +267,10 @@ class MainFrameModel {
|
|||||||
|
|
||||||
void onUpdateDownloadedEvent(UpdateDownloadedEvent e) {
|
void onUpdateDownloadedEvent(UpdateDownloadedEvent e) {
|
||||||
runInsideUIAsync {
|
runInsideUIAsync {
|
||||||
JOptionPane.showMessageDialog(null, "MuWire $e.version has been downloaded. You can update now",
|
Map<String, Object> args = new HashMap<>()
|
||||||
"Update Downloaded", JOptionPane.INFORMATION_MESSAGE)
|
args['available'] = null
|
||||||
|
args['downloaded'] = e
|
||||||
|
mvcGroup.createMVCGroup("update", "update", args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,6 +297,8 @@ class MainFrameModel {
|
|||||||
runInsideUIAsync {
|
runInsideUIAsync {
|
||||||
connections = core.connectionManager.getConnections().size()
|
connections = core.connectionManager.getConnections().size()
|
||||||
|
|
||||||
|
view.showRestoreOrEmpty()
|
||||||
|
|
||||||
if (connections > 0) {
|
if (connections > 0) {
|
||||||
def topPanel = builder.getVariable("top-panel")
|
def topPanel = builder.getVariable("top-panel")
|
||||||
topPanel.getLayout().show(topPanel, "top-search-panel")
|
topPanel.getLayout().show(topPanel, "top-search-panel")
|
||||||
@@ -355,6 +368,7 @@ class MainFrameModel {
|
|||||||
def dmtn = fileToNode.remove(e.unsharedFile)
|
def dmtn = fileToNode.remove(e.unsharedFile)
|
||||||
if (dmtn != null) {
|
if (dmtn != null) {
|
||||||
loadedFiles = fileToNode.size()
|
loadedFiles = fileToNode.size()
|
||||||
|
List<File> unshared = new ArrayList<>()
|
||||||
while (true) {
|
while (true) {
|
||||||
def parent = dmtn.getParent()
|
def parent = dmtn.getParent()
|
||||||
parent.remove(dmtn)
|
parent.remove(dmtn)
|
||||||
@@ -363,12 +377,16 @@ class MainFrameModel {
|
|||||||
if (parent.getChildCount() == 0) {
|
if (parent.getChildCount() == 0) {
|
||||||
File file = parent.getUserObject().file
|
File file = parent.getUserObject().file
|
||||||
if (core.muOptions.watchedDirectories.contains(file.toString()))
|
if (core.muOptions.watchedDirectories.contains(file.toString()))
|
||||||
core.eventBus.publish(new DirectoryUnsharedEvent(directory : parent.getUserObject().file))
|
unshared.add(file)
|
||||||
dmtn = parent
|
dmtn = parent
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if (!unshared.isEmpty()) {
|
||||||
|
File unsharedRoot = unshared.get( unshared.size() -1 )
|
||||||
|
core.eventBus.publish(new DirectoryUnsharedEvent(directory : unsharedRoot))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
view.refreshSharedFiles()
|
view.refreshSharedFiles()
|
||||||
}
|
}
|
||||||
@@ -376,15 +394,39 @@ class MainFrameModel {
|
|||||||
|
|
||||||
void onUploadEvent(UploadEvent e) {
|
void onUploadEvent(UploadEvent e) {
|
||||||
runInsideUIAsync {
|
runInsideUIAsync {
|
||||||
uploads << e.uploader
|
UploaderWrapper wrapper = null
|
||||||
|
uploads.each {
|
||||||
|
if (it.uploader == e.uploader) {
|
||||||
|
wrapper = it
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (wrapper != null) {
|
||||||
|
wrapper.uploader = e.uploader
|
||||||
|
wrapper.requests++
|
||||||
|
wrapper.finished = false
|
||||||
|
} else
|
||||||
|
uploads << new UploaderWrapper(uploader : e.uploader)
|
||||||
JTable table = builder.getVariable("uploads-table")
|
JTable table = builder.getVariable("uploads-table")
|
||||||
table.model.fireTableDataChanged()
|
table.model.fireTableDataChanged()
|
||||||
|
view.refreshSharedFiles()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onUploadFinishedEvent(UploadFinishedEvent e) {
|
void onUploadFinishedEvent(UploadFinishedEvent e) {
|
||||||
runInsideUIAsync {
|
runInsideUIAsync {
|
||||||
uploads.remove(e.uploader)
|
UploaderWrapper wrapper = null
|
||||||
|
uploads.each {
|
||||||
|
if (it.uploader == e.uploader) {
|
||||||
|
wrapper = it
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (uiSettings.clearUploads) {
|
||||||
|
uploads.remove(wrapper)
|
||||||
|
} else {
|
||||||
|
wrapper.finished = true
|
||||||
|
}
|
||||||
JTable table = builder.getVariable("uploads-table")
|
JTable table = builder.getVariable("uploads-table")
|
||||||
table.model.fireTableDataChanged()
|
table.model.fireTableDataChanged()
|
||||||
}
|
}
|
||||||
@@ -417,6 +459,12 @@ class MainFrameModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onSearchEvent(SearchEvent e) {
|
||||||
|
runInsideUIAsync {
|
||||||
|
view.refreshSharedFiles()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void onQueryEvent(QueryEvent e) {
|
void onQueryEvent(QueryEvent e) {
|
||||||
if (e.replyTo == core.me.destination)
|
if (e.replyTo == core.me.destination)
|
||||||
return
|
return
|
||||||
@@ -488,17 +536,16 @@ class MainFrameModel {
|
|||||||
|
|
||||||
void onUpdateAvailableEvent(UpdateAvailableEvent e) {
|
void onUpdateAvailableEvent(UpdateAvailableEvent e) {
|
||||||
runInsideUIAsync {
|
runInsideUIAsync {
|
||||||
|
Map<String, Object> args = new HashMap<>()
|
||||||
int option = JOptionPane.showConfirmDialog(null,
|
args['available'] = e
|
||||||
"MuWire $e.version is available from $e.signer. You have "+ metadata["application.version"]+" Update?",
|
args['downloaded'] = null
|
||||||
"New MuWire version availble", JOptionPane.OK_CANCEL_OPTION)
|
mvcGroup.createMVCGroup("update", "update", args)
|
||||||
if (option == JOptionPane.CANCEL_OPTION)
|
|
||||||
return
|
|
||||||
controller.search(e.infoHash,"MuWire update")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onRouterDisconnectedEvent(RouterDisconnectedEvent e) {
|
void onRouterDisconnectedEvent(RouterDisconnectedEvent e) {
|
||||||
|
if (core.getShutdown().get())
|
||||||
|
return
|
||||||
runInsideUIAsync {
|
runInsideUIAsync {
|
||||||
JOptionPane.showMessageDialog(null, "MuWire lost connection to the I2P router and will now exit.",
|
JOptionPane.showMessageDialog(null, "MuWire lost connection to the I2P router and will now exit.",
|
||||||
"Connection to I2P router lost", JOptionPane.WARNING_MESSAGE)
|
"Connection to I2P router lost", JOptionPane.WARNING_MESSAGE)
|
||||||
@@ -518,6 +565,12 @@ class MainFrameModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onSideCarFileEvent(SideCarFileEvent e) {
|
||||||
|
runInsideUIAsync {
|
||||||
|
view.refreshSharedFiles()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void insertIntoTree(SharedFile file) {
|
private void insertIntoTree(SharedFile file) {
|
||||||
List<File> parents = new ArrayList<>()
|
List<File> parents = new ArrayList<>()
|
||||||
File tmp = file.file.getParentFile()
|
File tmp = file.file.getParentFile()
|
||||||
@@ -573,4 +626,10 @@ class MainFrameModel {
|
|||||||
boolean canDownload(InfoHash hash) {
|
boolean canDownload(InfoHash hash) {
|
||||||
!downloadInfoHashes.contains(hash)
|
!downloadInfoHashes.contains(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class UploaderWrapper {
|
||||||
|
Uploader uploader
|
||||||
|
int requests
|
||||||
|
boolean finished
|
||||||
|
}
|
||||||
}
|
}
|
@@ -20,6 +20,7 @@ class MuWireStatusModel {
|
|||||||
@Observable int hopelessHosts
|
@Observable int hopelessHosts
|
||||||
@Observable int sharedFiles
|
@Observable int sharedFiles
|
||||||
@Observable int downloads
|
@Observable int downloads
|
||||||
|
@Observable int browsed
|
||||||
|
|
||||||
void mvcGroupInit(Map<String,String> args) {
|
void mvcGroupInit(Map<String,String> args) {
|
||||||
controller.refresh()
|
controller.refresh()
|
||||||
|
@@ -19,6 +19,9 @@ class OptionsModel {
|
|||||||
@Observable boolean searchComments
|
@Observable boolean searchComments
|
||||||
@Observable boolean browseFiles
|
@Observable boolean browseFiles
|
||||||
@Observable int speedSmoothSeconds
|
@Observable int speedSmoothSeconds
|
||||||
|
@Observable int totalUploadSlots
|
||||||
|
@Observable int uploadSlotsPerUser
|
||||||
|
@Observable boolean storeSearchHistory
|
||||||
|
|
||||||
// i2p options
|
// i2p options
|
||||||
@Observable String inboundLength
|
@Observable String inboundLength
|
||||||
@@ -38,6 +41,9 @@ class OptionsModel {
|
|||||||
@Observable boolean clearFinishedDownloads
|
@Observable boolean clearFinishedDownloads
|
||||||
@Observable boolean excludeLocalResult
|
@Observable boolean excludeLocalResult
|
||||||
@Observable boolean showSearchHashes
|
@Observable boolean showSearchHashes
|
||||||
|
@Observable boolean clearUploads
|
||||||
|
@Observable boolean exitOnClose
|
||||||
|
@Observable boolean closeDecisionMade
|
||||||
|
|
||||||
// bw options
|
// bw options
|
||||||
@Observable String inBw
|
@Observable String inBw
|
||||||
@@ -62,6 +68,8 @@ class OptionsModel {
|
|||||||
searchComments = settings.searchComments
|
searchComments = settings.searchComments
|
||||||
browseFiles = settings.browseFiles
|
browseFiles = settings.browseFiles
|
||||||
speedSmoothSeconds = settings.speedSmoothSeconds
|
speedSmoothSeconds = settings.speedSmoothSeconds
|
||||||
|
totalUploadSlots = settings.totalUploadSlots
|
||||||
|
uploadSlotsPerUser = settings.uploadSlotsPerUser
|
||||||
|
|
||||||
Core core = application.context.get("core")
|
Core core = application.context.get("core")
|
||||||
inboundLength = core.i2pOptions["inbound.length"]
|
inboundLength = core.i2pOptions["inbound.length"]
|
||||||
@@ -81,6 +89,9 @@ class OptionsModel {
|
|||||||
clearFinishedDownloads = uiSettings.clearFinishedDownloads
|
clearFinishedDownloads = uiSettings.clearFinishedDownloads
|
||||||
excludeLocalResult = uiSettings.excludeLocalResult
|
excludeLocalResult = uiSettings.excludeLocalResult
|
||||||
showSearchHashes = uiSettings.showSearchHashes
|
showSearchHashes = uiSettings.showSearchHashes
|
||||||
|
clearUploads = uiSettings.clearUploads
|
||||||
|
exitOnClose = uiSettings.exitOnClose
|
||||||
|
storeSearchHistory = uiSettings.storeSearchHistory
|
||||||
|
|
||||||
if (core.router != null) {
|
if (core.router != null) {
|
||||||
inBw = String.valueOf(settings.inBw)
|
inBw = String.valueOf(settings.inBw)
|
||||||
|
@@ -0,0 +1,9 @@
|
|||||||
|
package com.muwire.gui
|
||||||
|
|
||||||
|
import griffon.core.artifact.GriffonModel
|
||||||
|
import griffon.transform.Observable
|
||||||
|
import griffon.metadata.ArtifactProviderFor
|
||||||
|
|
||||||
|
@ArtifactProviderFor(GriffonModel)
|
||||||
|
class ShutdownWindowModel {
|
||||||
|
}
|
@@ -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()
|
borderLayout()
|
||||||
panel (constraints : BorderLayout.CENTER) {
|
panel (constraints : BorderLayout.CENTER) {
|
||||||
scrollPane {
|
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) {
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
59
gui/griffon-app/views/com/muwire/gui/CloseWarningView.groovy
Normal file
59
gui/griffon-app/views/com/muwire/gui/CloseWarningView.groovy
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
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.GridBagConstraints
|
||||||
|
import java.awt.event.WindowAdapter
|
||||||
|
import java.awt.event.WindowEvent
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull
|
||||||
|
|
||||||
|
@ArtifactProviderFor(GriffonView)
|
||||||
|
class CloseWarningView {
|
||||||
|
@MVCMember @Nonnull
|
||||||
|
FactoryBuilderSupport builder
|
||||||
|
@MVCMember @Nonnull
|
||||||
|
CloseWarningModel model
|
||||||
|
|
||||||
|
def mainFrame
|
||||||
|
def dialog
|
||||||
|
def panel
|
||||||
|
def checkbox
|
||||||
|
|
||||||
|
void initUI() {
|
||||||
|
mainFrame = application.windowManager.findWindow("main-frame")
|
||||||
|
|
||||||
|
dialog = new JDialog(mainFrame, "Close MuWire?", true)
|
||||||
|
panel = builder.panel {
|
||||||
|
gridBagLayout()
|
||||||
|
label(text : "Would you like to minimize to system tray or exit immediately?", constraints : gbc(gridx: 0, gridy: 0, gridwidth : 2))
|
||||||
|
label(text : "\n", constraints : gbc(gridx : 0, gridy : 1)) // TODO: real padding
|
||||||
|
label(text : "Remember my decision", constraints : gbc(gridx: 0, gridy : 2, weightx: 100, anchor : GridBagConstraints.LINE_END))
|
||||||
|
checkbox = checkBox(selected : bind {model.closeWarning}, constraints : gbc(gridx: 1, gridy :2))
|
||||||
|
panel (constraints : gbc(gridx: 0, gridy : 3, gridwidth : 2)) {
|
||||||
|
button(text : "Minimize To Tray", closeAction)
|
||||||
|
button(text : "Exit MuWire", exitAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dialog.getContentPane().add(panel)
|
||||||
|
dialog.pack()
|
||||||
|
dialog.setResizable(false)
|
||||||
|
dialog.setLocationRelativeTo(mainFrame)
|
||||||
|
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
|
||||||
|
dialog.addWindowListener(new WindowAdapter() {
|
||||||
|
public void windowClosed(WindowEvent e) {
|
||||||
|
mainFrame.setVisible(false)
|
||||||
|
mvcGroup.destroy()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
void mvcGroupInit(Map<String,String> args) {
|
||||||
|
dialog.show()
|
||||||
|
}
|
||||||
|
}
|
@@ -38,6 +38,7 @@ class ContentPanelView {
|
|||||||
|
|
||||||
void initUI() {
|
void initUI() {
|
||||||
mainFrame = application.windowManager.findWindow("main-frame")
|
mainFrame = application.windowManager.findWindow("main-frame")
|
||||||
|
int rowHeight = application.context.get("row-height")
|
||||||
dialog = new JDialog(mainFrame, "Content Control Panel", true)
|
dialog = new JDialog(mainFrame, "Content Control Panel", true)
|
||||||
|
|
||||||
mainPanel = builder.panel {
|
mainPanel = builder.panel {
|
||||||
@@ -48,7 +49,7 @@ class ContentPanelView {
|
|||||||
label(text : "Rules")
|
label(text : "Rules")
|
||||||
}
|
}
|
||||||
scrollPane (constraints : BorderLayout.CENTER) {
|
scrollPane (constraints : BorderLayout.CENTER) {
|
||||||
rulesTable = table(id : "rules-table", autoCreateRowSorter : true) {
|
rulesTable = table(id : "rules-table", autoCreateRowSorter : true, rowHeight : rowHeight) {
|
||||||
tableModel(list : model.rules) {
|
tableModel(list : model.rules) {
|
||||||
closureColumn(header: "Term", type:String, read: {row -> row.getTerm()})
|
closureColumn(header: "Term", type:String, read: {row -> row.getTerm()})
|
||||||
closureColumn(header: "Regex?", type:Boolean, read: {row -> row instanceof RegexMatcher})
|
closureColumn(header: "Regex?", type:Boolean, read: {row -> row instanceof RegexMatcher})
|
||||||
@@ -74,7 +75,7 @@ class ContentPanelView {
|
|||||||
label(text : "Hits")
|
label(text : "Hits")
|
||||||
}
|
}
|
||||||
scrollPane(constraints : BorderLayout.CENTER) {
|
scrollPane(constraints : BorderLayout.CENTER) {
|
||||||
hitsTable = table(id : "hits-table", autoCreateRowSorter : true) {
|
hitsTable = table(id : "hits-table", autoCreateRowSorter : true, rowHeight : rowHeight) {
|
||||||
tableModel(list : model.hits) {
|
tableModel(list : model.hits) {
|
||||||
closureColumn(header : "Searcher", type : String, read : {row -> row.persona.getHumanReadableName()})
|
closureColumn(header : "Searcher", type : String, read : {row -> row.persona.getHumanReadableName()})
|
||||||
closureColumn(header : "Keywords", type : String, read : {row -> row.keywords.join(" ")})
|
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.core.artifact.GriffonView
|
||||||
import griffon.inject.MVCMember
|
import griffon.inject.MVCMember
|
||||||
import griffon.metadata.ArtifactProviderFor
|
import griffon.metadata.ArtifactProviderFor
|
||||||
|
import net.i2p.data.DataHelper
|
||||||
|
|
||||||
import javax.swing.JDialog
|
import javax.swing.JDialog
|
||||||
import javax.swing.JPanel
|
import javax.swing.JPanel
|
||||||
@@ -64,10 +65,10 @@ class I2PStatusView {
|
|||||||
panel(border : titledBorder(title : "Bandwidth", border : etchedBorder(), titlePosition : TitledBorder.TOP),
|
panel(border : titledBorder(title : "Bandwidth", border : etchedBorder(), titlePosition : TitledBorder.TOP),
|
||||||
constraints : gbc(gridx: 0, gridy: 3, fill : GridBagConstraints.HORIZONTAL, weightx: 100)) {
|
constraints : gbc(gridx: 0, gridy: 3, fill : GridBagConstraints.HORIZONTAL, weightx: 100)) {
|
||||||
gridBagLayout()
|
gridBagLayout()
|
||||||
label(text : "Receive Bps (15 seconds)", constraints : gbc(gridx:0, gridy:0, anchor: GridBagConstraints.LINE_START, weightx: 100))
|
label(text : "Receive (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 : bind {DataHelper.formatSize2Decimal(model.receiveBps,false)+"B"}, 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 : "Send (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 : bind {DataHelper.formatSize2Decimal(model.sendBps, false)+"B"}, constraints : gbc(gridx: 1, gridy:1, anchor : GridBagConstraints.LINE_END))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
package com.muwire.gui
|
package com.muwire.gui
|
||||||
|
|
||||||
|
import griffon.core.GriffonApplication
|
||||||
import griffon.core.artifact.GriffonView
|
import griffon.core.artifact.GriffonView
|
||||||
import griffon.core.env.Metadata
|
import griffon.core.env.Metadata
|
||||||
import griffon.inject.MVCMember
|
import griffon.inject.MVCMember
|
||||||
@@ -10,7 +11,9 @@ import net.i2p.data.DataHelper
|
|||||||
import javax.swing.BorderFactory
|
import javax.swing.BorderFactory
|
||||||
import javax.swing.Box
|
import javax.swing.Box
|
||||||
import javax.swing.BoxLayout
|
import javax.swing.BoxLayout
|
||||||
|
import javax.swing.JComboBox
|
||||||
import javax.swing.JFileChooser
|
import javax.swing.JFileChooser
|
||||||
|
import javax.swing.JFrame
|
||||||
import javax.swing.JLabel
|
import javax.swing.JLabel
|
||||||
import javax.swing.JMenuItem
|
import javax.swing.JMenuItem
|
||||||
import javax.swing.JPopupMenu
|
import javax.swing.JPopupMenu
|
||||||
@@ -19,13 +22,19 @@ import javax.swing.JTable
|
|||||||
import javax.swing.JTree
|
import javax.swing.JTree
|
||||||
import javax.swing.ListSelectionModel
|
import javax.swing.ListSelectionModel
|
||||||
import javax.swing.SwingConstants
|
import javax.swing.SwingConstants
|
||||||
|
import javax.swing.SwingUtilities
|
||||||
import javax.swing.TransferHandler
|
import javax.swing.TransferHandler
|
||||||
import javax.swing.border.Border
|
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.table.DefaultTableCellRenderer
|
||||||
import javax.swing.tree.TreeNode
|
import javax.swing.tree.TreeNode
|
||||||
import javax.swing.tree.TreePath
|
import javax.swing.tree.TreePath
|
||||||
|
|
||||||
import com.muwire.core.Constants
|
import com.muwire.core.Constants
|
||||||
|
import com.muwire.core.Core
|
||||||
import com.muwire.core.MuWireSettings
|
import com.muwire.core.MuWireSettings
|
||||||
import com.muwire.core.SharedFile
|
import com.muwire.core.SharedFile
|
||||||
import com.muwire.core.download.Downloader
|
import com.muwire.core.download.Downloader
|
||||||
@@ -42,6 +51,8 @@ import java.awt.datatransfer.DataFlavor
|
|||||||
import java.awt.datatransfer.StringSelection
|
import java.awt.datatransfer.StringSelection
|
||||||
import java.awt.event.MouseAdapter
|
import java.awt.event.MouseAdapter
|
||||||
import java.awt.event.MouseEvent
|
import java.awt.event.MouseEvent
|
||||||
|
import java.awt.event.WindowAdapter
|
||||||
|
import java.awt.event.WindowEvent
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
|
|
||||||
import javax.annotation.Nonnull
|
import javax.annotation.Nonnull
|
||||||
@@ -54,20 +65,25 @@ class MainFrameView {
|
|||||||
@MVCMember @Nonnull
|
@MVCMember @Nonnull
|
||||||
MainFrameModel model
|
MainFrameModel model
|
||||||
|
|
||||||
|
@Inject @Nonnull GriffonApplication application
|
||||||
@Inject Metadata metadata
|
@Inject Metadata metadata
|
||||||
|
|
||||||
def downloadsTable
|
def downloadsTable
|
||||||
def lastDownloadSortEvent
|
def lastDownloadSortEvent
|
||||||
def lastSharedSortEvent
|
def lastSharedSortEvent
|
||||||
def trustTablesSortEvents = [:]
|
def trustTablesSortEvents = [:]
|
||||||
|
def expansionListener = new TreeExpansions()
|
||||||
|
|
||||||
|
|
||||||
UISettings settings
|
UISettings settings
|
||||||
|
|
||||||
void initUI() {
|
void initUI() {
|
||||||
settings = application.context.get("ui-settings")
|
settings = application.context.get("ui-settings")
|
||||||
|
int rowHeight = application.context.get("row-height")
|
||||||
builder.with {
|
builder.with {
|
||||||
application(size : [1024,768], id: 'main-frame',
|
application(size : [1024,768], id: 'main-frame',
|
||||||
locationRelativeTo : null,
|
locationRelativeTo : null,
|
||||||
|
defaultCloseOperation : JFrame.DO_NOTHING_ON_CLOSE,
|
||||||
title: application.configuration['application.title'] + " " +
|
title: application.configuration['application.title'] + " " +
|
||||||
metadata["application.version"] + " revision " + metadata["build.revision"],
|
metadata["application.version"] + " revision " + metadata["build.revision"],
|
||||||
iconImage: imageIcon('/MuWire-48x48.png').image,
|
iconImage: imageIcon('/MuWire-48x48.png').image,
|
||||||
@@ -77,18 +93,35 @@ class MainFrameView {
|
|||||||
pack : false,
|
pack : false,
|
||||||
visible : bind { model.coreInitialized }) {
|
visible : bind { model.coreInitialized }) {
|
||||||
menuBar {
|
menuBar {
|
||||||
|
menu (text : "File") {
|
||||||
|
menuItem("Exit", actionPerformed : {closeApplication()})
|
||||||
|
}
|
||||||
menu (text : "Options") {
|
menu (text : "Options") {
|
||||||
menuItem("Configuration", actionPerformed : {mvcGroup.createMVCGroup("Options")})
|
menuItem("Configuration", actionPerformed : {
|
||||||
menuItem("Content Control", actionPerformed : {
|
def params = [:]
|
||||||
def env = [:]
|
params['core'] = application.context.get("core")
|
||||||
env["core"] = model.core
|
params['settings'] = params['core'].muOptions
|
||||||
mvcGroup.createMVCGroup("content-panel", env)
|
params['uiSettings'] = settings
|
||||||
|
mvcGroup.createMVCGroup("Options", params)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
menu (text : "Status") {
|
menu (text : "Status") {
|
||||||
menuItem("MuWire", actionPerformed : {mvcGroup.createMVCGroup("mu-wire-status")})
|
menuItem("MuWire", actionPerformed : {mvcGroup.createMVCGroup("mu-wire-status")})
|
||||||
MuWireSettings muSettings = application.context.get("muwire-settings")
|
MuWireSettings muSettings = application.context.get("muwire-settings")
|
||||||
menuItem("I2P", enabled : bind {model.routerPresent}, actionPerformed: {mvcGroup.createMVCGroup("i-2-p-status")})
|
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()
|
borderLayout()
|
||||||
@@ -112,20 +145,39 @@ class MainFrameView {
|
|||||||
panel(constraints: BorderLayout.CENTER) {
|
panel(constraints: BorderLayout.CENTER) {
|
||||||
borderLayout()
|
borderLayout()
|
||||||
label(" Enter search here:", constraints: BorderLayout.WEST) // TODO: fix this
|
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) {
|
panel( constraints: BorderLayout.EAST) {
|
||||||
button(text: "Search", searchAction)
|
button(text: "Search", searchAction)
|
||||||
|
button(text : "", icon : imageIcon("/close_tab.png"), clearSearchAction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
panel (id: "cards-panel", constraints : BorderLayout.CENTER) {
|
panel (id: "cards-panel", constraints : BorderLayout.CENTER) {
|
||||||
cardLayout()
|
cardLayout()
|
||||||
panel (constraints : "search window") {
|
panel (id : "search window", constraints : "search window") {
|
||||||
borderLayout()
|
cardLayout()
|
||||||
tabbedPane(id : "result-tabs", constraints: BorderLayout.CENTER)
|
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") {
|
panel (constraints: "downloads window") {
|
||||||
gridLayout(rows : 1, cols : 1)
|
gridLayout(rows : 1, cols : 1)
|
||||||
@@ -133,7 +185,7 @@ class MainFrameView {
|
|||||||
panel {
|
panel {
|
||||||
borderLayout()
|
borderLayout()
|
||||||
scrollPane (constraints : BorderLayout.CENTER) {
|
scrollPane (constraints : BorderLayout.CENTER) {
|
||||||
downloadsTable = table(id : "downloads-table", autoCreateRowSorter : true) {
|
downloadsTable = table(id : "downloads-table", autoCreateRowSorter : true, rowHeight : rowHeight) {
|
||||||
tableModel(list: model.downloads) {
|
tableModel(list: model.downloads) {
|
||||||
closureColumn(header: "Name", preferredWidth: 300, type: String, read : {row -> row.downloader.file.getName()})
|
closureColumn(header: "Name", preferredWidth: 300, type: String, read : {row -> row.downloader.file.getName()})
|
||||||
closureColumn(header: "Status", preferredWidth: 50, type: String, read : {row -> row.downloader.getCurrentState().toString()})
|
closureColumn(header: "Status", preferredWidth: 50, type: String, read : {row -> row.downloader.getCurrentState().toString()})
|
||||||
@@ -212,11 +264,13 @@ class MainFrameView {
|
|||||||
panel (constraints : "shared files table") {
|
panel (constraints : "shared files table") {
|
||||||
borderLayout()
|
borderLayout()
|
||||||
scrollPane(constraints : BorderLayout.CENTER) {
|
scrollPane(constraints : BorderLayout.CENTER) {
|
||||||
table(id : "shared-files-table", autoCreateRowSorter: true) {
|
table(id : "shared-files-table", autoCreateRowSorter: true, rowHeight : rowHeight) {
|
||||||
tableModel(list : model.shared) {
|
tableModel(list : model.shared) {
|
||||||
closureColumn(header : "Name", preferredWidth : 500, type : String, read : {row -> row.getCachedPath()})
|
closureColumn(header : "Name", preferredWidth : 500, type : String, read : {row -> row.getCachedPath()})
|
||||||
closureColumn(header : "Size", preferredWidth : 100, type : Long, read : {row -> row.getCachedLength() })
|
closureColumn(header : "Size", preferredWidth : 50, type : Long, read : {row -> row.getCachedLength() })
|
||||||
closureColumn(header : "Comments", preferredWidth : 100, type : Boolean, read : {it.getComment() != null})
|
closureColumn(header : "Comments", preferredWidth : 50, type : Boolean, read : {it.getComment() != null})
|
||||||
|
closureColumn(header : "Search Hits", preferredWidth: 50, type : Integer, read : {it.getHits()})
|
||||||
|
closureColumn(header : "Downloaders", preferredWidth: 50, type : Integer, read : {it.getDownloaders().size()})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -226,7 +280,7 @@ class MainFrameView {
|
|||||||
scrollPane(constraints : BorderLayout.CENTER) {
|
scrollPane(constraints : BorderLayout.CENTER) {
|
||||||
def jtree = new JTree(model.sharedTree)
|
def jtree = new JTree(model.sharedTree)
|
||||||
jtree.setCellRenderer(new SharedTreeRenderer())
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -256,33 +310,42 @@ class MainFrameView {
|
|||||||
label("Uploads")
|
label("Uploads")
|
||||||
}
|
}
|
||||||
scrollPane (constraints : BorderLayout.CENTER) {
|
scrollPane (constraints : BorderLayout.CENTER) {
|
||||||
table(id : "uploads-table") {
|
table(id : "uploads-table", rowHeight : rowHeight) {
|
||||||
tableModel(list : model.uploads) {
|
tableModel(list : model.uploads) {
|
||||||
closureColumn(header : "Name", type : String, read : {row -> row.getName() })
|
closureColumn(header : "Name", type : String, read : {row -> row.uploader.getName() })
|
||||||
closureColumn(header : "Progress", type : String, read : { row ->
|
closureColumn(header : "Progress", type : String, read : { row ->
|
||||||
int percent = row.getProgress()
|
int percent = row.uploader.getProgress()
|
||||||
"$percent% of piece".toString()
|
"$percent% of piece".toString()
|
||||||
})
|
})
|
||||||
closureColumn(header : "Downloader", type : String, read : { row ->
|
closureColumn(header : "Downloader", type : String, read : { row ->
|
||||||
row.getDownloader()
|
row.uploader.getDownloader()
|
||||||
})
|
})
|
||||||
closureColumn(header : "Remote Pieces", type : String, read : { row ->
|
closureColumn(header : "Remote Pieces", type : String, read : { row ->
|
||||||
int pieces = row.getTotalPieces()
|
int pieces = row.uploader.getTotalPieces()
|
||||||
int done = row.getDonePieces()
|
int done = row.uploader.getDonePieces()
|
||||||
|
if (row.uploader.getProgress() == 100)
|
||||||
|
done++
|
||||||
int percent = -1
|
int percent = -1
|
||||||
if ( pieces != 0 ) {
|
if ( pieces != 0 ) {
|
||||||
percent = (done * 100) / pieces
|
percent = (done * 100) / pieces
|
||||||
}
|
}
|
||||||
long size = row.getTotalSize()
|
long size = row.uploader.getTotalSize()
|
||||||
String totalSize = ""
|
String totalSize = ""
|
||||||
if (size >= 0 ) {
|
if (size >= 0 ) {
|
||||||
totalSize = " of " + DataHelper.formatSize2Decimal(size, false) + "B"
|
totalSize = " of " + DataHelper.formatSize2Decimal(size, false) + "B"
|
||||||
}
|
}
|
||||||
String.format("%02d", percent) + "% ${totalSize} ($done/$pieces pcs)".toString()
|
String.format("%02d", percent) + "% ${totalSize} ($done/$pieces pcs)".toString()
|
||||||
})
|
})
|
||||||
|
closureColumn(header : "Speed", type : String, read : { row ->
|
||||||
|
int speed = row.uploader.speed()
|
||||||
|
DataHelper.formatSize2Decimal(speed, false) + "B/sec"
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
panel (constraints : BorderLayout.SOUTH) {
|
||||||
|
button(text : "Clear Finished Uploads", clearUploadsAction)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
panel (constraints: "monitor window") {
|
panel (constraints: "monitor window") {
|
||||||
@@ -293,7 +356,7 @@ class MainFrameView {
|
|||||||
label("Connections")
|
label("Connections")
|
||||||
}
|
}
|
||||||
scrollPane(constraints : BorderLayout.CENTER) {
|
scrollPane(constraints : BorderLayout.CENTER) {
|
||||||
table(id : "connections-table") {
|
table(id : "connections-table", rowHeight : rowHeight) {
|
||||||
tableModel(list : model.connectionList) {
|
tableModel(list : model.connectionList) {
|
||||||
closureColumn(header : "Destination", preferredWidth: 250, type: String, read : { row -> row.destination.toBase32() })
|
closureColumn(header : "Destination", preferredWidth: 250, type: String, read : { row -> row.destination.toBase32() })
|
||||||
closureColumn(header : "Direction", preferredWidth: 20, type: String, read : { row ->
|
closureColumn(header : "Direction", preferredWidth: 20, type: String, read : { row ->
|
||||||
@@ -312,7 +375,7 @@ class MainFrameView {
|
|||||||
label("Incoming searches")
|
label("Incoming searches")
|
||||||
}
|
}
|
||||||
scrollPane(constraints : BorderLayout.CENTER) {
|
scrollPane(constraints : BorderLayout.CENTER) {
|
||||||
table(id : "searches-table") {
|
table(id : "searches-table", rowHeight : rowHeight) {
|
||||||
tableModel(list : model.searches) {
|
tableModel(list : model.searches) {
|
||||||
closureColumn(header : "Keywords", type : String, read : {
|
closureColumn(header : "Keywords", type : String, read : {
|
||||||
sanitized = it.search.replace('<', ' ')
|
sanitized = it.search.replace('<', ' ')
|
||||||
@@ -345,7 +408,7 @@ class MainFrameView {
|
|||||||
panel (border : etchedBorder()){
|
panel (border : etchedBorder()){
|
||||||
borderLayout()
|
borderLayout()
|
||||||
scrollPane(constraints : BorderLayout.CENTER) {
|
scrollPane(constraints : BorderLayout.CENTER) {
|
||||||
table(id : "trusted-table", autoCreateRowSorter : true) {
|
table(id : "trusted-table", autoCreateRowSorter : true, rowHeight : rowHeight) {
|
||||||
tableModel(list : model.trusted) {
|
tableModel(list : model.trusted) {
|
||||||
closureColumn(header : "Trusted Users", type : String, read : { it.getHumanReadableName() } )
|
closureColumn(header : "Trusted Users", type : String, read : { it.getHumanReadableName() } )
|
||||||
}
|
}
|
||||||
@@ -361,7 +424,7 @@ class MainFrameView {
|
|||||||
panel (border : etchedBorder()){
|
panel (border : etchedBorder()){
|
||||||
borderLayout()
|
borderLayout()
|
||||||
scrollPane(constraints : BorderLayout.CENTER) {
|
scrollPane(constraints : BorderLayout.CENTER) {
|
||||||
table(id : "distrusted-table", autoCreateRowSorter : true) {
|
table(id : "distrusted-table", autoCreateRowSorter : true, rowHeight : rowHeight) {
|
||||||
tableModel(list : model.distrusted) {
|
tableModel(list : model.distrusted) {
|
||||||
closureColumn(header: "Distrusted Users", type : String, read : { it.getHumanReadableName() } )
|
closureColumn(header: "Distrusted Users", type : String, read : { it.getHumanReadableName() } )
|
||||||
}
|
}
|
||||||
@@ -380,7 +443,7 @@ class MainFrameView {
|
|||||||
label(text : "Trust List Subscriptions")
|
label(text : "Trust List Subscriptions")
|
||||||
}
|
}
|
||||||
scrollPane(constraints : BorderLayout.CENTER) {
|
scrollPane(constraints : BorderLayout.CENTER) {
|
||||||
table(id : "subscription-table", autoCreateRowSorter : true) {
|
table(id : "subscription-table", autoCreateRowSorter : true, rowHeight : rowHeight) {
|
||||||
tableModel(list : model.subscriptions) {
|
tableModel(list : model.subscriptions) {
|
||||||
closureColumn(header : "Name", preferredWidth: 200, type: String, read : {it.persona.getHumanReadableName()})
|
closureColumn(header : "Name", preferredWidth: 200, type: String, read : {it.persona.getHumanReadableName()})
|
||||||
closureColumn(header : "Trusted", preferredWidth : 20, type: Integer, read : {it.good.size()})
|
closureColumn(header : "Trusted", preferredWidth : 20, type: Integer, read : {it.good.size()})
|
||||||
@@ -433,6 +496,27 @@ class MainFrameView {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
mainFrame.addWindowListener(new WindowAdapter(){
|
||||||
|
public void windowClosing(WindowEvent e) {
|
||||||
|
if (application.getContext().get("tray-icon")) {
|
||||||
|
if (settings.closeWarning) {
|
||||||
|
runInsideUIAsync {
|
||||||
|
Map<String, Object> args2 = new HashMap<>()
|
||||||
|
args2.put("settings", settings)
|
||||||
|
args2.put("home", model.core.home)
|
||||||
|
mvcGroup.createMVCGroup("close-warning", "Close Warning", args2)
|
||||||
|
}
|
||||||
|
} else if (settings.exitOnClose)
|
||||||
|
closeApplication()
|
||||||
|
} else {
|
||||||
|
closeApplication()
|
||||||
|
}
|
||||||
|
}})
|
||||||
|
|
||||||
|
// search field
|
||||||
|
def searchField = builder.getVariable("search-field")
|
||||||
|
|
||||||
|
// downloads table
|
||||||
def downloadsTable = builder.getVariable("downloads-table")
|
def downloadsTable = builder.getVariable("downloads-table")
|
||||||
def selectionModel = downloadsTable.getSelectionModel()
|
def selectionModel = downloadsTable.getSelectionModel()
|
||||||
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
|
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
|
||||||
@@ -551,6 +635,8 @@ class MainFrameView {
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
sharedFilesTree.addTreeExpansionListener(expansionListener)
|
||||||
|
|
||||||
// searches table
|
// searches table
|
||||||
def searchesTable = builder.getVariable("searches-table")
|
def searchesTable = builder.getVariable("searches-table")
|
||||||
JPopupMenu searchTableMenu = new JPopupMenu()
|
JPopupMenu searchTableMenu = new JPopupMenu()
|
||||||
@@ -655,6 +741,9 @@ class MainFrameView {
|
|||||||
|
|
||||||
// show tree by default
|
// show tree by default
|
||||||
showSharedFilesTree.call()
|
showSharedFilesTree.call()
|
||||||
|
|
||||||
|
// show search panel by default
|
||||||
|
showSearchWindow.call()
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void showPopupMenu(JPopupMenu menu, MouseEvent event) {
|
private static void showPopupMenu(JPopupMenu menu, MouseEvent event) {
|
||||||
@@ -800,9 +889,22 @@ class MainFrameView {
|
|||||||
showPopupMenu(menu, e)
|
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 showSearchWindow = {
|
||||||
def cardsPanel = builder.getVariable("cards-panel")
|
def cardsPanel = builder.getVariable("cards-panel")
|
||||||
cardsPanel.getLayout().show(cardsPanel, "search window")
|
cardsPanel.getLayout().show(cardsPanel, "search window")
|
||||||
|
|
||||||
|
showRestoreOrEmpty()
|
||||||
|
|
||||||
model.searchesPaneButtonEnabled = false
|
model.searchesPaneButtonEnabled = false
|
||||||
model.downloadsPaneButtonEnabled = true
|
model.downloadsPaneButtonEnabled = true
|
||||||
model.uploadsPaneButtonEnabled = true
|
model.uploadsPaneButtonEnabled = true
|
||||||
@@ -890,8 +992,52 @@ class MainFrameView {
|
|||||||
public void refreshSharedFiles() {
|
public void refreshSharedFiles() {
|
||||||
def tree = builder.getVariable("shared-files-tree")
|
def tree = builder.getVariable("shared-files-tree")
|
||||||
TreePath[] selectedPaths = tree.getSelectionPaths()
|
TreePath[] selectedPaths = tree.getSelectionPaths()
|
||||||
|
Set<TreePath> expanded = new HashSet<>(expansionListener.expandedPaths)
|
||||||
|
|
||||||
model.sharedTree.nodeStructureChanged(model.treeRoot)
|
model.sharedTree.nodeStructureChanged(model.treeRoot)
|
||||||
|
|
||||||
|
expanded.each { tree.expandPath(it) }
|
||||||
tree.setSelectionPaths(selectedPaths)
|
tree.setSelectionPaths(selectedPaths)
|
||||||
|
|
||||||
builder.getVariable("shared-files-table").model.fireTableDataChanged()
|
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)
|
||||||
|
if (core != null) {
|
||||||
|
Thread t = new Thread({
|
||||||
|
core.shutdown()
|
||||||
|
application.shutdown()
|
||||||
|
}as Runnable)
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user