Compare commits

..

31 Commits

Author SHA1 Message Date
Zlatin Balevsky
54073af933 Release 0.5.2 2019-10-22 00:28:53 +01:00
Zlatin Balevsky
a32903fc8c prettier i2p status panel 2019-10-22 00:11:57 +01:00
Zlatin Balevsky
e40520be46 count hopeless and failing hosts, prettier status panel 2019-10-21 23:57:15 +01:00
Zlatin Balevsky
97482b949a de-capitalize for consistency 2019-10-21 22:50:21 +01:00
Zlatin Balevsky
92ee107312 remove duplicate variable 2019-10-21 22:23:29 +01:00
Zlatin Balevsky
2e8082af64 use titled borders everywhere for consistency 2019-10-21 22:12:39 +01:00
Zlatin Balevsky
8da5a428c9 make the i2p version a variable 2019-10-21 21:02:37 +01:00
Zlatin Balevsky
fd46b3c7d6 do not display fractions in percentage 2019-10-21 20:37:30 +01:00
Zlatin Balevsky
eea3b2563b allign router-specific settings 2019-10-21 20:16:36 +01:00
Zlatin Balevsky
50719f3828 move settings to top of panel 2019-10-21 20:12:08 +01:00
Zlatin Balevsky
01a45a89a8 reorganize the options view 2019-10-21 19:44:33 +01:00
Zlatin Balevsky
66bd249ed3 show percentage of fetched results 2019-10-21 18:28:37 +01:00
Zlatin Balevsky
265cd6ee15 more accurate description 2019-10-20 20:19:47 +01:00
Zlatin Balevsky
1dc88cb96b make speed smoothing interval configurable 2019-10-20 20:09:24 +01:00
Zlatin Balevsky
3e10d497b1 add an ETA column to downloads table 2019-10-20 19:11:32 +01:00
Zlatin Balevsky
9a0b3bb9d6 fix download table selection when sorted 2019-10-20 18:47:48 +01:00
Zlatin Balevsky
a1fe3c01b9 if no incompletes are in serialized json, use the default one, assuming an upgrade 2019-10-20 18:24:16 +01:00
Zlatin Balevsky
ab323db62a add ability to choose the incompletes location 2019-10-20 18:16:07 +01:00
Zlatin Balevsky
d954387e41 fix showing of local files in results 2019-10-20 11:59:48 +01:00
Zlatin Balevsky
ea9db21a18 wip on compressed results 2019-10-20 01:01:34 +01:00
Zlatin Balevsky
136cf89c9b groovy != java 2019-10-20 00:55:31 +01:00
Zlatin Balevsky
46de1baf88 compressed results 2019-10-20 00:54:32 +01:00
Zlatin Balevsky
13f7b8563c fix a bug where disabled browsing was shown as browsable. Log the response code if it's not 200 2019-10-19 22:33:47 +01:00
Zlatin Balevsky
9c15208f3a Release 0.5.1 2019-10-19 19:11:04 +01:00
Zlatin Balevsky
a9ce9d96b3 wip on menu; close zlib stream 2019-10-19 18:54:58 +01:00
Zlatin Balevsky
4d2a5a8018 MainFrameModel doesn't need to listen to single result events anymore 2019-10-19 18:12:30 +01:00
Zlatin Balevsky
8395047386 compress results in browse connections 2019-10-19 17:59:08 +01:00
Zlatin Balevsky
cb23aa44f0 enable SEVERE log messages if no config file specified 2019-10-19 05:53:33 +01:00
Zlatin Balevsky
dbcb8508b8 add a view comment button 2019-10-19 05:35:04 +01:00
Zlatin Balevsky
47d406d93b add a border around the two panels 2019-10-19 04:59:37 +01:00
Zlatin Balevsky
e06f1805c2 redirect griffon logging to jul 2019-10-19 04:45:45 +01:00
38 changed files with 549 additions and 179 deletions

View File

@@ -2,7 +2,7 @@ subprojects {
apply plugin: 'groovy'
dependencies {
compile 'net.i2p:i2p:0.9.42'
compile "net.i2p:i2p:${i2pVersion}"
compile 'org.codehaus.groovy:groovy-all:2.4.15'
}

View File

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

View File

@@ -2,9 +2,9 @@ apply plugin : 'application'
mainClassName = 'com.muwire.core.Core'
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties']
dependencies {
compile 'net.i2p:router:0.9.42'
compile 'net.i2p.client:mstreaming:0.9.42'
compile 'net.i2p.client:streaming:0.9.42'
compile "net.i2p:router:${i2pVersion}"
compile "net.i2p.client:mstreaming:${i2pVersion}"
compile "net.i2p.client:streaming:${i2pVersion}"
testCompile 'org.junit.jupiter:junit-jupiter-api:5.4.2'
testCompile 'junit:junit:4.12'

View File

@@ -375,7 +375,7 @@ public class Core {
}
}
Core core = new Core(props, home, "0.5.0")
Core core = new Core(props, home, "0.5.2")
core.startServices()
// ... at the end, sleep or execute script

View File

@@ -22,6 +22,7 @@ class MuWireSettings {
String updateType
String nickname
File downloadLocation
File incompleteLocation
CrawlerResponse crawlerResponse
boolean shareDownloadedFiles
boolean shareHiddenFiles
@@ -31,6 +32,7 @@ class MuWireSettings {
float downloadSequentialRatio
int hostClearInterval, hostHopelessInterval, hostRejectInterval
int meshExpiration
int speedSmoothSeconds
boolean embeddedRouter
int inBw, outBw
Set<String> watchedKeywords
@@ -50,6 +52,9 @@ class MuWireSettings {
nickname = props.getProperty("nickname","MuWireUser")
downloadLocation = new File((String)props.getProperty("downloadLocation",
System.getProperty("user.home")))
String incompleteLocationProp = props.getProperty("incompleteLocation")
if (incompleteLocationProp != null)
incompleteLocation = new File(incompleteLocationProp)
downloadRetryInterval = Integer.parseInt(props.getProperty("downloadRetryInterval","60"))
updateCheckInterval = Integer.parseInt(props.getProperty("updateCheckInterval","24"))
autoDownloadUpdate = Boolean.parseBoolean(props.getProperty("autoDownloadUpdate","true"))
@@ -66,6 +71,7 @@ class MuWireSettings {
outBw = Integer.valueOf(props.getProperty("outBw","128"))
searchComments = Boolean.valueOf(props.getProperty("searchComments","true"))
browseFiles = Boolean.valueOf(props.getProperty("browseFiles","true"))
speedSmoothSeconds = Integer.valueOf(props.getProperty("speedSmoothSeconds","60"))
watchedDirectories = readEncodedSet(props, "watchedDirectories")
watchedKeywords = readEncodedSet(props, "watchedKeywords")
@@ -91,6 +97,8 @@ class MuWireSettings {
props.setProperty("crawlerResponse", crawlerResponse.toString())
props.setProperty("nickname", nickname)
props.setProperty("downloadLocation", downloadLocation.getAbsolutePath())
if (incompleteLocation != null)
props.setProperty("incompleteLocation", incompleteLocation.getAbsolutePath())
props.setProperty("downloadRetryInterval", String.valueOf(downloadRetryInterval))
props.setProperty("updateCheckInterval", String.valueOf(updateCheckInterval))
props.setProperty("autoDownloadUpdate", String.valueOf(autoDownloadUpdate))
@@ -107,6 +115,7 @@ class MuWireSettings {
props.setProperty("outBw", String.valueOf(outBw))
props.setProperty("searchComments", String.valueOf(searchComments))
props.setProperty("browseFiles", String.valueOf(browseFiles))
props.setProperty("speedSmoothSeconds", String.valueOf(speedSmoothSeconds))
writeEncodedSet(watchedDirectories, "watchedDirectories", props)
writeEncodedSet(watchedKeywords, "watchedKeywords", props)

View File

@@ -133,6 +133,7 @@ abstract class Connection implements Closeable {
query.keywords = e.searchEvent.getSearchTerms()
query.oobInfohash = e.searchEvent.oobInfohash
query.searchComments = e.searchEvent.searchComments
query.compressedResults = e.searchEvent.compressedResults
if (e.searchEvent.searchHash != null)
query.infohash = Base64.encode(e.searchEvent.searchHash)
query.replyTo = e.replyTo.toBase64()
@@ -213,12 +214,16 @@ abstract class Connection implements Closeable {
boolean searchComments = false
if (search.searchComments != null)
searchComments = search.searchComments
boolean compressedResults = false
if (search.compressedResults != null)
compressedResults = search.compressedResults
SearchEvent searchEvent = new SearchEvent(searchTerms : search.keywords,
searchHash : infohash,
uuid : uuid,
oobInfohash : oob,
searchComments : searchComments)
searchComments : searchComments,
compressedResults : compressedResults)
QueryEvent event = new QueryEvent ( searchEvent : searchEvent,
replyTo : replyTo,
originator : originator,

View File

@@ -5,8 +5,11 @@ import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.logging.Level
import java.util.zip.DeflaterOutputStream
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
import java.util.zip.InflaterInputStream
import com.muwire.core.Constants
import com.muwire.core.EventBus
import com.muwire.core.MuWireSettings
import com.muwire.core.Persona
@@ -27,6 +30,7 @@ import com.muwire.core.search.UnexpectedResultsException
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import groovy.util.logging.Log
import net.i2p.data.Base64
@Log
class ConnectionAcceptor {
@@ -130,6 +134,9 @@ class ConnectionAcceptor {
case (byte)'P':
processPOST(e)
break
case (byte)'R':
processRESULTS(e)
break
case (byte)'T':
processTRUST(e)
break
@@ -236,7 +243,7 @@ class ConnectionAcceptor {
Persona sender = new Persona(dis)
if (sender.destination != e.getDestination())
throw new IOException("Sender destination mismatch expected $e.getDestination(), got $sender.destination")
throw new IOException("Sender destination mismatch expected ${e.getDestination()}, got $sender.destination")
int nResults = dis.readUnsignedShort()
UIResultEvent[] results = new UIResultEvent[nResults]
for (int i = 0; i < nResults; i++) {
@@ -254,6 +261,66 @@ class ConnectionAcceptor {
}
}
private void processRESULTS(Endpoint e) {
InputStream is = e.getInputStream()
DataInputStream dis = new DataInputStream(is)
byte[] esults = new byte[7]
dis.readFully(esults)
if (esults != "ESULTS ".getBytes(StandardCharsets.US_ASCII))
throw new IOException("Invalid RESULTS connection")
JsonSlurper slurper = new JsonSlurper()
try {
String uuid = DataUtil.readTillRN(dis)
UUID resultsUUID = UUID.fromString(uuid)
if (!searchManager.hasLocalSearch(resultsUUID))
throw new UnexpectedResultsException(resultsUUID.toString())
// parse all headers
Map<String,String> headers = new HashMap<>()
String header
while((header = DataUtil.readTillRN(is)) != "" && headers.size() < Constants.MAX_HEADERS) {
int colon = header.indexOf(':')
if (colon == -1 || colon == header.length() - 1)
throw new IOException("invalid header $header")
String key = header.substring(0, colon)
String value = header.substring(colon + 1)
headers[key] = value.trim()
}
if (!headers.containsKey("Sender"))
throw new IOException("No Sender header")
if (!headers.containsKey("Count"))
throw new IOException("No Count header")
byte [] personaBytes = Base64.decode(headers['Sender'])
Persona sender = new Persona(new ByteArrayInputStream(personaBytes))
if (sender.destination != e.getDestination())
throw new IOException("Sender destination mismatch expected ${e.getDestination()}, got $sender.destination")
int nResults = Integer.parseInt(headers['Count'])
if (nResults > Constants.MAX_RESULTS)
throw new IOException("too many results $nResults")
dis = new DataInputStream(new GZIPInputStream(dis))
UIResultEvent[] results = new UIResultEvent[nResults]
for (int i = 0; i < nResults; i++) {
int jsonSize = dis.readUnsignedShort()
byte [] payload = new byte[jsonSize]
dis.readFully(payload)
def json = slurper.parse(payload)
results[i] = ResultsParser.parse(sender, resultsUUID, json)
}
eventBus.publish(new UIResultBatchEvent(uuid: resultsUUID, results: results))
} catch (IOException bad) {
log.log(Level.WARNING, "failed to process RESULTS", bad)
} finally {
e.close()
}
}
private void processBROWSE(Endpoint e) {
try {
byte [] rowse = new byte[7]
@@ -279,7 +346,7 @@ class ConnectionAcceptor {
os.write("Count: ${sharedFiles.size()}\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
DataOutputStream dos = new DataOutputStream(os)
DataOutputStream dos = new DataOutputStream(new GZIPOutputStream(os))
JsonOutput jsonOutput = new JsonOutput()
sharedFiles.each {
def obj = ResultsSender.sharedFileToObj(it, false)
@@ -288,6 +355,7 @@ class ConnectionAcceptor {
dos.write(json.getBytes(StandardCharsets.US_ASCII))
}
dos.flush()
dos.close()
} finally {
e.close()
}

View File

@@ -34,7 +34,7 @@ public class DownloadManager {
private final MuWireSettings muSettings
private final I2PConnector connector
private final Executor executor
private final File incompletes, home
private final File home
private final Persona me
private final Map<InfoHash, Downloader> downloaders = new ConcurrentHashMap<>()
@@ -46,12 +46,9 @@ public class DownloadManager {
this.meshManager = meshManager
this.muSettings = muSettings
this.connector = connector
this.incompletes = new File(home,"incompletes")
this.home = home
this.me = me
incompletes.mkdir()
this.executor = Executors.newCachedThreadPool({ r ->
Thread rv = new Thread(r)
rv.setName("download-worker")
@@ -63,6 +60,11 @@ public class DownloadManager {
public void onUIDownloadEvent(UIDownloadEvent e) {
File incompletes = muSettings.incompleteLocation
if (incompletes == null)
incompletes = new File(home, "incompletes")
incompletes.mkdirs()
def size = e.result[0].size
def infohash = e.result[0].infohash
def pieceSize = e.result[0].pieceSize
@@ -126,6 +128,12 @@ public class DownloadManager {
boolean sequential = false
if (json.sequential != null)
sequential = json.sequential
File incompletes
if (json.incompletes != null)
incompletes = new File(DataUtil.readi18nString(Base64.decode(json.incompletes)))
else
incompletes = new File(home, "incompletes")
Pieces pieces = getPieces(infoHash, (long)json.length, json.pieceSizePow2, sequential)
@@ -195,6 +203,8 @@ public class DownloadManager {
json.sequential = downloader.pieces.ratio == 0f
json.incompletes = Base64.encode(DataUtil.encodei18nString(downloader.incompletes.getAbsolutePath()))
writer.println(JsonOutput.toJson(json))
}
}

View File

@@ -27,6 +27,7 @@ import net.i2p.util.ConcurrentHashSet
@Log
public class Downloader {
public enum DownloadState { CONNECTING, HASHLIST, DOWNLOADING, FAILED, CANCELLED, PAUSED, FINISHED }
private enum WorkerState { CONNECTING, HASHLIST, DOWNLOADING, FINISHED}
@@ -48,6 +49,7 @@ public class Downloader {
private final I2PConnector connector
private final Set<Destination> destinations
private final int nPieces
private final File incompletes
private final File piecesFile
private final File incompleteFile
final int pieceSizePow2
@@ -76,16 +78,13 @@ public class Downloader {
this.length = length
this.connector = connector
this.destinations = destinations
this.incompletes = incompletes
this.piecesFile = new File(incompletes, file.getName()+".pieces")
this.incompleteFile = new File(incompletes, file.getName()+".part")
this.pieceSizePow2 = pieceSizePow2
this.pieceSize = 1 << pieceSizePow2
this.pieces = pieces
this.nPieces = pieces.nPieces
// default size suitable for an average of 5 seconds / 5 elements / 5 interval units
// it's easily adjustable by resizing the size of speedArr
this.speedArr = [ 0, 0, 0, 0, 0 ]
}
public synchronized InfoHash getInfoHash() {
@@ -145,6 +144,12 @@ public class Downloader {
currSpeed += it.speed()
}
}
if (speedArr.size() != downloadManager.muSettings.speedSmoothSeconds) {
speedArr.clear()
downloadManager.muSettings.speedSmoothSeconds.times { speedArr.add(0) }
speedPos = 0
}
// normalize to speedArr.size
currSpeed /= speedArr.size()

View File

@@ -105,6 +105,22 @@ class HostCache extends Service {
Collections.shuffle(rv)
rv[0..n-1]
}
int countFailingHosts() {
List<Destination> rv = new ArrayList<>(hosts.keySet())
rv.retainAll {
hosts[it].isFailed()
}
rv.size()
}
int countHopelessHosts() {
List<Destination> rv = new ArrayList<>(hosts.keySet())
rv.retainAll {
hosts[it].isHopeless()
}
rv.size()
}
void load() {
if (storage.exists()) {

View File

@@ -13,6 +13,7 @@ import java.nio.charset.StandardCharsets
import java.util.concurrent.Executor
import java.util.concurrent.Executors
import java.util.logging.Level
import java.util.zip.GZIPInputStream
@Log
class BrowseManager {
@@ -39,7 +40,7 @@ class BrowseManager {
InputStream is = endpoint.getInputStream()
String code = DataUtil.readTillRN(is)
if (!code.startsWith("200"))
throw new IOException("Invalid code")
throw new IOException("Invalid code $code")
// parse all headers
Map<String,String> headers = new HashMap<>()
@@ -59,10 +60,10 @@ class BrowseManager {
int results = Integer.parseInt(headers['Count'])
// at this stage, start pulling the results
eventBus.publish(new BrowseStatusEvent(status : BrowseStatus.FETCHING))
eventBus.publish(new BrowseStatusEvent(status : BrowseStatus.FETCHING, totalResults : results))
JsonSlurper slurper = new JsonSlurper()
DataInputStream dis = new DataInputStream(is)
DataInputStream dis = new DataInputStream(new GZIPInputStream(is))
UUID uuid = UUID.randomUUID()
for (int i = 0; i < results; i++) {
int size = dis.readUnsignedShort()

View File

@@ -4,4 +4,5 @@ import com.muwire.core.Event
class BrowseStatusEvent extends Event {
BrowseStatus status
int totalResults
}

View File

@@ -97,7 +97,7 @@ class ResultsParser {
boolean browse = false
if (json.browse != null)
browse = true
browse = json.browse
return new UIResultEvent( sender : p,
name : name,

View File

@@ -14,6 +14,7 @@ import java.util.concurrent.ThreadFactory
import java.util.concurrent.atomic.AtomicInteger
import java.util.logging.Level
import java.util.stream.Collectors
import java.util.zip.GZIPOutputStream
import com.muwire.core.DownloadedFile
import com.muwire.core.EventBus
@@ -53,9 +54,10 @@ class ResultsSender {
this.settings = settings
}
void sendResults(UUID uuid, SharedFile[] results, Destination target, boolean oobInfohash) {
void sendResults(UUID uuid, SharedFile[] results, Destination target, boolean oobInfohash, boolean compressedResults) {
log.info("Sending $results.length results for uuid $uuid to ${target.toBase32()} oobInfohash : $oobInfohash")
if (target.equals(me.destination)) {
def uiResultEvents = []
results.each {
long length = it.getFile().length()
int pieceSize = it.getPieceSize()
@@ -77,11 +79,12 @@ class ResultsSender {
sources : suggested,
comment : comment
)
eventBus.publish(uiResultEvent)
uiResultEvents << uiResultEvent
}
eventBus.publish(new UIResultBatchEvent(uuid : uuid, results : uiResultEvents))
} else {
executor.execute(new ResultSendJob(uuid : uuid, results : results,
target: target, oobInfohash : oobInfohash))
target: target, oobInfohash : oobInfohash, compressedResults : compressedResults))
}
}
@@ -90,27 +93,49 @@ class ResultsSender {
SharedFile [] results
Destination target
boolean oobInfohash
boolean compressedResults
@Override
public void run() {
try {
JsonOutput jsonOutput = new JsonOutput()
Endpoint endpoint = null;
try {
endpoint = connector.connect(target)
DataOutputStream os = new DataOutputStream(endpoint.getOutputStream())
os.write("POST $uuid\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
me.write(os)
os.writeShort((short)results.length)
results.each {
def obj = sharedFileToObj(it, settings.browseFiles)
def json = jsonOutput.toJson(obj)
os.writeShort((short)json.length())
os.write(json.getBytes(StandardCharsets.US_ASCII))
if (!compressedResults) {
try {
endpoint = connector.connect(target)
DataOutputStream os = new DataOutputStream(endpoint.getOutputStream())
os.write("POST $uuid\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
me.write(os)
os.writeShort((short)results.length)
results.each {
def obj = sharedFileToObj(it, settings.browseFiles)
def json = jsonOutput.toJson(obj)
os.writeShort((short)json.length())
os.write(json.getBytes(StandardCharsets.US_ASCII))
}
os.flush()
} finally {
endpoint?.close()
}
} else {
try {
endpoint = connector.connect(target)
OutputStream os = endpoint.getOutputStream()
os.write("RESULTS $uuid\r\n".getBytes(StandardCharsets.US_ASCII))
os.write("Sender: ${me.toBase64()}\r\n".getBytes(StandardCharsets.US_ASCII))
os.write("Count: $results.length\r\n".getBytes(StandardCharsets.US_ASCII))
os.write("\r\n".getBytes(StandardCharsets.US_ASCII))
DataOutputStream dos = new DataOutputStream(new GZIPOutputStream(os))
results.each {
def obj = sharedFileToObj(it, settings.browseFiles)
def json = jsonOutput.toJson(obj)
dos.writeShort((short)json.length())
dos.write(json.getBytes(StandardCharsets.US_ASCII))
}
dos.close()
} finally {
endpoint?.close()
}
os.flush()
} finally {
endpoint?.close()
}
} catch (Exception e) {
log.log(Level.WARNING, "problem sending results",e)

View File

@@ -10,11 +10,12 @@ class SearchEvent extends Event {
UUID uuid
boolean oobInfohash
boolean searchComments
boolean compressedResults
String toString() {
def infoHash = null
if (searchHash != null)
infoHash = new InfoHash(searchHash)
"searchTerms: $searchTerms searchHash:$infoHash, uuid:$uuid oobInfohash:$oobInfohash searchComments:$searchComments"
"searchTerms: $searchTerms searchHash:$infoHash, uuid:$uuid oobInfohash:$oobInfohash searchComments:$searchComments compressedResults:$compressedResults"
}
}

View File

@@ -44,7 +44,7 @@ public class SearchManager {
log.info("No results for search uuid $event.uuid")
return
}
resultsSender.sendResults(event.uuid, event.results, target, event.searchEvent.oobInfohash)
resultsSender.sendResults(event.uuid, event.results, target, event.searchEvent.oobInfohash, event.searchEvent.compressedResults)
}
boolean hasLocalSearch(UUID uuid) {

View File

@@ -8,4 +8,6 @@ public class Constants {
public static final int MAX_HEADER_SIZE = 0x1 << 14;
public static final int MAX_HEADERS = 16;
public static final int MAX_RESULTS = 0x1 << 16;
}

View File

@@ -1,5 +1,6 @@
group = com.muwire
version = 0.5.0
version = 0.5.2
i2pVersion = 0.9.42
groovyVersion = 2.4.15
slf4jVersion = 1.7.25
spockVersion = 1.1-groovy-2.4
@@ -12,7 +13,6 @@ targetCompatibility=1.8
# plugin properties
author = zab@mail.i2p
signer = zab@mail.i2p
i2pVersion=0.9.41
keystorePassword=changeit
websiteURL=http://muwire.i2p
updateURLsu3=http://muwire.i2p/MuWire.su3

View File

@@ -58,7 +58,11 @@ dependencies {
compile project(":core")
compile "org.codehaus.griffon:griffon-guice:${griffon.version}"
runtime "org.slf4j:slf4j-simple:${slf4jVersion}"
// runtime "org.slf4j:slf4j-simple:${slf4jVersion}"
runtime group: 'org.slf4j', name: 'slf4j-jdk14', version: "${slf4jVersion}"
runtime group: 'org.slf4j', name: 'slf4j-api', version: "${slf4jVersion}"
runtime group: 'org.slf4j', name: 'jul-to-slf4j', version: "${slf4jVersion}"
runtime "javax.annotation:javax.annotation-api:1.3.2"
testCompile "org.codehaus.griffon:griffon-fest-test:${griffon.version}"

View File

@@ -10,6 +10,7 @@ import javax.annotation.Nonnull
import com.muwire.core.EventBus
import com.muwire.core.download.UIDownloadEvent
import com.muwire.core.search.BrowseStatus
import com.muwire.core.search.BrowseStatusEvent
import com.muwire.core.search.UIBrowseEvent
import com.muwire.core.search.UIResultEvent
@@ -38,12 +39,15 @@ class BrowseController {
void onBrowseStatusEvent(BrowseStatusEvent e) {
runInsideUIAsync {
model.status = e.status
if (e.status == BrowseStatus.FETCHING)
model.totalResults = e.totalResults
}
}
void onUIResultEvent(UIResultEvent e) {
runInsideUIAsync {
model.results << e
model.resultCount = model.results.size()
view.resultsTable.model.fireTableDataChanged()
}
}

View File

@@ -78,7 +78,7 @@ class MainFrameController {
def searchEvent
if (hashSearch) {
searchEvent = new SearchEvent(searchHash : root, uuid : uuid, oobInfohash: true)
searchEvent = new SearchEvent(searchHash : root, uuid : uuid, oobInfohash: true, compressedResults : true)
} else {
// this can be improved a lot
def replaced = search.toLowerCase().trim().replaceAll(SplitPattern.SPLIT_PATTERN, " ")
@@ -86,7 +86,7 @@ class MainFrameController {
def nonEmpty = []
terms.each { if (it.length() > 0) nonEmpty << it }
searchEvent = new SearchEvent(searchTerms : nonEmpty, uuid : uuid, oobInfohash: true,
searchComments : core.muOptions.searchComments)
searchComments : core.muOptions.searchComments, compressedResults : true)
}
boolean firstHop = core.muOptions.allowUntrusted || core.muOptions.searchExtraHop
core.eventBus.publish(new QueryEvent(searchEvent : searchEvent, firstHop : firstHop,

View File

@@ -31,6 +31,9 @@ class MuWireStatusController {
model.outgoingConnections = outgoing
model.knownHosts = core.hostCache.hosts.size()
model.failingHosts = core.hostCache.countFailingHosts()
model.hopelessHosts = core.hostCache.countHopelessHosts()
model.sharedFiles = core.fileManager.fileToSharedFile.size()

View File

@@ -91,8 +91,15 @@ class OptionsController {
model.browseFiles = browseFiles
settings.browseFiles = browseFiles
text = view.speedSmoothSecondsField.text
model.speedSmoothSeconds = Integer.valueOf(text)
settings.speedSmoothSeconds = Integer.valueOf(text)
String downloadLocation = model.downloadLocation
settings.downloadLocation = new File(downloadLocation)
String incompleteLocation = model.incompleteLocation
settings.incompleteLocation = new File(incompleteLocation)
if (settings.embeddedRouter) {
text = view.inBwField.text
@@ -174,7 +181,18 @@ class OptionsController {
int rv = chooser.showOpenDialog(null)
if (rv == JFileChooser.APPROVE_OPTION)
model.downloadLocation = chooser.getSelectedFile().getAbsolutePath()
}
}
@ControllerAction
void incompleteLocation() {
def chooser = new JFileChooser()
chooser.setFileHidingEnabled(false)
chooser.setDialogTitle("Select location for downloaded files")
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY)
int rv = chooser.showOpenDialog(null)
if (rv == JFileChooser.APPROVE_OPTION)
model.incompleteLocation = chooser.getSelectedFile().getAbsolutePath()
}
@ControllerAction
void automaticFontAction() {

View File

@@ -4,6 +4,8 @@ import griffon.core.artifact.GriffonController
import griffon.core.controller.ControllerAction
import griffon.inject.MVCMember
import griffon.metadata.ArtifactProviderFor
import net.i2p.data.Base64
import javax.annotation.Nonnull
import com.muwire.core.Core
@@ -101,4 +103,22 @@ class SearchTabController {
mvcGroup.createMVCGroup("browse", groupId, params)
}
@ControllerAction
void showComment() {
int[] selectedRows = view.resultsTable.getSelectedRows()
if (selectedRows.length != 1)
return
if (view.lastSortEvent != null)
selectedRows[0] = view.resultsTable.rowSorter.convertRowIndexToModel(selectedRows[0])
UIResultEvent event = model.results[selectedRows[0]]
if (event.comment == null)
return
String groupId = Base64.encode(event.infohash.getRoot())
Map<String,Object> params = new HashMap<>()
params['result'] = event
mvcGroup.createMVCGroup("show-comment", groupId, params)
}
}

View File

@@ -35,11 +35,11 @@ class Initialize extends AbstractLifecycleHandler {
void execute() {
if (System.getProperty("java.util.logging.config.file") == null) {
log.info("No config file specified, so turning off logging")
log.info("No config file specified, so turning off most logging")
def names = LogManager.getLogManager().getLoggerNames()
while(names.hasMoreElements()) {
def name = names.nextElement()
LogManager.getLogManager().getLogger(name).setLevel(Level.OFF)
LogManager.getLogManager().getLogger(name).setLevel(Level.SEVERE)
}
}

View File

@@ -45,9 +45,12 @@ class Ready extends AbstractLifecycleHandler {
props.load(it)
}
props = new MuWireSettings(props)
if (props.incompleteLocation == null)
props.incompleteLocation = new File(home, "incompletes")
} else {
log.info("creating new properties")
props = new MuWireSettings()
props.incompleteLocation = new File(home, "incompletes")
props.embeddedRouter = Boolean.parseBoolean(System.getProperties().getProperty("embeddedRouter"))
props.updateType = System.getProperty("updateType","jar")
def nickname

View File

@@ -14,6 +14,8 @@ class BrowseModel {
@Observable BrowseStatus status
@Observable boolean downloadActionEnabled
@Observable boolean viewCommentActionEnabled
@Observable int totalResults
@Observable int resultCount
def results = []
}

View File

@@ -181,7 +181,6 @@ class MainFrameModel {
core = e.getNewValue()
routerPresent = core.router != null
me = core.me.getHumanReadableName()
core.eventBus.register(UIResultEvent.class, this)
core.eventBus.register(UIResultBatchEvent.class, this)
core.eventBus.register(DownloadStartedEvent.class, this)
core.eventBus.register(ConnectionEvent.class, this)

View File

@@ -16,6 +16,8 @@ class MuWireStatusModel {
@Observable int incomingConnections
@Observable int outgoingConnections
@Observable int knownHosts
@Observable int failingHosts
@Observable int hopelessHosts
@Observable int sharedFiles
@Observable int downloads

View File

@@ -15,8 +15,10 @@ class OptionsModel {
@Observable boolean shareDownloadedFiles
@Observable boolean shareHiddenFiles
@Observable String downloadLocation
@Observable String incompleteLocation
@Observable boolean searchComments
@Observable boolean browseFiles
@Observable int speedSmoothSeconds
// i2p options
@Observable String inboundLength
@@ -56,8 +58,10 @@ class OptionsModel {
shareDownloadedFiles = settings.shareDownloadedFiles
shareHiddenFiles = settings.shareHiddenFiles
downloadLocation = settings.downloadLocation.getAbsolutePath()
incompleteLocation = settings.incompleteLocation.getAbsolutePath()
searchComments = settings.searchComments
browseFiles = settings.browseFiles
speedSmoothSeconds = settings.speedSmoothSeconds
Core core = application.context.get("core")
inboundLength = core.i2pOptions["inbound.length"]

View File

@@ -22,6 +22,7 @@ class SearchTabModel {
@Observable boolean downloadActionEnabled
@Observable boolean trustButtonsEnabled
@Observable boolean browseActionEnabled
@Observable boolean viewCommentActionEnabled
Core core
UISettings uiSettings

View File

@@ -3,9 +3,12 @@ package com.muwire.gui
import griffon.core.artifact.GriffonView
import griffon.inject.MVCMember
import griffon.metadata.ArtifactProviderFor
import net.i2p.data.Base64
import javax.swing.JDialog
import javax.swing.JLabel
import javax.swing.JMenuItem
import javax.swing.JPopupMenu
import javax.swing.ListSelectionModel
import javax.swing.SwingConstants
import javax.swing.table.DefaultTableCellRenderer
@@ -13,6 +16,10 @@ import javax.swing.table.DefaultTableCellRenderer
import com.muwire.core.search.UIResultEvent
import java.awt.BorderLayout
import java.awt.Toolkit
import java.awt.datatransfer.StringSelection
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.awt.event.WindowAdapter
import java.awt.event.WindowEvent
@@ -42,6 +49,7 @@ class BrowseView {
panel (constraints : BorderLayout.NORTH) {
label(text: "Status:")
label(text: bind {model.status.toString()})
label(text : bind {model.totalResults == 0 ? "" : Math.round(model.resultCount * 100 / model.totalResults)+ "%"})
}
scrollPane (constraints : BorderLayout.CENTER){
resultsTable = table(autoCreateRowSorter : true) {
@@ -95,9 +103,70 @@ class BrowseView {
downloadActionEnabled &= mvcGroup.parentGroup.parentGroup.model.canDownload(model.results[it].infohash)
}
model.downloadActionEnabled = downloadActionEnabled
resultsTable.addMouseListener(new MouseAdapter() {
public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger())
showMenu(e)
}
public void mousePressed(MouseEvent e) {
if (e.isPopupTrigger())
showMenu(e)
}
})
})
}
private void showMenu(MouseEvent e) {
JPopupMenu menu = new JPopupMenu()
if (model.downloadActionEnabled) {
JMenuItem download = new JMenuItem("Download")
download.addActionListener({controller.download()})
menu.add(download)
}
if (model.viewCommentActionEnabled) {
JMenuItem viewComment = new JMenuItem("View Comment")
viewComment.addActionListener({controller.viewComment()})
menu.add(viewComment)
}
JMenuItem copyHash = new JMenuItem("Copy Hash To Clipboard")
copyHash.addActionListener({
List<UIResultEvent> results = selectedResults()
def hash = ""
for(Iterator<UIResultEvent> iter = results.iterator(); iter.hasNext();) {
UIResultEvent result = iter.next()
hash += Base64.encode(result.infohash.getRoot())
if (iter.hasNext())
hash += "\n"
}
copyString(hash)
})
menu.add(copyHash)
JMenuItem copyName = new JMenuItem("Copy Name To Clipboard")
copyName.addActionListener({
List<UIResultEvent> results = selectedResults()
def name = ""
for(Iterator<UIResultEvent> iter = results.iterator(); iter.hasNext();) {
UIResultEvent result = iter.next()
name += result.getName()
if (iter.hasNext())
name += "\n"
}
copyString(name)
})
menu.add(copyName)
menu.show(e.getComponent(), e.getX(), e.getY())
}
private static copyString(String s) {
StringSelection selection = new StringSelection(s)
def clipboard = Toolkit.getDefaultToolkit().getSystemClipboard()
clipboard.setContents(selection, null)
}
void mvcGroupInit(Map<String,String> args) {
controller.register()

View File

@@ -7,8 +7,10 @@ import griffon.metadata.ArtifactProviderFor
import javax.swing.JDialog
import javax.swing.JPanel
import javax.swing.SwingConstants
import javax.swing.border.TitledBorder
import java.awt.BorderLayout
import java.awt.GridBagConstraints
import java.awt.event.WindowAdapter
import java.awt.event.WindowEvent
@@ -33,24 +35,40 @@ class I2PStatusView {
panel = builder.panel {
gridBagLayout()
label(text : "Network status", constraints : gbc(gridx:0, gridy:0))
label(text : bind {model.networkStatus}, constraints : gbc(gridx: 1, gridy:0))
label(text: "Floodfill", constraints : gbc(gridx: 0, gridy : 1))
label(text : bind {model.floodfill}, constraints : gbc(gridx:1, gridy:1))
label(text : "NTCP Connections", constraints : gbc(gridx:0, gridy:2))
label(text : bind {model.ntcpConnections}, constraints : gbc(gridx: 1, gridy:2))
label(text : "SSU Connections", constraints : gbc(gridx:0, gridy:3))
label(text : bind {model.ssuConnections}, constraints : gbc(gridx: 1, gridy:3))
label(text : "Participating Tunnels", constraints : gbc(gridx:0, gridy:4))
label(text : bind {model.participatingTunnels}, constraints : gbc(gridx: 1, gridy:4))
label(text : "Participating Bandwidth", constraints : gbc(gridx:0, gridy:5))
label(text : bind {model.participatingBW}, constraints : gbc(gridx: 1, gridy:5))
label(text : "Active Peers", constraints : gbc(gridx:0, gridy:6))
label(text : bind {model.activePeers}, constraints : gbc(gridx: 1, gridy:6))
label(text : "Receive Bps (15 seconds)", constraints : gbc(gridx:0, gridy:7))
label(text : bind {model.receiveBps}, constraints : gbc(gridx: 1, gridy:7))
label(text : "Send Bps (15 seconds)", constraints : gbc(gridx:0, gridy:8))
label(text : bind {model.sendBps}, constraints : gbc(gridx: 1, gridy:8))
panel(border : titledBorder(title : "General", border : etchedBorder(), titlePosition : TitledBorder.TOP),
constraints : gbc(gridx: 0, gridy : 0, fill : GridBagConstraints.HORIZONTAL, weightx: 100)) {
gridBagLayout()
label(text : "Network status", constraints : gbc(gridx:0, gridy:0, anchor : GridBagConstraints.LINE_START, weightx: 100))
label(text : bind {model.networkStatus}, constraints : gbc(gridx: 1, gridy:0, anchor : GridBagConstraints.LINE_END))
label(text: "Floodfill", constraints : gbc(gridx: 0, gridy : 1, anchor: GridBagConstraints.LINE_START, weightx: 100))
label(text : bind {model.floodfill}, constraints : gbc(gridx:1, gridy:1, anchor : GridBagConstraints.LINE_END))
label(text : "Active Peers", constraints : gbc(gridx:0, gridy:2, anchor : GridBagConstraints.LINE_START, weightx: 100))
label(text : bind {model.activePeers}, constraints : gbc(gridx: 1, gridy:2, anchor : GridBagConstraints.LINE_END))
}
panel(border : titledBorder(title : "Connections", border : etchedBorder(), titlePosition : TitledBorder.TOP),
constraints : gbc(gridx: 0, gridy: 1, fill : GridBagConstraints.HORIZONTAL, weightx: 100)) {
gridBagLayout()
label(text : "NTCP", constraints : gbc(gridx:0, gridy:0, anchor: GridBagConstraints.LINE_START, weightx: 100))
label(text : bind {model.ntcpConnections}, constraints : gbc(gridx: 1, gridy:0, anchor : GridBagConstraints.LINE_END))
label(text : "SSU", constraints : gbc(gridx:0, gridy:1, anchor: GridBagConstraints.LINE_START, weightx: 100))
label(text : bind {model.ssuConnections}, constraints : gbc(gridx: 1, gridy:1, anchor : GridBagConstraints.LINE_END))
}
panel(border : titledBorder(title : "Participation", border : etchedBorder(), titlePosition : TitledBorder.TOP),
constraints : gbc(gridx: 0, gridy: 2, fill : GridBagConstraints.HORIZONTAL, weightx: 100)) {
gridBagLayout()
label(text : "Tunnels", constraints : gbc(gridx:0, gridy:0, anchor: GridBagConstraints.LINE_START, weightx: 100))
label(text : bind {model.participatingTunnels}, constraints : gbc(gridx: 1, gridy:0, anchor : GridBagConstraints.LINE_END))
label(text : "Bandwidth", constraints : gbc(gridx:0, gridy:1, anchor: GridBagConstraints.LINE_START, weightx: 100))
label(text : bind {model.participatingBW}, constraints : gbc(gridx: 1, gridy:1, anchor : GridBagConstraints.LINE_END))
}
panel(border : titledBorder(title : "Bandwidth", border : etchedBorder(), titlePosition : TitledBorder.TOP),
constraints : gbc(gridx: 0, gridy: 3, fill : GridBagConstraints.HORIZONTAL, weightx: 100)) {
gridBagLayout()
label(text : "Receive Bps (15 seconds)", constraints : gbc(gridx:0, gridy:0, anchor: GridBagConstraints.LINE_START, weightx: 100))
label(text : bind {model.receiveBps}, constraints : gbc(gridx: 1, gridy:0, anchor : GridBagConstraints.LINE_END))
label(text : "Send Bps (15 seconds)", constraints : gbc(gridx:0, gridy:1, anchor: GridBagConstraints.LINE_START, weightx: 100))
label(text : bind {model.sendBps}, constraints : gbc(gridx: 1, gridy:1, anchor : GridBagConstraints.LINE_END))
}
}
buttonsPanel = builder.panel {

View File

@@ -141,6 +141,15 @@ class MainFrameView {
closureColumn(header: "Speed", preferredWidth: 50, type:String, read :{row ->
DataHelper.formatSize2Decimal(row.downloader.speed(), false) + "B/sec"
})
closureColumn(header : "ETA", preferredWidth : 50, type:String, read :{ row ->
def speed = row.downloader.speed()
if (speed == 0)
return "Unknown"
else {
def remaining = (row.downloader.nPieces - row.downloader.donePieces()) * row.downloader.pieceSize / speed
return DataHelper.formatDuration(remaining.toLong() * 1000)
}
})
}
}
}
@@ -717,9 +726,12 @@ class MainFrameView {
}
int selectedDownloaderRow() {
int selected = builder.getVariable("downloads-table").getSelectedRow()
def downloadsTable = builder.getVariable("downloads-table")
int selected = downloadsTable.getSelectedRow()
if (selected < 0)
return selected
if (lastDownloadSortEvent != null)
selected = lastDownloadSortEvent.convertPreviousRowIndexToModel(selected)
selected = downloadsTable.rowSorter.convertRowIndexToModel(selected)
selected
}

View File

@@ -7,10 +7,12 @@ import griffon.metadata.ArtifactProviderFor
import javax.swing.JDialog
import javax.swing.JPanel
import javax.swing.SwingConstants
import javax.swing.border.TitledBorder
import com.muwire.core.Core
import java.awt.BorderLayout
import java.awt.GridBagConstraints
import java.awt.event.WindowAdapter
import java.awt.event.WindowEvent
@@ -35,16 +37,32 @@ class MuWireStatusView {
panel = builder.panel {
gridBagLayout()
label(text : "Incoming connections", constraints : gbc(gridx:0, gridy:0))
label(text : bind {model.incomingConnections}, constraints : gbc(gridx:1, gridy:0))
label(text : "Outgoing connections", constraints : gbc(gridx:0, gridy:1))
label(text : bind {model.outgoingConnections}, constraints : gbc(gridx:1, gridy:1))
label(text : "Known hosts", constraints : gbc(gridx:0, gridy:2))
label(text : bind {model.knownHosts}, constraints : gbc(gridx:1, gridy:2))
label(text : "Shared files", constraints : gbc(gridx:0, gridy:3))
label(text : bind {model.sharedFiles}, constraints : gbc(gridx:1, gridy:3))
label(text : "Downloads", constraints : gbc(gridx:0, gridy:4))
label(text : bind {model.downloads}, constraints : gbc(gridx:1, gridy:4))
panel(border : titledBorder(title : "Connections", border : etchedBorder(), titlePosition : TitledBorder.TOP),
constraints : gbc(gridx : 0, gridy: 0, fill : GridBagConstraints.HORIZONTAL, weightx: 100)) {
gridBagLayout()
label(text : "Incoming", constraints : gbc(gridx:0, gridy:0, anchor : GridBagConstraints.LINE_START, weightx : 100))
label(text : bind {model.incomingConnections}, constraints : gbc(gridx:1, gridy:0, anchor : GridBagConstraints.LINE_END))
label(text : "Outgoing", constraints : gbc(gridx:0, gridy:1, anchor : GridBagConstraints.LINE_START, weightx : 100))
label(text : bind {model.outgoingConnections}, constraints : gbc(gridx:1, gridy:1, anchor : GridBagConstraints.LINE_END))
}
panel(border : titledBorder(title : "Hosts", border : etchedBorder(), titlePosition : TitledBorder.TOP),
constraints : gbc(gridx : 0, gridy : 1, fill : GridBagConstraints.HORIZONTAL, weightx: 100)) {
gridBagLayout()
label(text : "Known", constraints : gbc(gridx:0, gridy:0, anchor : GridBagConstraints.LINE_START, weightx : 100))
label(text : bind {model.knownHosts}, constraints : gbc(gridx:1, gridy:0, anchor : GridBagConstraints.LINE_END))
label(text : "Failing", constraints : gbc(gridx:0, gridy:1, anchor : GridBagConstraints.LINE_START, weightx : 100))
label(text : bind {model.failingHosts}, constraints : gbc(gridx:1, gridy:1, anchor : GridBagConstraints.LINE_END))
label(text : "Hopeless", constraints : gbc(gridx:0, gridy:2, anchor : GridBagConstraints.LINE_START, weightx : 100))
label(text : bind {model.hopelessHosts}, constraints : gbc(gridx:1, gridy:2, anchor : GridBagConstraints.LINE_END))
}
panel(border : titledBorder(title : "Files", border : etchedBorder(), titlePosition : TitledBorder.TOP),
constraints : gbc(gridx : 0, gridy : 2, fill : GridBagConstraints.HORIZONTAL, weightx: 100)) {
gridBagLayout()
label(text : "Shared", constraints : gbc(gridx:0, gridy:0, anchor : GridBagConstraints.LINE_START, weightx : 100))
label(text : bind {model.sharedFiles}, constraints : gbc(gridx:1, gridy:0, anchor : GridBagConstraints.LINE_END))
label(text : "Downloading", constraints : gbc(gridx:0, gridy:1, anchor : GridBagConstraints.LINE_START, weightx : 100))
label(text : bind {model.downloads}, constraints : gbc(gridx:1, gridy:1, anchor : GridBagConstraints.LINE_END))
}
}
buttonsPanel = builder.panel {
gridBagLayout()
@@ -60,7 +78,8 @@ class MuWireStatusView {
statusPanel.add(buttonsPanel, BorderLayout.SOUTH)
dialog.getContentPane().add(statusPanel)
dialog.pack()
dialog.setSize(200,300)
dialog.setResizable(false)
dialog.setLocationRelativeTo(mainFrame)
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
dialog.addWindowListener(new WindowAdapter() {

View File

@@ -3,11 +3,13 @@ package com.muwire.gui
import griffon.core.artifact.GriffonView
import griffon.inject.MVCMember
import griffon.metadata.ArtifactProviderFor
import groovy.swing.factory.TitledBorderFactory
import javax.swing.JDialog
import javax.swing.JPanel
import javax.swing.JTabbedPane
import javax.swing.SwingConstants
import javax.swing.border.TitledBorder
import com.muwire.core.Core
@@ -39,6 +41,7 @@ class OptionsView {
def shareHiddenCheckbox
def searchCommentsCheckbox
def browseFilesCheckbox
def speedSmoothSecondsField
def inboundLengthField
def inboundQuantityField
@@ -74,101 +77,156 @@ class OptionsView {
d.setResizable(false)
p = builder.panel {
gridBagLayout()
label(text : "Search in comments", constraints:gbc(gridx: 0, gridy:0))
searchCommentsCheckbox = checkBox(selected : bind {model.searchComments}, constraints : gbc(gridx:1, gridy:0))
panel (border : titledBorder(title : "Search Settings", border : etchedBorder(), titlePosition : TitledBorder.TOP),
constraints : gbc(gridx: 0, gridy : 0, fill : GridBagConstraints.HORIZONTAL)) {
gridBagLayout()
label(text : "Search in comments", constraints:gbc(gridx: 0, gridy:0, anchor : GridBagConstraints.LINE_START,
fill : GridBagConstraints.HORIZONTAL, weightx: 100))
searchCommentsCheckbox = checkBox(selected : bind {model.searchComments}, constraints : gbc(gridx:1, gridy:0,
anchor : GridBagConstraints.LINE_END, fill : GridBagConstraints.HORIZONTAL, weightx: 0))
label(text : "Allow browsing", constraints : gbc(gridx : 0, gridy : 1, anchor : GridBagConstraints.LINE_START,
fill : GridBagConstraints.HORIZONTAL, weightx: 100))
browseFilesCheckbox = checkBox(selected : bind {model.browseFiles}, constraints : gbc(gridx : 1, gridy : 1,
anchor : GridBagConstraints.LINE_END, fill : GridBagConstraints.HORIZONTAL, weightx: 0))
}
label(text : "Retry failed downloads every", constraints : gbc(gridx: 0, gridy: 1))
retryField = textField(text : bind { model.downloadRetryInterval }, columns : 2, constraints : gbc(gridx: 1, gridy: 1))
label(text : "seconds", constraints : gbc(gridx : 2, gridy: 1))
label(text : "Check for updates every", constraints : gbc(gridx : 0, gridy: 2))
updateField = textField(text : bind {model.updateCheckInterval }, columns : 2, constraints : gbc(gridx : 1, gridy: 2))
label(text : "hours", constraints : gbc(gridx: 2, gridy : 2))
label(text : "Download updates automatically", constraints: gbc(gridx :0, gridy : 3))
autoDownloadUpdateCheckbox = checkBox(selected : bind {model.autoDownloadUpdate}, constraints : gbc(gridx:1, gridy : 3))
label(text : "Share downloaded files", constraints : gbc(gridx : 0, gridy:4))
shareDownloadedCheckbox = checkBox(selected : bind {model.shareDownloadedFiles}, constraints : gbc(gridx :1, gridy:4))
panel (border : titledBorder(title : "Download Settings", border : etchedBorder(), titlePosition : TitledBorder.TOP,
constraints : gbc(gridx : 0, gridy : 1, fill : GridBagConstraints.HORIZONTAL))) {
gridBagLayout()
label(text : "Retry failed downloads every (seconds)", constraints : gbc(gridx: 0, gridy: 0, anchor : GridBagConstraints.LINE_START, weightx: 100))
retryField = textField(text : bind { model.downloadRetryInterval }, columns : 2,
constraints : gbc(gridx: 2, gridy: 0, anchor : GridBagConstraints.LINE_END, weightx: 0))
label(text : "Save downloaded files to:", constraints: gbc(gridx:0, gridy:1, anchor : GridBagConstraints.LINE_START))
label(text : bind {model.downloadLocation}, constraints: gbc(gridx:1, gridy:1, anchor : GridBagConstraints.LINE_START))
button(text : "Choose", constraints : gbc(gridx : 2, gridy:1), downloadLocationAction)
label(text : "Store incomplete files in:", constraints: gbc(gridx:0, gridy:2, anchor : GridBagConstraints.LINE_START))
label(text : bind {model.incompleteLocation}, constraints: gbc(gridx:1, gridy:2, anchor : GridBagConstraints.LINE_START))
button(text : "Choose", constraints : gbc(gridx : 2, gridy:2), incompleteLocationAction)
}
label(text : "Share hidden files", constraints : gbc(gridx : 0, gridy:5))
shareHiddenCheckbox = checkBox(selected : bind {model.shareHiddenFiles}, constraints : gbc(gridx :1, gridy:5))
panel (border : titledBorder(title : "Sharing Settings", border : etchedBorder(), titlePosition : TitledBorder.TOP,
constraints : gbc(gridx : 0, gridy : 2, fill : GridBagConstraints.HORIZONTAL))) {
gridBagLayout()
label(text : "Share downloaded files", constraints : gbc(gridx : 0, gridy:0, anchor : GridBagConstraints.LINE_START, weightx : 100))
shareDownloadedCheckbox = checkBox(selected : bind {model.shareDownloadedFiles}, constraints : gbc(gridx :1, gridy:0, weightx : 0))
label(text : "Share hidden files", constraints : gbc(gridx : 0, gridy:1, anchor : GridBagConstraints.LINE_START, weightx : 100))
shareHiddenCheckbox = checkBox(selected : bind {model.shareHiddenFiles}, constraints : gbc(gridx :1, gridy:1, weightx : 0))
}
label(text : "Allow browsing", constraints : gbc(gridx : 0, gridy : 6))
browseFilesCheckbox = checkBox(selected : bind {model.browseFiles}, constraints : gbc(gridx : 1, gridy : 6))
panel (border : titledBorder(title : "Update Settings", border : etchedBorder(), titlePosition : TitledBorder.TOP,
constraints : gbc(gridx : 0, gridy : 3, fill : GridBagConstraints.HORIZONTAL))) {
gridBagLayout()
label(text : "Check for updates every (hours)", constraints : gbc(gridx : 0, gridy: 0, anchor : GridBagConstraints.LINE_START, weightx : 100))
updateField = textField(text : bind {model.updateCheckInterval }, columns : 2, constraints : gbc(gridx : 1, gridy: 0, weightx: 0))
label(text : "Save downloaded files to:", constraints: gbc(gridx:0, gridy:7))
button(text : "Choose", constraints : gbc(gridx : 1, gridy:7), downloadLocationAction)
label(text : bind {model.downloadLocation}, constraints: gbc(gridx:0, gridy:8, gridwidth:2))
label(text : "Download updates automatically", constraints: gbc(gridx :0, gridy : 1, anchor : GridBagConstraints.LINE_START, weightx: 100))
autoDownloadUpdateCheckbox = checkBox(selected : bind {model.autoDownloadUpdate},
constraints : gbc(gridx:1, gridy : 1, anchor : GridBagConstraints.LINE_END))
}
}
i = builder.panel {
gridBagLayout()
label(text : "Changing these settings requires a restart", constraints : gbc(gridx : 0, gridy : 0, gridwidth: 2))
label(text : "Inbound Length", constraints : gbc(gridx:0, gridy:1))
inboundLengthField = textField(text : bind {model.inboundLength}, columns : 2, constraints : gbc(gridx:1, gridy:1))
label(text : "Inbound Quantity", constraints : gbc(gridx:0, gridy:2))
inboundQuantityField = textField(text : bind {model.inboundQuantity}, columns : 2, constraints : gbc(gridx:1, gridy:2))
label(text : "Outbound Length", constraints : gbc(gridx:0, gridy:3))
outboundLengthField = textField(text : bind {model.outboundLength}, columns : 2, constraints : gbc(gridx:1, gridy:3))
label(text : "Outbound Quantity", constraints : gbc(gridx:0, gridy:4))
outboundQuantityField = textField(text : bind {model.outboundQuantity}, columns : 2, constraints : gbc(gridx:1, gridy:4))
label(text : "Changing any I2P settings requires a restart", constraints : gbc(gridx:0, gridy : 0))
panel (border : titledBorder(title : "Tunnel Settings", border : etchedBorder(), titlePosition: TitledBorder.TOP,
constraints : gbc(gridx: 0, gridy: 1, fill : GridBagConstraints.HORIZONTAL, weightx : 100))) {
gridBagLayout()
label(text : "Inbound length", constraints : gbc(gridx:0, gridy:0, anchor : GridBagConstraints.LINE_START, weightx : 100))
inboundLengthField = textField(text : bind {model.inboundLength}, columns : 2, constraints : gbc(gridx:1, gridy:0,
anchor : GridBagConstraints.LINE_END))
label(text : "Inbound quantity", constraints : gbc(gridx:0, gridy:1, anchor : GridBagConstraints.LINE_START, weightx : 100))
inboundQuantityField = textField(text : bind {model.inboundQuantity}, columns : 2, constraints : gbc(gridx:1, gridy:1,
anchor : GridBagConstraints.LINE_END))
label(text : "Outbound length", constraints : gbc(gridx:0, gridy:2, anchor : GridBagConstraints.LINE_START, weightx : 100))
outboundLengthField = textField(text : bind {model.outboundLength}, columns : 2, constraints : gbc(gridx:1, gridy:2,
anchor : GridBagConstraints.LINE_END))
label(text : "Outbound quantity", constraints : gbc(gridx:0, gridy:3, anchor : GridBagConstraints.LINE_START, weightx : 100))
outboundQuantityField = textField(text : bind {model.outboundQuantity}, columns : 2, constraints : gbc(gridx:1, gridy:3,
anchor : GridBagConstraints.LINE_END))
}
Core core = application.context.get("core")
if (core.router != null) {
label(text : "TCP Port", constraints : gbc(gridx :0, gridy: 5))
i2pNTCPPortField = textField(text : bind {model.i2pNTCPPort}, columns : 4, constraints : gbc(gridx:1, gridy:5))
label(text : "UDP Port", constraints : gbc(gridx :0, gridy: 6))
i2pUDPPortField = textField(text : bind {model.i2pUDPPort}, columns : 4, constraints : gbc(gridx:1, gridy:6))
panel(border : titledBorder(title : "Port Settings", border : etchedBorder(), titlePosition : TitledBorder.TOP,
constraints : gbc(gridx: 0, gridy : 2, fill : GridBagConstraints.HORIZONTAL, weightx: 100))) {
gridBagLayout()
label(text : "TCP port", constraints : gbc(gridx :0, gridy: 0, anchor : GridBagConstraints.LINE_START, weightx : 100))
i2pNTCPPortField = textField(text : bind {model.i2pNTCPPort}, columns : 4, constraints : gbc(gridx:1, gridy:0, anchor : GridBagConstraints.LINE_END))
label(text : "UDP port", constraints : gbc(gridx :0, gridy: 1, anchor : GridBagConstraints.LINE_START, weightx : 100))
i2pUDPPortField = textField(text : bind {model.i2pUDPPort}, columns : 4, constraints : gbc(gridx:1, gridy:1, anchor : GridBagConstraints.LINE_END))
}
}
panel(constraints : gbc(gridx: 0, gridy: 3, weighty: 100))
}
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, anchor : GridBagConstraints.LINE_START))
label(text : "Font", constraints : gbc(gridx: 0, gridy : 2))
fontField = textField(text : bind {model.font}, columns : 4, constraints : gbc(gridx : 1, gridy:2, anchor : GridBagConstraints.LINE_START))
label(text : "Font Size", constraints : gbc(gridx: 0, gridy : 3))
buttonGroup(id: "fontSizeGroup")
radioButton(text: "Automatic", selected : bind {model.automaticFontSize}, buttonGroup : fontSizeGroup,
constraints : gbc(gridx : 1, gridy: 3, anchor : GridBagConstraints.LINE_START), automaticFontAction)
radioButton(text: "Custom", selected : bind {!model.automaticFontSize}, buttonGroup : fontSizeGroup,
constraints : gbc(gridx : 1, gridy: 4, anchor : GridBagConstraints.LINE_START), customFontAction)
fontSizeField = textField(text : bind {model.customFontSize}, enabled : bind {!model.automaticFontSize}, constraints : gbc(gridx : 2, gridy : 4))
label(text : "Automatically Clear Cancelled Downloads", constraints: gbc(gridx: 0, gridy:5))
clearCancelledDownloadsCheckbox = checkBox(selected : bind {model.clearCancelledDownloads},
constraints : gbc(gridx : 1, gridy:5, anchor : GridBagConstraints.LINE_START))
label(text : "Automatically Clear Finished Downloads", constraints: gbc(gridx: 0, gridy:6))
clearFinishedDownloadsCheckbox = checkBox(selected : bind {model.clearFinishedDownloads},
constraints : gbc(gridx : 1, gridy:6, anchor : GridBagConstraints.LINE_START))
label(text : "Exclude Local Files From Results", constraints: gbc(gridx:0, gridy:7))
excludeLocalResultCheckbox = checkBox(selected : bind {model.excludeLocalResult},
constraints : gbc(gridx: 1, gridy : 7, anchor : GridBagConstraints.LINE_START))
panel (border : titledBorder(title : "Theme Settings (changes require restart)", border : etchedBorder(), titlePosition: TitledBorder.TOP,
constraints : gbc(gridx : 0, gridy : 0, fill : GridBagConstraints.HORIZONTAL, weightx : 100))) {
gridBagLayout()
label(text : "Look And Feel", constraints : gbc(gridx: 0, gridy:0, anchor : GridBagConstraints.LINE_START, weightx: 100))
lnfField = textField(text : bind {model.lnf}, columns : 4, constraints : gbc(gridx : 3, gridy : 0, anchor : GridBagConstraints.LINE_START))
label(text : "Font", constraints : gbc(gridx: 0, gridy : 1, anchor : GridBagConstraints.LINE_START, weightx: 100))
fontField = textField(text : bind {model.font}, columns : 4, constraints : gbc(gridx : 3, gridy:1, anchor : GridBagConstraints.LINE_START))
label(text : "Font size", constraints : gbc(gridx: 0, gridy : 2, anchor : GridBagConstraints.LINE_START, weightx : 100))
buttonGroup(id: "fontSizeGroup")
radioButton(text: "Automatic", selected : bind {model.automaticFontSize}, buttonGroup : fontSizeGroup,
constraints : gbc(gridx : 1, gridy: 2, anchor : GridBagConstraints.LINE_START), automaticFontAction)
radioButton(text: "Custom", selected : bind {!model.automaticFontSize}, buttonGroup : fontSizeGroup,
constraints : gbc(gridx : 2, gridy: 2, anchor : GridBagConstraints.LINE_START), customFontAction)
fontSizeField = textField(text : bind {model.customFontSize}, enabled : bind {!model.automaticFontSize},
constraints : gbc(gridx : 3, gridy : 2, anchor : GridBagConstraints.LINE_END))
}
panel (border : titledBorder(title : "Other Settings", border : etchedBorder(), titlePosition : TitledBorder.TOP),
constraints : gbc(gridx : 0, gridy : 1, fill : GridBagConstraints.HORIZONTAL, weightx : 100)) {
gridBagLayout()
label(text : "Automatically clear cancelled downloads", constraints: gbc(gridx: 0, gridy:0, anchor : GridBagConstraints.LINE_START, weightx: 100))
clearCancelledDownloadsCheckbox = checkBox(selected : bind {model.clearCancelledDownloads},
constraints : gbc(gridx : 1, gridy:0, anchor : GridBagConstraints.LINE_END))
label(text : "Automatically flear finished downloads", constraints: gbc(gridx: 0, gridy:1, anchor : GridBagConstraints.LINE_START, weightx: 100))
clearFinishedDownloadsCheckbox = checkBox(selected : bind {model.clearFinishedDownloads},
constraints : gbc(gridx : 1, gridy:1, anchor : GridBagConstraints.LINE_END))
label(text : "Smooth download speed over (seconds)", constraints : gbc(gridx: 0, gridy : 2, anchor : GridBagConstraints.LINE_START, weightx: 100))
speedSmoothSecondsField = textField(text : bind {model.speedSmoothSeconds},
constraints : gbc(gridx:1, gridy: 2, anchor : GridBagConstraints.LINE_START))
label(text : "Exclude local files from results", constraints: gbc(gridx:0, gridy:3, anchor : GridBagConstraints.LINE_START, weightx: 100))
excludeLocalResultCheckbox = checkBox(selected : bind {model.excludeLocalResult},
constraints : gbc(gridx: 1, gridy : 3, anchor : GridBagConstraints.LINE_END))
}
panel (constraints : gbc(gridx: 0, gridy: 2, weighty: 100))
}
bandwidth = builder.panel {
gridBagLayout()
label(text : "Changing these settings requires a restart", constraints : gbc(gridx : 0, gridy : 0, gridwidth: 2))
label(text : "Inbound bandwidth (KB)", constraints : gbc(gridx: 0, gridy : 1))
inBwField = textField(text : bind {model.inBw}, columns : 3, constraints : gbc(gridx : 1, gridy : 1))
label(text : "Outbound bandwidth (KB)", constraints : gbc(gridx: 0, gridy : 2))
outBwField = textField(text : bind {model.outBw}, columns : 3, constraints : gbc(gridx : 1, gridy : 2))
panel( border : titledBorder(title : "Changing bandwidth settings requires a restart", border : etchedBorder(), titlePosition : TitledBorder.TOP),
constraints : gbc(gridx : 0, gridy : 0, fill : GridBagConstraints.HORIZONTAL, weightx : 100)) {
gridBagLayout()
label(text : "Inbound bandwidth (KB)", constraints : gbc(gridx: 0, gridy : 0, anchor : GridBagConstraints.LINE_START, weightx : 100))
inBwField = textField(text : bind {model.inBw}, columns : 3, constraints : gbc(gridx : 1, gridy : 0, anchor : GridBagConstraints.LINE_END))
label(text : "Outbound bandwidth (KB)", constraints : gbc(gridx: 0, gridy : 1, anchor : GridBagConstraints.LINE_START, weightx : 100))
outBwField = textField(text : bind {model.outBw}, columns : 3, constraints : gbc(gridx : 1, gridy : 1, anchor : GridBagConstraints.LINE_END))
}
panel(constraints : gbc(gridx: 0, gridy: 1, weighty: 100))
}
trust = builder.panel {
gridBagLayout()
label(text : "Allow only trusted connections", constraints : gbc(gridx: 0, gridy : 0))
allowUntrustedCheckbox = checkBox(selected : bind {model.onlyTrusted}, constraints : gbc(gridx: 1, gridy : 0))
label(text : "Search extra hop", constraints : gbc(gridx:0, gridy:1))
searchExtraHopCheckbox = checkBox(selected : bind {model.searchExtraHop}, constraints : gbc(gridx: 1, gridy : 1))
label(text : "Allow others to view my trust list", constraints : gbc(gridx: 0, gridy : 2))
allowTrustListsCheckbox = checkBox(selected : bind {model.trustLists}, constraints : gbc(gridx: 1, gridy : 2))
label(text : "Update trust lists every ", constraints : gbc(gridx:0, gridy:3))
trustListIntervalField = textField(text : bind {model.trustListInterval}, constraints:gbc(gridx:1, gridy:3))
label(text : "hours", constraints : gbc(gridx: 2, gridy:3))
panel (border : titledBorder(title : "Trust Settings", border : etchedBorder(), titlePosition : TitledBorder.TOP),
constraints : gbc(gridx : 0, gridy : 0, fill : GridBagConstraints.HORIZONTAL, weightx: 100)) {
gridBagLayout()
label(text : "Allow only trusted connections", constraints : gbc(gridx: 0, gridy : 0, anchor : GridBagConstraints.LINE_START, weightx: 100))
allowUntrustedCheckbox = checkBox(selected : bind {model.onlyTrusted}, constraints : gbc(gridx: 1, gridy : 0, anchor : GridBagConstraints.LINE_END))
label(text : "Search extra hop", constraints : gbc(gridx:0, gridy:1, anchor : GridBagConstraints.LINE_START, weightx : 100))
searchExtraHopCheckbox = checkBox(selected : bind {model.searchExtraHop}, constraints : gbc(gridx: 1, gridy : 1, anchor : GridBagConstraints.LINE_END))
label(text : "Allow others to view my trust list", constraints : gbc(gridx: 0, gridy : 2, anchor : GridBagConstraints.LINE_START, weightx : 100))
allowTrustListsCheckbox = checkBox(selected : bind {model.trustLists}, constraints : gbc(gridx: 1, gridy : 2, anchor : GridBagConstraints.LINE_END))
label(text : "Update trust lists every (hours)", constraints : gbc(gridx:0, gridy:3, anchor : GridBagConstraints.LINE_START, weightx : 100))
trustListIntervalField = textField(text : bind {model.trustListInterval}, constraints:gbc(gridx:1, gridy:3, anchor : GridBagConstraints.LINE_END))
}
panel(constraints : gbc(gridx: 0, gridy : 1, weighty: 100))
}

View File

@@ -72,10 +72,10 @@ class SearchTabView {
}
panel(constraints : BorderLayout.SOUTH) {
gridLayout(rows: 1, cols : 2)
panel {
panel (border : etchedBorder()){
button(text : "Browse Host", enabled : bind {model.browseActionEnabled}, browseAction)
}
panel {
panel (border : etchedBorder()){
button(text : "Trust", enabled: bind {model.trustButtonsEnabled }, trustAction)
button(text : "Neutral", enabled: bind {model.trustButtonsEnabled}, neutralAction)
button(text : "Distrust", enabled : bind {model.trustButtonsEnabled}, distrustAction)
@@ -100,6 +100,7 @@ class SearchTabView {
panel()
panel {
button(text : "Download", enabled : bind {model.downloadActionEnabled}, downloadAction)
button(text : "View Comment", enabled : bind {model.viewCommentActionEnabled}, showCommentAction)
}
panel {
gridBagLayout()
@@ -190,6 +191,16 @@ class SearchTabView {
}
})
resultsTable.getSelectionModel().addListSelectionListener({
def result = getSelectedResult()
if (result == null) {
model.viewCommentActionEnabled = false
return
} else {
model.viewCommentActionEnabled = result.comment != null
}
})
// senders table
sendersTable.setDefaultRenderer(Integer.class, centerRenderer)
sendersTable.rowSorter.addRowSorterListener({evt -> lastSendersSortEvent = evt})
@@ -241,12 +252,9 @@ class SearchTabView {
showMenu = true
// show comment if any
int selectedRow = resultsTable.getSelectedRow()
if (lastSortEvent != null)
selectedRow = resultsTable.rowSorter.convertRowIndexToModel(selectedRow)
if (model.results[selectedRow].comment != null) {
JMenuItem showComment = new JMenuItem("Show Comment")
showComment.addActionListener({mvcGroup.view.showComment()})
if (model.viewCommentActionEnabled) {
JMenuItem showComment = new JMenuItem("View Comment")
showComment.addActionListener({mvcGroup.controller.showComment()})
menu.add(showComment)
}
}
@@ -283,23 +291,6 @@ class SearchTabView {
clipboard.setContents(selection, null)
}
def showComment() {
int selectedRow = resultsTable.getSelectedRow()
if (selectedRow < 0)
return
if (lastSortEvent != null)
selectedRow = resultsTable.rowSorter.convertRowIndexToModel(selectedRow)
UIResultEvent event = model.results[selectedRow]
if (event.comment == null)
return
String groupId = Base64.encode(event.infohash.getRoot())
Map<String,Object> params = new HashMap<>()
params['result'] = event
mvcGroup.createMVCGroup("show-comment", groupId, params)
}
int selectedSenderRow() {
int row = sendersTable.getSelectedRow()
if (row < 0)