Compare commits

...

19 Commits

Author SHA1 Message Date
34d9165bd5 Release 0.1.10 2019-06-14 16:43:28 +01:00
2e52dd5c49 fix overwriting of custom nickname 2019-06-14 16:20:21 +01:00
2a315dd734 add option to exclude local results from searches 2019-06-14 14:48:01 +01:00
6b661b99c5 fix sorting by size in shared files table 2019-06-14 13:47:35 +01:00
5dacd60bbb hook up cleaning up of cancelled/finished downloads 2019-06-14 13:11:20 +01:00
f8f7cfe836 UI options panel 2019-06-14 12:51:27 +01:00
0b4f261bc1 ability to not show monitor panel 2019-06-14 12:21:14 +01:00
042d67d784 fix selection of size column 2019-06-14 11:46:31 +01:00
800df88f14 proper sorting by size 2019-06-14 11:10:19 +01:00
4d1eac50a0 update readme for sorting bug 2019-06-14 10:39:58 +01:00
c48df7f14b Release 0.1.9 2019-06-13 22:57:08 +01:00
9d04148001 remember loaded downloads from previous sessions 2019-06-13 22:53:23 +01:00
bb4d522572 Release 0.1.8 2019-06-13 15:27:06 +01:00
8052501e52 increase persistence interval to 15 seconds 2019-06-13 15:25:30 +01:00
66cc6d8ab7 reduce piece size by factor of 8 2019-06-13 15:24:26 +01:00
a45e57f5ec Release 0.1.7 2019-06-13 10:28:44 +01:00
7d8ca55d87 fix emiting of download finished event 2019-06-13 10:27:18 +01:00
de22f3c6b9 use metal lnf on java 9 or newer 2019-06-13 05:02:11 +01:00
3b0eb5678d update wire protocol 2019-06-12 23:46:48 +01:00
19 changed files with 186 additions and 24 deletions

View File

@ -31,4 +31,4 @@ The first time you run MuWire it will ask you to select a nickname. This nickna
### Known bugs and limitations
* Many UI features you would expect are not there yet
* Sorting the results table sometimes causes the wrong result to be downloaded

View File

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

View File

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

View File

@ -92,9 +92,9 @@ public class Core {
if (i2pOptionsFile.exists()) {
i2pOptionsFile.withInputStream { i2pOptions.load(it) }
if (!i2pOptions.hasProperty("inbound.nickname"))
if (!i2pOptions.containsKey("inbound.nickname"))
i2pOptions["inbound.nickname"] = "MuWire"
if (!i2pOptions.hasProperty("outbound.nickname"))
if (!i2pOptions.containsKey("outbound.nickname"))
i2pOptions["outbound.nickname"] = "MuWire"
} else {
i2pOptions["inbound.nickname"] = "MuWire"
@ -160,7 +160,7 @@ public class Core {
eventBus.register(SearchEvent.class, fileManager)
log.info "initializing persistence service"
persisterService = new PersisterService(new File(home, "files.json"), eventBus, 5000, fileManager)
persisterService = new PersisterService(new File(home, "files.json"), eventBus, 15000, fileManager)
log.info("initializing host cache")
File hostStorage = new File(home, "hosts.json")
@ -260,7 +260,7 @@ public class Core {
}
}
Core core = new Core(props, home, "0.1.6")
Core core = new Core(props, home, "0.1.10")
core.startServices()
// ... at the end, sleep or execute script

View File

@ -99,6 +99,7 @@ public class DownloadManager {
}
def downloader = new Downloader(eventBus, this, me, file, (long)json.length,
infoHash, json.pieceSizePow2, connector, destinations, incompletes)
downloaders.add(downloader)
downloader.download()
eventBus.publish(new DownloadStartedEvent(downloader : downloader))
}

View File

@ -239,9 +239,12 @@ public class Downloader {
if (downloaded.isComplete() && !eventFired) {
piecesFile.delete()
eventFired = true
eventBus.publish(new FileDownloadedEvent(downloadedFile : new DownloadedFile(file, getInfoHash(), pieceSizePow2, Collections.emptySet())),
downloader : Downloader.this)
}
eventBus.publish(
new FileDownloadedEvent(
downloadedFile : new DownloadedFile(file, getInfoHash(), pieceSizePow2, Collections.emptySet()),
downloader : Downloader.this))
}
endpoint?.close()
}
}

View File

@ -20,12 +20,12 @@ class FileHasher {
* @return the size of each piece in power of 2
*/
static int getPieceSize(long size) {
if (size <= 0x1 << 27)
if (size <= 0x1 << 30)
return 17
for (int i = 28; i <= 37; i++) {
for (int i = 31; i <= 37; i++) {
if (size <= 0x1L << i) {
return i-10
return i-13
}
}

View File

@ -181,6 +181,8 @@ Search results are sent through and HTTP POST method from the responder to the o
* The "altlocs" list contains list of alternate personas that the responder thinks may also have the file.
* The "pieceSize" field is the size of the each individual file piece (except possibly the last) in powers of 2
Results version 1 contain the full hashlist, version 2 does not contain that list. See the "infohash-upgrade" document for more information.
### "Who do you trust" query - any node to any node
(See the "web-of-trust" document for more info on this query)

View File

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

View File

@ -66,6 +66,38 @@ class OptionsController {
settings.write(it)
}
// UI Setttings
UISettings uiSettings = application.context.get("ui-settings")
text = view.lnfField.text
model.lnf = text
uiSettings.lnf = text
text = view.fontField.text
model.font = text
uiSettings.font = text
boolean showMonitor = view.monitorCheckbox.model.isSelected()
model.showMonitor = showMonitor
uiSettings.showMonitor = showMonitor
boolean clearCancelledDownloads = view.clearCancelledDownloadsCheckbox.model.isSelected()
model.clearCancelledDownloads = clearCancelledDownloads
uiSettings.clearCancelledDownloads = clearCancelledDownloads
boolean clearFinishedDownloads = view.clearFinishedDownloadsCheckbox.model.isSelected()
model.clearFinishedDownloads = clearFinishedDownloads
uiSettings.clearFinishedDownloads = clearFinishedDownloads
boolean excludeLocalResult = view.excludeLocalResultCheckbox.model.isSelected()
model.excludeLocalResult = excludeLocalResult
uiSettings.excludeLocalResult = excludeLocalResult
File uiSettingsFile = new File(core.home, "gui.properties")
uiSettingsFile.withOutputStream {
uiSettings.write(it)
}
cancel()
}

View File

@ -69,8 +69,13 @@ class Initialize extends AbstractLifecycleHandler {
uiSettings = new UISettings(props)
log.info "will try default lnfs"
if (isMacOSX()) {
uiSettings.lnf = "nimbus"
lookAndFeel('nimbus') // otherwise the file chooser doesn't open???
if (SystemVersion.isJava9()) {
uiSettings.lnf = "metal"
lookAndFeel("metal")
} else {
uiSettings.lnf = "nimbus"
lookAndFeel('nimbus') // otherwise the file chooser doesn't open???
}
} else {
LookAndFeel chosen = lookAndFeel('system', 'gtk')
uiSettings.lnf = chosen.name

View File

@ -79,11 +79,28 @@ class MainFrameModel {
void mvcGroupInit(Map<String, Object> args) {
UISettings uiSettings = application.context.get("ui-settings")
Timer timer = new Timer("download-pumper", true)
timer.schedule({
runInsideUIAsync {
if (!mvcGroup.alive)
return
// remove cancelled or finished downloads
def toRemove = []
downloads.each {
if (uiSettings.clearCancelledDownloads &&
it.downloader.getCurrentState() == Downloader.DownloadState.CANCELLED)
toRemove << it
if (uiSettings.clearFinishedDownloads &&
it.downloader.getCurrentState() == Downloader.DownloadState.FINISHED)
toRemove << it
}
toRemove.each {
downloads.remove(it)
}
builder.getVariable("uploads-table")?.model.fireTableDataChanged()
updateTablePreservingSelection("downloads-table")

View File

@ -20,6 +20,14 @@ class OptionsModel {
@Observable String outboundLength
@Observable String outboundQuantity
// gui options
@Observable boolean showMonitor
@Observable String lnf
@Observable String font
@Observable boolean clearCancelledDownloads
@Observable boolean clearFinishedDownloads
@Observable boolean excludeLocalResult
void mvcGroupInit(Map<String, String> args) {
MuWireSettings settings = application.context.get("muwire-settings")
downloadRetryInterval = settings.downloadRetryInterval
@ -32,5 +40,13 @@ class OptionsModel {
inboundQuantity = core.i2pOptions["inbound.quantity"]
outboundLength = core.i2pOptions["outbound.length"]
outboundQuantity = core.i2pOptions["outbound.quantity"]
UISettings uiSettings = application.context.get("ui-settings")
showMonitor = uiSettings.showMonitor
lnf = uiSettings.lnf
font = uiSettings.font
clearCancelledDownloads = uiSettings.clearCancelledDownloads
clearFinishedDownloads = uiSettings.clearFinishedDownloads
excludeLocalResult = uiSettings.excludeLocalResult
}
}

View File

@ -19,6 +19,7 @@ class SearchTabModel {
FactoryBuilderSupport builder
Core core
UISettings uiSettings
String uuid
def results = []
def hashBucket = [:]
@ -26,6 +27,7 @@ class SearchTabModel {
void mvcGroupInit(Map<String, String> args) {
core = mvcGroup.parentGroup.model.core
uiSettings = application.context.get("ui-settings")
mvcGroup.parentGroup.model.results[UUID.fromString(uuid)] = mvcGroup
}
@ -34,6 +36,9 @@ class SearchTabModel {
}
void handleResult(UIResultEvent e) {
if (uiSettings.excludeLocalResult &&
e.sender == core.me)
return
runInsideUIAsync {
def bucket = hashBucket.get(e.infohash)
if (bucket == null) {

View File

@ -41,6 +41,7 @@ class MainFrameView {
def lastDownloadSortEvent
void initUI() {
UISettings settings = application.context.get("ui-settings")
builder.with {
application(size : [1024,768], id: 'main-frame',
locationRelativeTo : null,
@ -63,7 +64,8 @@ class MainFrameView {
gridLayout(rows:1, cols: 2)
button(text: "Searches", actionPerformed : showSearchWindow)
button(text: "Uploads", actionPerformed : showUploadsWindow)
button(text: "Monitor", actionPerformed : showMonitorWindow)
if (settings.showMonitor)
button(text: "Monitor", actionPerformed : showMonitorWindow)
button(text: "Trust", actionPerformed : showTrustWindow)
}
panel(id: "top-panel", constraints: BorderLayout.CENTER) {
@ -142,8 +144,7 @@ class MainFrameView {
table(id : "shared-files-table", autoCreateRowSorter: true) {
tableModel(list : model.shared) {
closureColumn(header : "Name", preferredWidth : 550, type : String, read : {row -> row.file.getAbsolutePath()})
closureColumn(header : "Size", preferredWidth : 50, type : String,
read : {row -> DataHelper.formatSize2Decimal(row.file.length(),false) + "B"})
closureColumn(header : "Size", preferredWidth : 50, type : Long, read : {row -> row.file.length() })
}
}
}
@ -264,7 +265,9 @@ class MainFrameView {
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
selectionModel.addListSelectionListener({
int selectedRow = selectedDownloaderRow()
def downloader = model.downloads[selectedRow].downloader
def downloader = model.downloads[selectedRow]?.downloader
if (downloader == null)
return
switch(downloader.getCurrentState()) {
case Downloader.DownloadState.CONNECTING :
case Downloader.DownloadState.DOWNLOADING :
@ -286,6 +289,10 @@ class MainFrameView {
downloadsTable.setDefaultRenderer(Integer.class, centerRenderer)
downloadsTable.rowSorter.addRowSorterListener({evt -> lastDownloadSortEvent = evt})
// shared files table
def sharedFilesTable = builder.getVariable("shared-files-table")
sharedFilesTable.columnModel.getColumn(1).setCellRenderer(new SizeRenderer())
}
int selectedDownloaderRow() {

View File

@ -25,6 +25,8 @@ class OptionsView {
def d
def p
def i
def u
def retryField
def updateField
def allowUntrustedCheckbox
@ -35,6 +37,13 @@ class OptionsView {
def outboundLengthField
def outboundQuantityField
def lnfField
def monitorCheckbox
def fontField
def clearCancelledDownloadsCheckbox
def clearFinishedDownloadsCheckbox
def excludeLocalResultCheckbox
def buttonsPanel
def mainFrame
@ -72,6 +81,22 @@ class OptionsView {
label(text : "Outbound Quantity", constraints : gbc(gridx:0, gridy:4))
outboundQuantityField = textField(text : bind {model.outboundQuantity}, columns : 2, constraints : gbc(gridx:1, gridy:4))
}
u = builder.panel {
gridBagLayout()
label(text : "Changing these settings requires a restart", constraints : gbc(gridx : 0, gridy : 0, gridwidth: 2))
label(text : "Look And Feel", constraints : gbc(gridx: 0, gridy:1))
lnfField = textField(text : bind {model.lnf}, columns : 4, constraints : gbc(gridx : 1, gridy : 1))
label(text : "Font", constraints : gbc(gridx: 0, gridy : 2))
fontField = textField(text : bind {model.font}, columns : 4, constraints : gbc(gridx : 1, gridy:2))
label(text : "Show Monitor", constraints : gbc(gridx :0, gridy: 3))
monitorCheckbox = checkBox(selected : bind {model.showMonitor}, constraints : gbc(gridx : 1, gridy: 3))
label(text : "Clear Cancelled Downloads", constraints: gbc(gridx: 0, gridy:4))
clearCancelledDownloadsCheckbox = checkBox(selected : bind {model.clearCancelledDownloads}, constraints : gbc(gridx : 1, gridy:4))
label(text : "Clear Finished Downloads", constraints: gbc(gridx: 0, gridy:5))
clearFinishedDownloadsCheckbox = checkBox(selected : bind {model.clearFinishedDownloads}, constraints : gbc(gridx : 1, gridy:5))
label(text : "Exclude Local Files From Results", constraints: gbc(gridx:0, gridy:6))
excludeLocalResultCheckbox = checkBox(selected : bind {model.excludeLocalResult}, constraints : gbc(gridx: 1, gridy : 6))
}
buttonsPanel = builder.panel {
gridBagLayout()
button(text : "Save", constraints : gbc(gridx : 1, gridy: 2), saveAction)
@ -81,8 +106,9 @@ class OptionsView {
void mvcGroupInit(Map<String,String> args) {
def tabbedPane = new JTabbedPane()
tabbedPane.addTab("MuWire Options", p)
tabbedPane.addTab("I2P Options", i)
tabbedPane.addTab("MuWire", p)
tabbedPane.addTab("I2P", i)
tabbedPane.addTab("GUI", u)
JPanel panel = new JPanel()
panel.setLayout(new BorderLayout())

View File

@ -6,12 +6,17 @@ import griffon.inject.MVCMember
import griffon.metadata.ArtifactProviderFor
import net.i2p.data.DataHelper
import javax.swing.JComponent
import javax.swing.JLabel
import javax.swing.JTable
import javax.swing.ListSelectionModel
import javax.swing.SwingConstants
import javax.swing.table.DefaultTableCellRenderer
import com.muwire.core.util.DataUtil
import java.awt.BorderLayout
import java.awt.Color
import javax.annotation.Nonnull
@ -35,7 +40,7 @@ class SearchTabView {
resultsTable = table(id : "results-table", autoCreateRowSorter : true) {
tableModel(list: model.results) {
closureColumn(header: "Name", preferredWidth: 350, type: String, read : {row -> row.name.replace('<','_')})
closureColumn(header: "Size", preferredWidth: 50, type: String, read : {row -> DataHelper.formatSize2Decimal(row.size, false)+"B"})
closureColumn(header: "Size", preferredWidth: 50, type: Long, read : {row -> row.size})
closureColumn(header: "Sources", preferredWidth: 10, type : Integer, read : { row -> model.hashBucket[row.infohash].size()})
closureColumn(header: "Sender", preferredWidth: 170, type: String, read : {row -> row.sender.getHumanReadableName()})
closureColumn(header: "Trust", preferredWidth: 50, type: String, read : {row ->
@ -86,6 +91,9 @@ class SearchTabView {
resultsTable.setDefaultRenderer(Integer.class,centerRenderer)
resultsTable.columnModel.getColumn(4).setCellRenderer(centerRenderer)
resultsTable.columnModel.getColumn(1).setCellRenderer(new SizeRenderer())
resultsTable.rowSorter.addRowSorterListener({ evt -> lastSortEvent = evt})
}

View File

@ -0,0 +1,29 @@
package com.muwire.gui
import javax.swing.JComponent
import javax.swing.JLabel
import javax.swing.JTable
import javax.swing.table.DefaultTableCellRenderer
import net.i2p.data.DataHelper
class SizeRenderer extends DefaultTableCellRenderer {
SizeRenderer() {
setHorizontalAlignment(JLabel.CENTER)
}
@Override
JComponent getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
Long l = (Long) value
String formatted = DataHelper.formatSize2Decimal(l, false)+"B"
setText(formatted)
if (isSelected) {
setForeground(table.getSelectionForeground())
setBackground(table.getSelectionBackground())
} else {
setForeground(table.getForeground())
setBackground(table.getBackground())
}
this
}
}

View File

@ -5,19 +5,30 @@ class UISettings {
String lnf
boolean showMonitor
String font
boolean clearCancelledDownloads
boolean clearFinishedDownloads
boolean excludeLocalResult
UISettings(Properties props) {
lnf = props.getProperty("lnf", "system")
showMonitor = Boolean.parseBoolean(props.getProperty("showMonitor", "true"))
font = props.getProperty("font",null)
clearCancelledDownloads = Boolean.parseBoolean(props.getProperty("clearCancelledDownloads","false"))
clearFinishedDownloads = Boolean.parseBoolean(props.getProperty("clearFinishedDownloads","false"))
excludeLocalResult = Boolean.parseBoolean(props.getProperty("excludeLocalResult","false"))
}
void write(OutputStream out) throws IOException {
Properties props = new Properties()
props.setProperty("lnf", lnf)
props.setProperty("showMonitor", showMonitor)
props.setProperty("showMonitor", String.valueOf(showMonitor))
props.setProperty("clearCancelledDownloads", String.valueOf(clearCancelledDownloads))
props.setProperty("clearFinishedDownloads", String.valueOf(clearFinishedDownloads))
props.setProperty("excludeLocalResult", String.valueOf(excludeLocalResult))
if (font != null)
props.setProperty("font", font)
props.store(out, "UI Properties")
}
}