Compare commits

...

26 Commits

Author SHA1 Message Date
Zlatin Balevsky
040248560a Release 0.1.11 2019-06-14 22:26:28 +01:00
Zlatin Balevsky
77caaf83de reset instead of close 2019-06-14 22:08:25 +01:00
Zlatin Balevsky
cc5ece5103 do not throw exception on shutdown 2019-06-14 21:36:50 +01:00
Zlatin Balevsky
db7e21e343 close connections in parallel, more shutdown fixes 2019-06-14 21:25:22 +01:00
Zlatin Balevsky
a388eaec1d shutdown all connections on shutdown 2019-06-14 20:53:54 +01:00
Zlatin Balevsky
8ff39072c7 download file on double-clicking a result 2019-06-14 20:42:26 +01:00
Zlatin Balevsky
55d2ac9b24 delete partial files and pieces file on cancel 2019-06-14 20:27:14 +01:00
Zlatin Balevsky
6ebe492fd8 if nothing is enabled cancel and retry buttons are disabled 2019-06-14 18:37:18 +01:00
Zlatin Balevsky
165cd542ec work around not having a selected row while cancelling a download 2019-06-14 18:28:00 +01:00
Zlatin Balevsky
5ca0c8b00d wip on unshare selected files popup menu 2019-06-14 18:08:56 +01:00
Zlatin Balevsky
b6a38e3f23 revert to default lnf if the desired one fails 2019-06-14 18:01:14 +01:00
Zlatin Balevsky
34d9165bd5 Release 0.1.10 2019-06-14 16:43:28 +01:00
Zlatin Balevsky
2e52dd5c49 fix overwriting of custom nickname 2019-06-14 16:20:21 +01:00
Zlatin Balevsky
2a315dd734 add option to exclude local results from searches 2019-06-14 14:48:01 +01:00
Zlatin Balevsky
6b661b99c5 fix sorting by size in shared files table 2019-06-14 13:47:35 +01:00
Zlatin Balevsky
5dacd60bbb hook up cleaning up of cancelled/finished downloads 2019-06-14 13:11:20 +01:00
Zlatin Balevsky
f8f7cfe836 UI options panel 2019-06-14 12:51:27 +01:00
Zlatin Balevsky
0b4f261bc1 ability to not show monitor panel 2019-06-14 12:21:14 +01:00
Zlatin Balevsky
042d67d784 fix selection of size column 2019-06-14 11:46:31 +01:00
Zlatin Balevsky
800df88f14 proper sorting by size 2019-06-14 11:10:19 +01:00
Zlatin Balevsky
4d1eac50a0 update readme for sorting bug 2019-06-14 10:39:58 +01:00
Zlatin Balevsky
c48df7f14b Release 0.1.9 2019-06-13 22:57:08 +01:00
Zlatin Balevsky
9d04148001 remember loaded downloads from previous sessions 2019-06-13 22:53:23 +01:00
Zlatin Balevsky
bb4d522572 Release 0.1.8 2019-06-13 15:27:06 +01:00
Zlatin Balevsky
8052501e52 increase persistence interval to 15 seconds 2019-06-13 15:25:30 +01:00
Zlatin Balevsky
66cc6d8ab7 reduce piece size by factor of 8 2019-06-13 15:24:26 +01:00
23 changed files with 256 additions and 26 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 ### Known bugs and limitations
* Many UI features you would expect are not there yet * 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 Core core
try { try {
core = new Core(props, home, "0.1.7") core = new Core(props, home, "0.1.11")
} 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"

View File

@@ -53,7 +53,7 @@ class CliDownloader {
Core core Core core
try { try {
core = new Core(props, home, "0.1.7") core = new Core(props, home, "0.1.11")
} 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"

View File

@@ -69,6 +69,7 @@ public class Core {
private final ConnectionAcceptor connectionAcceptor private final ConnectionAcceptor connectionAcceptor
private final ConnectionEstablisher connectionEstablisher private final ConnectionEstablisher connectionEstablisher
private final HasherService hasherService private final HasherService hasherService
private final DownloadManager downloadManager
public Core(MuWireSettings props, File home, String myVersion) { public Core(MuWireSettings props, File home, String myVersion) {
this.home = home this.home = home
@@ -92,9 +93,9 @@ public class Core {
if (i2pOptionsFile.exists()) { if (i2pOptionsFile.exists()) {
i2pOptionsFile.withInputStream { i2pOptions.load(it) } i2pOptionsFile.withInputStream { i2pOptions.load(it) }
if (!i2pOptions.hasProperty("inbound.nickname")) if (!i2pOptions.containsKey("inbound.nickname"))
i2pOptions["inbound.nickname"] = "MuWire" i2pOptions["inbound.nickname"] = "MuWire"
if (!i2pOptions.hasProperty("outbound.nickname")) if (!i2pOptions.containsKey("outbound.nickname"))
i2pOptions["outbound.nickname"] = "MuWire" i2pOptions["outbound.nickname"] = "MuWire"
} else { } else {
i2pOptions["inbound.nickname"] = "MuWire" i2pOptions["inbound.nickname"] = "MuWire"
@@ -160,7 +161,7 @@ public class Core {
eventBus.register(SearchEvent.class, fileManager) eventBus.register(SearchEvent.class, fileManager)
log.info "initializing persistence service" 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") log.info("initializing host cache")
File hostStorage = new File(home, "hosts.json") File hostStorage = new File(home, "hosts.json")
@@ -195,7 +196,7 @@ public class Core {
eventBus.register(ResultsEvent.class, searchManager) eventBus.register(ResultsEvent.class, searchManager)
log.info("initializing download manager") log.info("initializing download manager")
DownloadManager downloadManager = new DownloadManager(eventBus, i2pConnector, home, me) downloadManager = new DownloadManager(eventBus, i2pConnector, home, me)
eventBus.register(UIDownloadEvent.class, downloadManager) eventBus.register(UIDownloadEvent.class, downloadManager)
eventBus.register(UILoadedEvent.class, downloadManager) eventBus.register(UILoadedEvent.class, downloadManager)
eventBus.register(FileDownloadedEvent.class, downloadManager) eventBus.register(FileDownloadedEvent.class, downloadManager)
@@ -233,7 +234,14 @@ public class Core {
} }
public void shutdown() { public void shutdown() {
log.info("shutting down connection manager")
connectionManager.shutdown() connectionManager.shutdown()
log.info("shutting down download manageer")
downloadManager.shutdown()
log.info("shutting down connection acceeptor")
connectionAcceptor.stop()
log.info("shutting down connection establisher")
connectionEstablisher.stop()
} }
static main(args) { static main(args) {
@@ -260,7 +268,7 @@ public class Core {
} }
} }
Core core = new Core(props, home, "0.1.7") Core core = new Core(props, home, "0.1.11")
core.startServices() core.startServices()
// ... at the end, sleep or execute script // ... at the end, sleep or execute script

View File

@@ -76,9 +76,9 @@ abstract class Connection implements Closeable {
return return
} }
log.info("closing $name") log.info("closing $name")
endpoint.close()
reader.interrupt() reader.interrupt()
writer.interrupt() writer.interrupt()
endpoint.close()
eventBus.publish(new DisconnectionEvent(destination: endpoint.destination)) eventBus.publish(new DisconnectionEvent(destination: endpoint.destination))
} }

View File

@@ -38,6 +38,8 @@ class ConnectionAcceptor {
final ExecutorService acceptorThread final ExecutorService acceptorThread
final ExecutorService handshakerThreads final ExecutorService handshakerThreads
private volatile shutdown
ConnectionAcceptor(EventBus eventBus, UltrapeerConnectionManager manager, ConnectionAcceptor(EventBus eventBus, UltrapeerConnectionManager manager,
MuWireSettings settings, I2PAcceptor acceptor, HostCache hostCache, MuWireSettings settings, I2PAcceptor acceptor, HostCache hostCache,
@@ -73,11 +75,13 @@ class ConnectionAcceptor {
} }
void stop() { void stop() {
shutdown = true
acceptorThread.shutdownNow() acceptorThread.shutdownNow()
handshakerThreads.shutdownNow() handshakerThreads.shutdownNow()
} }
private void acceptLoop() { private void acceptLoop() {
try {
while(true) { while(true) {
def incoming = acceptor.accept() def incoming = acceptor.accept()
log.info("accepted connection from ${incoming.destination.toBase32()}") log.info("accepted connection from ${incoming.destination.toBase32()}")
@@ -93,6 +97,11 @@ class ConnectionAcceptor {
} }
handshakerThreads.execute({processIncoming(incoming)} as Runnable) handshakerThreads.execute({processIncoming(incoming)} as Runnable)
} }
} catch (Exception e) {
log.log(Level.WARNING, "exception in accept loop",e)
if (!shutdown)
throw e
}
} }
private void processIncoming(Endpoint e) { private void processIncoming(Endpoint e) {

View File

@@ -34,7 +34,7 @@ class Endpoint implements Closeable {
try {outputStream.close()} catch (Exception ignore) {} try {outputStream.close()} catch (Exception ignore) {}
} }
if (toClose != null) { if (toClose != null) {
try {toClose.close()} catch (Exception ignore) {} try {toClose.reset()} catch (Exception ignore) {}
} }
} }

View File

@@ -104,8 +104,8 @@ class UltrapeerConnectionManager extends ConnectionManager {
@Override @Override
void shutdown() { void shutdown() {
peerConnections.each {k,v -> v.close() } peerConnections.values().stream().parallel().forEach({v -> v.close()})
leafConnections.each {k,v -> v.close() } leafConnections.values().stream().parallel().forEach({v -> v.close()})
peerConnections.clear() peerConnections.clear()
leafConnections.clear() leafConnections.clear()
} }

View File

@@ -99,6 +99,7 @@ public class DownloadManager {
} }
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) infoHash, json.pieceSizePow2, connector, destinations, incompletes)
downloaders.add(downloader)
downloader.download() downloader.download()
eventBus.publish(new DownloadStartedEvent(downloader : downloader)) eventBus.publish(new DownloadStartedEvent(downloader : downloader))
} }
@@ -134,4 +135,9 @@ public class DownloadManager {
} }
} }
} }
public void shutdown() {
downloaders.each { it.stop() }
Downloader.executorService.shutdownNow()
}
} }

View File

@@ -169,6 +169,12 @@ public class Downloader {
public void cancel() { public void cancel() {
cancelled = true cancelled = true
stop()
file.delete()
piecesFile.delete()
}
void stop() {
activeWorkers.values().each { activeWorkers.values().each {
it.cancel() it.cancel()
} }

View File

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

View File

@@ -1,5 +1,5 @@
group = com.muwire group = com.muwire
version = 0.1.7 version = 0.1.11
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

View File

@@ -181,6 +181,10 @@ class MainFrameController {
model.hashSearch = true model.hashSearch = true
} }
void unshareSelectedFiles() {
println "unsharing selected files"
}
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()

View File

@@ -66,6 +66,38 @@ class OptionsController {
settings.write(it) 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() cancel()
} }

View File

@@ -18,6 +18,7 @@ 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.util.logging.Level
@Log @Log
class Initialize extends AbstractLifecycleHandler { class Initialize extends AbstractLifecycleHandler {
@@ -50,7 +51,12 @@ class Initialize extends AbstractLifecycleHandler {
uiSettings = new UISettings(props) uiSettings = new UISettings(props)
log.info("settting user-specified lnf $uiSettings.lnf") log.info("settting user-specified lnf $uiSettings.lnf")
lookAndFeel(uiSettings.lnf) try {
lookAndFeel(uiSettings.lnf)
} catch (Throwable bad) {
log.log(Level.WARNING,"couldn't set desired look and feeel, switching to defaults", bad)
uiSettings.lnf = lookAndFeel("system","gtk","metal").getID()
}
if (uiSettings.font != null) { if (uiSettings.font != null) {
log.info("setting user-specified font $uiSettings.font") log.info("setting user-specified font $uiSettings.font")
@@ -78,7 +84,7 @@ class Initialize extends AbstractLifecycleHandler {
} }
} else { } else {
LookAndFeel chosen = lookAndFeel('system', 'gtk') LookAndFeel chosen = lookAndFeel('system', 'gtk')
uiSettings.lnf = chosen.name uiSettings.lnf = chosen.getID()
log.info("ended up applying $chosen.name") log.info("ended up applying $chosen.name")
} }
} }

View File

@@ -79,11 +79,28 @@ class MainFrameModel {
void mvcGroupInit(Map<String, Object> args) { void mvcGroupInit(Map<String, Object> args) {
UISettings uiSettings = application.context.get("ui-settings")
Timer timer = new Timer("download-pumper", true) Timer timer = new Timer("download-pumper", true)
timer.schedule({ timer.schedule({
runInsideUIAsync { runInsideUIAsync {
if (!mvcGroup.alive) if (!mvcGroup.alive)
return 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() builder.getVariable("uploads-table")?.model.fireTableDataChanged()
updateTablePreservingSelection("downloads-table") updateTablePreservingSelection("downloads-table")

View File

@@ -20,6 +20,14 @@ class OptionsModel {
@Observable String outboundLength @Observable String outboundLength
@Observable String outboundQuantity @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) { void mvcGroupInit(Map<String, String> args) {
MuWireSettings settings = application.context.get("muwire-settings") MuWireSettings settings = application.context.get("muwire-settings")
downloadRetryInterval = settings.downloadRetryInterval downloadRetryInterval = settings.downloadRetryInterval
@@ -32,5 +40,13 @@ class OptionsModel {
inboundQuantity = core.i2pOptions["inbound.quantity"] inboundQuantity = core.i2pOptions["inbound.quantity"]
outboundLength = core.i2pOptions["outbound.length"] outboundLength = core.i2pOptions["outbound.length"]
outboundQuantity = core.i2pOptions["outbound.quantity"] 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 FactoryBuilderSupport builder
Core core Core core
UISettings uiSettings
String uuid String uuid
def results = [] def results = []
def hashBucket = [:] def hashBucket = [:]
@@ -26,6 +27,7 @@ class SearchTabModel {
void mvcGroupInit(Map<String, String> args) { void mvcGroupInit(Map<String, String> args) {
core = mvcGroup.parentGroup.model.core core = mvcGroup.parentGroup.model.core
uiSettings = application.context.get("ui-settings")
mvcGroup.parentGroup.model.results[UUID.fromString(uuid)] = mvcGroup mvcGroup.parentGroup.model.results[UUID.fromString(uuid)] = mvcGroup
} }
@@ -34,6 +36,9 @@ class SearchTabModel {
} }
void handleResult(UIResultEvent e) { void handleResult(UIResultEvent e) {
if (uiSettings.excludeLocalResult &&
e.sender == core.me)
return
runInsideUIAsync { runInsideUIAsync {
def bucket = hashBucket.get(e.infohash) def bucket = hashBucket.get(e.infohash)
if (bucket == null) { if (bucket == null) {

View File

@@ -10,6 +10,8 @@ import javax.swing.Box
import javax.swing.BoxLayout import javax.swing.BoxLayout
import javax.swing.JFileChooser import javax.swing.JFileChooser
import javax.swing.JLabel import javax.swing.JLabel
import javax.swing.JMenuItem
import javax.swing.JPopupMenu
import javax.swing.JSplitPane import javax.swing.JSplitPane
import javax.swing.ListSelectionModel import javax.swing.ListSelectionModel
import javax.swing.SwingConstants import javax.swing.SwingConstants
@@ -26,6 +28,8 @@ import java.awt.FlowLayout
import java.awt.GridBagConstraints import java.awt.GridBagConstraints
import java.awt.GridBagLayout import java.awt.GridBagLayout
import java.awt.Insets import java.awt.Insets
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import javax.annotation.Nonnull import javax.annotation.Nonnull
@@ -41,6 +45,7 @@ class MainFrameView {
def lastDownloadSortEvent def lastDownloadSortEvent
void initUI() { void initUI() {
UISettings settings = application.context.get("ui-settings")
builder.with { builder.with {
application(size : [1024,768], id: 'main-frame', application(size : [1024,768], id: 'main-frame',
locationRelativeTo : null, locationRelativeTo : null,
@@ -63,7 +68,8 @@ class MainFrameView {
gridLayout(rows:1, cols: 2) gridLayout(rows:1, cols: 2)
button(text: "Searches", actionPerformed : showSearchWindow) button(text: "Searches", actionPerformed : showSearchWindow)
button(text: "Uploads", actionPerformed : showUploadsWindow) button(text: "Uploads", actionPerformed : showUploadsWindow)
button(text: "Monitor", actionPerformed : showMonitorWindow) if (settings.showMonitor)
button(text: "Monitor", actionPerformed : showMonitorWindow)
button(text: "Trust", actionPerformed : showTrustWindow) button(text: "Trust", actionPerformed : showTrustWindow)
} }
panel(id: "top-panel", constraints: BorderLayout.CENTER) { panel(id: "top-panel", constraints: BorderLayout.CENTER) {
@@ -142,8 +148,7 @@ class MainFrameView {
table(id : "shared-files-table", autoCreateRowSorter: true) { table(id : "shared-files-table", autoCreateRowSorter: true) {
tableModel(list : model.shared) { tableModel(list : model.shared) {
closureColumn(header : "Name", preferredWidth : 550, type : String, read : {row -> row.file.getAbsolutePath()}) closureColumn(header : "Name", preferredWidth : 550, type : String, read : {row -> row.file.getAbsolutePath()})
closureColumn(header : "Size", preferredWidth : 50, type : String, closureColumn(header : "Size", preferredWidth : 50, type : Long, read : {row -> row.file.length() })
read : {row -> DataHelper.formatSize2Decimal(row.file.length(),false) + "B"})
} }
} }
} }
@@ -264,7 +269,14 @@ class MainFrameView {
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION) selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
selectionModel.addListSelectionListener({ selectionModel.addListSelectionListener({
int selectedRow = selectedDownloaderRow() int selectedRow = selectedDownloaderRow()
def downloader = model.downloads[selectedRow].downloader if (selectedRow < 0) {
model.cancelButtonEnabled = false
model.retryButtonEnabled = false
return
}
def downloader = model.downloads[selectedRow]?.downloader
if (downloader == null)
return
switch(downloader.getCurrentState()) { switch(downloader.getCurrentState()) {
case Downloader.DownloadState.CONNECTING : case Downloader.DownloadState.CONNECTING :
case Downloader.DownloadState.DOWNLOADING : case Downloader.DownloadState.DOWNLOADING :
@@ -286,6 +298,31 @@ class MainFrameView {
downloadsTable.setDefaultRenderer(Integer.class, centerRenderer) downloadsTable.setDefaultRenderer(Integer.class, centerRenderer)
downloadsTable.rowSorter.addRowSorterListener({evt -> lastDownloadSortEvent = evt}) downloadsTable.rowSorter.addRowSorterListener({evt -> lastDownloadSortEvent = evt})
// shared files table
def sharedFilesTable = builder.getVariable("shared-files-table")
sharedFilesTable.columnModel.getColumn(1).setCellRenderer(new SizeRenderer())
JPopupMenu sharedFilesMenu = new JPopupMenu()
JMenuItem unshareSelectedFiles = new JMenuItem("Unshare selected files")
unshareSelectedFiles.addActionListener({mvcGroup.controller.unshareSelectedFiles()})
sharedFilesMenu.add(unshareSelectedFiles)
sharedFilesTable.addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger())
showPopupMenu(sharedFilesMenu, e)
}
@Override
public void mousePressed(MouseEvent e) {
if (e.isPopupTrigger())
showPopupMenu(sharedFilesMenu, e)
}
})
}
def showPopupMenu(JPopupMenu menu, MouseEvent event) {
menu.show(event.getComponent(), event.getX(), event.getY())
} }
int selectedDownloaderRow() { int selectedDownloaderRow() {

View File

@@ -25,6 +25,8 @@ class OptionsView {
def d def d
def p def p
def i def i
def u
def retryField def retryField
def updateField def updateField
def allowUntrustedCheckbox def allowUntrustedCheckbox
@@ -35,6 +37,13 @@ class OptionsView {
def outboundLengthField def outboundLengthField
def outboundQuantityField def outboundQuantityField
def lnfField
def monitorCheckbox
def fontField
def clearCancelledDownloadsCheckbox
def clearFinishedDownloadsCheckbox
def excludeLocalResultCheckbox
def buttonsPanel def buttonsPanel
def mainFrame def mainFrame
@@ -72,6 +81,22 @@ class OptionsView {
label(text : "Outbound Quantity", constraints : gbc(gridx:0, gridy:4)) label(text : "Outbound Quantity", constraints : gbc(gridx:0, gridy:4))
outboundQuantityField = textField(text : bind {model.outboundQuantity}, columns : 2, constraints : gbc(gridx:1, 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 { buttonsPanel = builder.panel {
gridBagLayout() gridBagLayout()
button(text : "Save", constraints : gbc(gridx : 1, gridy: 2), saveAction) button(text : "Save", constraints : gbc(gridx : 1, gridy: 2), saveAction)
@@ -81,8 +106,9 @@ class OptionsView {
void mvcGroupInit(Map<String,String> args) { void mvcGroupInit(Map<String,String> args) {
def tabbedPane = new JTabbedPane() def tabbedPane = new JTabbedPane()
tabbedPane.addTab("MuWire Options", p) tabbedPane.addTab("MuWire", p)
tabbedPane.addTab("I2P Options", i) tabbedPane.addTab("I2P", i)
tabbedPane.addTab("GUI", u)
JPanel panel = new JPanel() JPanel panel = new JPanel()
panel.setLayout(new BorderLayout()) panel.setLayout(new BorderLayout())

View File

@@ -6,12 +6,19 @@ import griffon.inject.MVCMember
import griffon.metadata.ArtifactProviderFor import griffon.metadata.ArtifactProviderFor
import net.i2p.data.DataHelper import net.i2p.data.DataHelper
import javax.swing.JComponent
import javax.swing.JLabel import javax.swing.JLabel
import javax.swing.JTable
import javax.swing.ListSelectionModel import javax.swing.ListSelectionModel
import javax.swing.SwingConstants import javax.swing.SwingConstants
import javax.swing.table.DefaultTableCellRenderer import javax.swing.table.DefaultTableCellRenderer
import com.muwire.core.util.DataUtil
import java.awt.BorderLayout import java.awt.BorderLayout
import java.awt.Color
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import javax.annotation.Nonnull import javax.annotation.Nonnull
@@ -35,7 +42,7 @@ class SearchTabView {
resultsTable = table(id : "results-table", autoCreateRowSorter : true) { resultsTable = table(id : "results-table", autoCreateRowSorter : true) {
tableModel(list: model.results) { tableModel(list: model.results) {
closureColumn(header: "Name", preferredWidth: 350, type: String, read : {row -> row.name.replace('<','_')}) 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: "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: "Sender", preferredWidth: 170, type: String, read : {row -> row.sender.getHumanReadableName()})
closureColumn(header: "Trust", preferredWidth: 50, type: String, read : {row -> closureColumn(header: "Trust", preferredWidth: 50, type: String, read : {row ->
@@ -86,7 +93,18 @@ class SearchTabView {
resultsTable.setDefaultRenderer(Integer.class,centerRenderer) resultsTable.setDefaultRenderer(Integer.class,centerRenderer)
resultsTable.columnModel.getColumn(4).setCellRenderer(centerRenderer) resultsTable.columnModel.getColumn(4).setCellRenderer(centerRenderer)
resultsTable.columnModel.getColumn(1).setCellRenderer(new SizeRenderer())
resultsTable.rowSorter.addRowSorterListener({ evt -> lastSortEvent = evt}) resultsTable.rowSorter.addRowSorterListener({ evt -> lastSortEvent = evt})
resultsTable.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.button == MouseEvent.BUTTON1 && e.clickCount == 2)
mvcGroup.parentGroup.controller.download()
}
})
} }
def closeTab = { def closeTab = {

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 String lnf
boolean showMonitor boolean showMonitor
String font String font
boolean clearCancelledDownloads
boolean clearFinishedDownloads
boolean excludeLocalResult
UISettings(Properties props) { UISettings(Properties props) {
lnf = props.getProperty("lnf", "system") lnf = props.getProperty("lnf", "system")
showMonitor = Boolean.parseBoolean(props.getProperty("showMonitor", "true")) showMonitor = Boolean.parseBoolean(props.getProperty("showMonitor", "true"))
font = props.getProperty("font",null) 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 { void write(OutputStream out) throws IOException {
Properties props = new Properties() Properties props = new Properties()
props.setProperty("lnf", lnf) 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) if (font != null)
props.setProperty("font", font) props.setProperty("font", font)
props.store(out, "UI Properties") props.store(out, "UI Properties")
} }
} }