Compare commits
22 Commits
muwire-0.5
...
muwire-0.5
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ff1df88601 | ||
![]() |
4ed572ba51 | ||
![]() |
fd3f55ab4d | ||
![]() |
1358e14467 | ||
![]() |
e22d5fea11 | ||
![]() |
7ade4aa10d | ||
![]() |
a9f623a91a | ||
![]() |
1ce410e943 | ||
![]() |
27aad9d75d | ||
![]() |
24591b10f2 | ||
![]() |
e4f1ea5c10 | ||
![]() |
c73c44c5f2 | ||
![]() |
309cbcc580 | ||
![]() |
86894f242b | ||
![]() |
568255140f | ||
![]() |
f6d2bac5bb | ||
![]() |
1c396711ed | ||
![]() |
c154d9538d | ||
![]() |
8043782446 | ||
![]() |
00c529cca1 | ||
![]() |
094b9ac2b0 | ||
![]() |
0dae0a561b |
@@ -32,7 +32,7 @@ import com.muwire.core.UILoadedEvent
|
||||
import com.muwire.core.files.AllFilesLoadedEvent
|
||||
|
||||
class CliLanterna {
|
||||
private static final String MW_VERSION = "0.5.6"
|
||||
private static final String MW_VERSION = "0.5.7"
|
||||
|
||||
private static volatile Core core
|
||||
|
||||
@@ -82,14 +82,14 @@ class CliLanterna {
|
||||
props.setDownloadLocation(downloadLocationFile)
|
||||
props.incompleteLocation = incompletesLocationFile
|
||||
|
||||
propsFile.withOutputStream {
|
||||
propsFile.withPrintWriter("UTF-8", {
|
||||
props.write(it)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
props = new Properties()
|
||||
propsFile.withInputStream {
|
||||
propsFile.withReader("UTF-8", {
|
||||
props.load(it)
|
||||
}
|
||||
})
|
||||
props = new MuWireSettings(props)
|
||||
}
|
||||
props.updateType = "cli-lanterna"
|
||||
|
@@ -8,7 +8,11 @@ import com.muwire.core.search.SearchEvent
|
||||
import com.muwire.core.search.UIResultBatchEvent
|
||||
import com.muwire.core.search.UIResultEvent
|
||||
|
||||
import net.i2p.crypto.DSAEngine
|
||||
import net.i2p.data.Base64
|
||||
import net.i2p.data.Signature
|
||||
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
import com.googlecode.lanterna.gui2.TextGUIThread
|
||||
import com.googlecode.lanterna.gui2.table.TableModel
|
||||
@@ -40,20 +44,27 @@ class SearchModel {
|
||||
}
|
||||
|
||||
def searchEvent
|
||||
byte [] payload
|
||||
if (hashSearch) {
|
||||
searchEvent = new SearchEvent(searchHash : root, uuid : UUID.randomUUID(), oobInfohash : true, compressedResults : true)
|
||||
payload = root
|
||||
} else {
|
||||
def replaced = query.toLowerCase().trim().replaceAll(SplitPattern.SPLIT_PATTERN, " ")
|
||||
def terms = replaced.split(" ")
|
||||
def nonEmpty = []
|
||||
terms.each { if (it.length() > 0) nonEmpty << it }
|
||||
payload = String.join(" ", nonEmpty).getBytes(StandardCharsets.UTF_8)
|
||||
searchEvent = new SearchEvent(searchTerms : nonEmpty, uuid : UUID.randomUUID(), oobInfohash: true,
|
||||
searchComments : core.muOptions.searchComments, compressedResults : true)
|
||||
}
|
||||
|
||||
boolean firstHop = core.muOptions.allowUntrusted || core.muOptions.searchExtraHop
|
||||
|
||||
Signature sig = DSAEngine.getInstance().sign(payload, core.spk)
|
||||
|
||||
core.eventBus.publish(new QueryEvent(searchEvent : searchEvent, firstHop : firstHop,
|
||||
replyTo: core.me.destination, receivedOn: core.me.destination,
|
||||
originator : core.me))
|
||||
originator : core.me, sig: sig.data))
|
||||
}
|
||||
|
||||
void unregister() {
|
||||
|
@@ -199,6 +199,6 @@ class TrustView extends BasicWindow {
|
||||
|
||||
private void saveMuSettings() {
|
||||
File settingsFile = new File(core.home,"MuWire.properties")
|
||||
settingsFile.withOutputStream { core.muOptions.write(it) }
|
||||
settingsFile.withPrintWriter("UTF-8",{ core.muOptions.write(it) })
|
||||
}
|
||||
}
|
||||
|
@@ -103,6 +103,8 @@ public class Core {
|
||||
private final Router router
|
||||
|
||||
final AtomicBoolean shutdown = new AtomicBoolean()
|
||||
|
||||
final SigningPrivateKey spk
|
||||
|
||||
public Core(MuWireSettings props, File home, String myVersion) {
|
||||
this.home = home
|
||||
@@ -180,7 +182,7 @@ public class Core {
|
||||
i2pSession = socketManager.getSession()
|
||||
|
||||
def destination = new Destination()
|
||||
def spk = new SigningPrivateKey(Constants.SIG_TYPE)
|
||||
spk = new SigningPrivateKey(Constants.SIG_TYPE)
|
||||
keyDat.withInputStream {
|
||||
destination.readBytes(it)
|
||||
def privateKey = new PrivateKey()
|
||||
@@ -191,8 +193,9 @@ public class Core {
|
||||
def baos = new ByteArrayOutputStream()
|
||||
def daos = new DataOutputStream(baos)
|
||||
daos.write(Constants.PERSONA_VERSION)
|
||||
daos.writeShort((short)props.getNickname().length())
|
||||
daos.write(props.getNickname().getBytes(StandardCharsets.UTF_8))
|
||||
byte [] name = props.getNickname().getBytes(StandardCharsets.UTF_8)
|
||||
daos.writeShort((short)name.length)
|
||||
daos.write(name)
|
||||
destination.writeBytes(daos)
|
||||
daos.flush()
|
||||
byte [] payload = baos.toByteArray()
|
||||
@@ -335,8 +338,7 @@ public class Core {
|
||||
return
|
||||
}
|
||||
log.info("saving settings")
|
||||
File f = new File(home, "MuWire.properties")
|
||||
f.withOutputStream { muOptions.write(it) }
|
||||
saveMuSettings()
|
||||
log.info("shutting down trust subscriber")
|
||||
trustSubscriber.stop()
|
||||
log.info("shutting down download manageer")
|
||||
@@ -357,6 +359,11 @@ public class Core {
|
||||
}
|
||||
log.info("shutdown complete")
|
||||
}
|
||||
|
||||
public void saveMuSettings() {
|
||||
File f = new File(home, "MuWire.properties")
|
||||
f.withPrintWriter("UTF-8", { muOptions.write(it) })
|
||||
}
|
||||
|
||||
static main(args) {
|
||||
def home = System.getProperty("user.home") + File.separator + ".MuWire"
|
||||
@@ -382,7 +389,7 @@ public class Core {
|
||||
}
|
||||
}
|
||||
|
||||
Core core = new Core(props, home, "0.5.6")
|
||||
Core core = new Core(props, home, "0.5.7")
|
||||
core.startServices()
|
||||
|
||||
// ... at the end, sleep or execute script
|
||||
|
@@ -78,10 +78,10 @@ class MuWireSettings {
|
||||
totalUploadSlots = Integer.valueOf(props.getProperty("totalUploadSlots","-1"))
|
||||
uploadSlotsPerUser = Integer.valueOf(props.getProperty("uploadSlotsPerUser","-1"))
|
||||
|
||||
watchedDirectories = readEncodedSet(props, "watchedDirectories")
|
||||
watchedKeywords = readEncodedSet(props, "watchedKeywords")
|
||||
watchedRegexes = readEncodedSet(props, "watchedRegexes")
|
||||
negativeFileTree = readEncodedSet(props, "negativeFileTree")
|
||||
watchedDirectories = DataUtil.readEncodedSet(props, "watchedDirectories")
|
||||
watchedKeywords = DataUtil.readEncodedSet(props, "watchedKeywords")
|
||||
watchedRegexes = DataUtil.readEncodedSet(props, "watchedRegexes")
|
||||
negativeFileTree = DataUtil.readEncodedSet(props, "negativeFileTree")
|
||||
|
||||
trustSubscriptions = new HashSet<>()
|
||||
if (props.containsKey("trustSubscriptions")) {
|
||||
@@ -93,7 +93,7 @@ class MuWireSettings {
|
||||
|
||||
}
|
||||
|
||||
void write(OutputStream out) throws IOException {
|
||||
void write(Writer out) throws IOException {
|
||||
Properties props = new Properties()
|
||||
props.setProperty("leaf", isLeaf.toString())
|
||||
props.setProperty("allowUntrusted", allowUntrusted.toString())
|
||||
@@ -125,10 +125,10 @@ class MuWireSettings {
|
||||
props.setProperty("totalUploadSlots", String.valueOf(totalUploadSlots))
|
||||
props.setProperty("uploadSlotsPerUser", String.valueOf(uploadSlotsPerUser))
|
||||
|
||||
writeEncodedSet(watchedDirectories, "watchedDirectories", props)
|
||||
writeEncodedSet(watchedKeywords, "watchedKeywords", props)
|
||||
writeEncodedSet(watchedRegexes, "watchedRegexes", props)
|
||||
writeEncodedSet(negativeFileTree, "negativeFileTree", props)
|
||||
DataUtil.writeEncodedSet(watchedDirectories, "watchedDirectories", props)
|
||||
DataUtil.writeEncodedSet(watchedKeywords, "watchedKeywords", props)
|
||||
DataUtil.writeEncodedSet(watchedRegexes, "watchedRegexes", props)
|
||||
DataUtil.writeEncodedSet(negativeFileTree, "negativeFileTree", props)
|
||||
|
||||
if (!trustSubscriptions.isEmpty()) {
|
||||
String encoded = trustSubscriptions.stream().
|
||||
@@ -137,25 +137,7 @@ class MuWireSettings {
|
||||
props.setProperty("trustSubscriptions", encoded)
|
||||
}
|
||||
|
||||
props.store(out, "")
|
||||
}
|
||||
|
||||
private static Set<String> readEncodedSet(Properties props, String property) {
|
||||
Set<String> rv = new ConcurrentHashSet<>()
|
||||
if (props.containsKey(property)) {
|
||||
String[] encoded = props.getProperty(property).split(",")
|
||||
encoded.each { rv << DataUtil.readi18nString(Base64.decode(it)) }
|
||||
}
|
||||
rv
|
||||
}
|
||||
|
||||
private static void writeEncodedSet(Set<String> set, String property, Properties props) {
|
||||
if (set.isEmpty())
|
||||
return
|
||||
String encoded = set.stream().
|
||||
map({Base64.encode(DataUtil.encodei18nString(it))}).
|
||||
collect(Collectors.joining(","))
|
||||
props.setProperty(property, encoded)
|
||||
props.store(out, "This file is UTF-8")
|
||||
}
|
||||
|
||||
boolean isLeaf() {
|
||||
|
@@ -22,8 +22,9 @@ public class Name {
|
||||
|
||||
public void write(OutputStream out) throws IOException {
|
||||
DataOutputStream dos = new DataOutputStream(out)
|
||||
dos.writeShort(name.length())
|
||||
dos.write(name.getBytes(StandardCharsets.UTF_8))
|
||||
byte [] bytes = name.getBytes(StandardCharsets.UTF_8)
|
||||
dos.writeShort(bytes.length)
|
||||
dos.write(bytes)
|
||||
}
|
||||
|
||||
public getName() {
|
||||
|
@@ -1,5 +1,6 @@
|
||||
package com.muwire.core.connection
|
||||
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.concurrent.BlockingQueue
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.ExecutorService
|
||||
@@ -10,6 +11,7 @@ import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.logging.Level
|
||||
|
||||
import com.muwire.core.Constants
|
||||
import com.muwire.core.EventBus
|
||||
import com.muwire.core.MuWireSettings
|
||||
import com.muwire.core.Persona
|
||||
@@ -21,8 +23,10 @@ import com.muwire.core.trust.TrustLevel
|
||||
import com.muwire.core.trust.TrustService
|
||||
|
||||
import groovy.util.logging.Log
|
||||
import net.i2p.crypto.DSAEngine
|
||||
import net.i2p.data.Base64
|
||||
import net.i2p.data.Destination
|
||||
import net.i2p.data.Signature
|
||||
|
||||
@Log
|
||||
abstract class Connection implements Closeable {
|
||||
@@ -147,6 +151,8 @@ abstract class Connection implements Closeable {
|
||||
query.replyTo = e.replyTo.toBase64()
|
||||
if (e.originator != null)
|
||||
query.originator = e.originator.toBase64()
|
||||
if (e.sig != null)
|
||||
query.sig = Base64.encode(e.sig)
|
||||
messages.put(query)
|
||||
}
|
||||
|
||||
@@ -225,6 +231,24 @@ abstract class Connection implements Closeable {
|
||||
boolean compressedResults = false
|
||||
if (search.compressedResults != null)
|
||||
compressedResults = search.compressedResults
|
||||
byte[] sig = null
|
||||
// TODO: make this mandatory at some point
|
||||
if (search.sig != null) {
|
||||
sig = Base64.decode(search.sig)
|
||||
byte [] payload
|
||||
if (infohash != null)
|
||||
payload = infohash
|
||||
else
|
||||
payload = String.join(" ",search.keywords).getBytes(StandardCharsets.UTF_8)
|
||||
def spk = originator.destination.getSigningPublicKey()
|
||||
def signature = new Signature(Constants.SIG_TYPE, sig)
|
||||
if (!DSAEngine.getInstance().verifySignature(signature, payload, spk)) {
|
||||
log.info("signature didn't match keywords")
|
||||
return
|
||||
} else
|
||||
log.info("query signature verified")
|
||||
} else
|
||||
log.info("no signature in query")
|
||||
|
||||
SearchEvent searchEvent = new SearchEvent(searchTerms : search.keywords,
|
||||
searchHash : infohash,
|
||||
@@ -236,7 +260,8 @@ abstract class Connection implements Closeable {
|
||||
replyTo : replyTo,
|
||||
originator : originator,
|
||||
receivedOn : endpoint.destination,
|
||||
firstHop : search.firstHop )
|
||||
firstHop : search.firstHop,
|
||||
sig : sig )
|
||||
eventBus.publish(event)
|
||||
|
||||
}
|
||||
|
@@ -21,6 +21,7 @@ import java.nio.file.Files
|
||||
import java.nio.file.StandardOpenOption
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import java.util.logging.Level
|
||||
|
||||
@Log
|
||||
@@ -37,13 +38,12 @@ class DownloadSession {
|
||||
private final Set<Integer> available
|
||||
private final MessageDigest digest
|
||||
|
||||
private long lastSpeedRead = System.currentTimeMillis()
|
||||
private long dataSinceLastRead
|
||||
private final AtomicLong dataSinceLastRead
|
||||
|
||||
private MappedByteBuffer mapped
|
||||
|
||||
DownloadSession(EventBus eventBus, String meB64, Pieces pieces, InfoHash infoHash, Endpoint endpoint, File file,
|
||||
int pieceSize, long fileLength, Set<Integer> available) {
|
||||
int pieceSize, long fileLength, Set<Integer> available, AtomicLong dataSinceLastRead) {
|
||||
this.eventBus = eventBus
|
||||
this.meB64 = meB64
|
||||
this.pieces = pieces
|
||||
@@ -53,6 +53,7 @@ class DownloadSession {
|
||||
this.pieceSize = pieceSize
|
||||
this.fileLength = fileLength
|
||||
this.available = available
|
||||
this.dataSinceLastRead = dataSinceLastRead
|
||||
try {
|
||||
digest = MessageDigest.getInstance("SHA-256")
|
||||
} catch (NoSuchAlgorithmException impossible) {
|
||||
@@ -190,7 +191,7 @@ class DownloadSession {
|
||||
throw new IOException()
|
||||
synchronized(this) {
|
||||
mapped.put(tmp, 0, read)
|
||||
dataSinceLastRead += read
|
||||
dataSinceLastRead.addAndGet(read)
|
||||
pieces.markPartial(piece, mapped.position())
|
||||
}
|
||||
}
|
||||
@@ -222,13 +223,4 @@ class DownloadSession {
|
||||
return 0
|
||||
mapped.position()
|
||||
}
|
||||
|
||||
synchronized int speed() {
|
||||
final long now = System.currentTimeMillis()
|
||||
long interval = Math.max(1000, now - lastSpeedRead)
|
||||
lastSpeedRead = now;
|
||||
int rv = (int) (dataSinceLastRead * 1000.0 / interval)
|
||||
dataSinceLastRead = 0
|
||||
rv
|
||||
}
|
||||
}
|
||||
|
@@ -12,6 +12,7 @@ import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import java.util.logging.Level
|
||||
|
||||
import com.muwire.core.Constants
|
||||
@@ -61,10 +62,11 @@ public class Downloader {
|
||||
private final AtomicBoolean eventFired = new AtomicBoolean()
|
||||
private boolean piecesFileClosed
|
||||
|
||||
private final AtomicLong dataSinceLastRead = new AtomicLong(0)
|
||||
private volatile long lastSpeedRead = System.currentTimeMillis()
|
||||
private ArrayList speedArr = new ArrayList<Integer>()
|
||||
private int speedPos = 0
|
||||
private int speedAvg = 0
|
||||
private long timestamp = Instant.now().toEpochMilli()
|
||||
|
||||
public Downloader(EventBus eventBus, DownloadManager downloadManager,
|
||||
Persona me, File file, long length, InfoHash infoHash,
|
||||
@@ -139,10 +141,11 @@ public class Downloader {
|
||||
public int speed() {
|
||||
int currSpeed = 0
|
||||
if (getCurrentState() == DownloadState.DOWNLOADING) {
|
||||
activeWorkers.values().each {
|
||||
if (it.currentState == WorkerState.DOWNLOADING)
|
||||
currSpeed += it.speed()
|
||||
}
|
||||
long dataRead = dataSinceLastRead.getAndSet(0)
|
||||
long now = System.currentTimeMillis()
|
||||
if (now > lastSpeedRead)
|
||||
currSpeed = (int) (dataRead * 1000.0 / (now - lastSpeedRead))
|
||||
lastSpeedRead = now
|
||||
}
|
||||
|
||||
if (speedArr.size() != downloadManager.muSettings.speedSmoothSeconds) {
|
||||
@@ -302,7 +305,7 @@ public class Downloader {
|
||||
boolean requestPerformed
|
||||
while(!pieces.isComplete()) {
|
||||
currentSession = new DownloadSession(eventBus, me.toBase64(), pieces, getInfoHash(),
|
||||
endpoint, incompleteFile, pieceSize, length, available)
|
||||
endpoint, incompleteFile, pieceSize, length, available, dataSinceLastRead)
|
||||
requestPerformed = currentSession.request()
|
||||
if (!requestPerformed)
|
||||
break
|
||||
@@ -339,12 +342,6 @@ public class Downloader {
|
||||
}
|
||||
}
|
||||
|
||||
int speed() {
|
||||
if (currentSession == null)
|
||||
return 0
|
||||
currentSession.speed()
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
downloadThread?.interrupt()
|
||||
}
|
||||
|
@@ -86,9 +86,9 @@ class DirectoryWatcher {
|
||||
|
||||
private void saveMuSettings() {
|
||||
File muSettingsFile = new File(home, "MuWire.properties")
|
||||
muSettingsFile.withOutputStream {
|
||||
muSettingsFile.withPrintWriter("UTF-8", {
|
||||
muOptions.write(it)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private void watch() {
|
||||
|
@@ -45,7 +45,7 @@ class FileTree {
|
||||
true
|
||||
}
|
||||
|
||||
private static class TreeNode {
|
||||
public static class TreeNode {
|
||||
TreeNode parent
|
||||
File file
|
||||
final Set<TreeNode> children = new HashSet<>()
|
||||
|
@@ -34,9 +34,12 @@ class HasherService {
|
||||
return
|
||||
if (fileManager.fileToSharedFile.containsKey(canonical))
|
||||
return
|
||||
if (canonical.getName().endsWith(".mwcomment") && canonical.length() <= Constants.MAX_COMMENT_LENGTH)
|
||||
eventBus.publish(new SideCarFileEvent(file : canonical))
|
||||
else if (hashed.add(canonical))
|
||||
if (canonical.isFile() && fileManager.negativeTree.fileToNode.containsKey(canonical))
|
||||
return
|
||||
if (canonical.getName().endsWith(".mwcomment")) {
|
||||
if (canonical.length() <= Constants.MAX_COMMENT_LENGTH)
|
||||
eventBus.publish(new SideCarFileEvent(file : canonical))
|
||||
} else if (hashed.add(canonical))
|
||||
executor.execute( { -> process(canonical) } as Runnable)
|
||||
}
|
||||
|
||||
|
@@ -4,4 +4,9 @@ import com.muwire.core.Event
|
||||
|
||||
class SideCarFileEvent extends Event {
|
||||
File file
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + " file: "+file.getAbsolutePath()
|
||||
}
|
||||
}
|
||||
|
@@ -12,6 +12,7 @@ class QueryEvent extends Event {
|
||||
Destination replyTo
|
||||
Persona originator
|
||||
Destination receivedOn
|
||||
byte[] sig
|
||||
|
||||
String toString() {
|
||||
"searchEvent: $searchEvent firstHop:$firstHop, replyTo:${replyTo.toBase32()}" +
|
||||
|
@@ -11,10 +11,14 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.muwire.core.Constants;
|
||||
|
||||
import net.i2p.data.Base64;
|
||||
import net.i2p.util.ConcurrentHashSet;
|
||||
|
||||
public class DataUtil {
|
||||
|
||||
@@ -165,4 +169,22 @@ public class DataUtil {
|
||||
} catch(Exception ex) { }
|
||||
cb = null;
|
||||
}
|
||||
|
||||
public static Set<String> readEncodedSet(Properties props, String property) {
|
||||
Set<String> rv = new ConcurrentHashSet<>();
|
||||
if (props.containsKey(property)) {
|
||||
String [] encoded = props.getProperty(property).split(",");
|
||||
for(String s : encoded)
|
||||
rv.add(readi18nString(Base64.decode(s)));
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static void writeEncodedSet(Set<String> set, String property, Properties props) {
|
||||
if (set.isEmpty())
|
||||
return;
|
||||
String encoded = set.stream().map(s -> Base64.encode(encodei18nString(s)))
|
||||
.collect(Collectors.joining(","));
|
||||
props.setProperty(property, encoded);
|
||||
}
|
||||
}
|
||||
|
@@ -2,6 +2,8 @@ package com.muwire.core.download
|
||||
|
||||
import static org.junit.Assert.fail
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
@@ -76,7 +78,7 @@ class DownloadSessionTest {
|
||||
toUploader = new PipedOutputStream(fromDownloader)
|
||||
endpoint = new Endpoint(null, fromUploader, toUploader, null)
|
||||
|
||||
session = new DownloadSession(eventBus, "",pieces, infoHash, endpoint, target, pieceSize, size, available)
|
||||
session = new DownloadSession(eventBus, "",pieces, infoHash, endpoint, target, pieceSize, size, available, new AtomicLong())
|
||||
downloadThread = new Thread( { perform() } as Runnable)
|
||||
downloadThread.setDaemon(true)
|
||||
downloadThread.start()
|
||||
|
@@ -1,11 +1,12 @@
|
||||
group = com.muwire
|
||||
version = 0.5.6
|
||||
version = 0.5.7
|
||||
i2pVersion = 0.9.43
|
||||
groovyVersion = 2.4.15
|
||||
slf4jVersion = 1.7.25
|
||||
spockVersion = 1.1-groovy-2.4
|
||||
grailsVersion=4.0.0
|
||||
gorm.version=7.0.2.RELEASE
|
||||
griffonEnv=prod
|
||||
|
||||
sourceCompatibility=1.8
|
||||
targetCompatibility=1.8
|
||||
|
@@ -81,4 +81,9 @@ mvcGroups {
|
||||
view = 'com.muwire.gui.UpdateView'
|
||||
controller = 'com.muwire.gui.UpdateController'
|
||||
}
|
||||
'advanced-sharing' {
|
||||
model = 'com.muwire.gui.AdvancedSharingModel'
|
||||
view = 'com.muwire.gui.AdvancedSharingView'
|
||||
controller = 'com.muwire.gui.AdvancedSharingController'
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,17 @@
|
||||
package com.muwire.gui
|
||||
|
||||
import griffon.core.artifact.GriffonController
|
||||
import griffon.core.controller.ControllerAction
|
||||
import griffon.inject.MVCMember
|
||||
import griffon.metadata.ArtifactProviderFor
|
||||
import javax.annotation.Nonnull
|
||||
|
||||
import com.muwire.core.Core
|
||||
|
||||
@ArtifactProviderFor(GriffonController)
|
||||
class AdvancedSharingController {
|
||||
@MVCMember @Nonnull
|
||||
AdvancedSharingModel model
|
||||
@MVCMember @Nonnull
|
||||
AdvancedSharingView view
|
||||
}
|
@@ -97,9 +97,6 @@ class ContentPanelController {
|
||||
}
|
||||
|
||||
void saveMuWireSettings() {
|
||||
File f = new File(core.home, "MuWire.properties")
|
||||
f.withOutputStream {
|
||||
core.muOptions.write(it)
|
||||
}
|
||||
core.saveMuSettings()
|
||||
}
|
||||
}
|
@@ -7,7 +7,12 @@ import griffon.core.mvc.MVCGroup
|
||||
import griffon.core.mvc.MVCGroupConfiguration
|
||||
import griffon.inject.MVCMember
|
||||
import griffon.metadata.ArtifactProviderFor
|
||||
import net.i2p.crypto.DSAEngine
|
||||
import net.i2p.data.Base64
|
||||
import net.i2p.data.Signature
|
||||
|
||||
import java.awt.event.ActionEvent
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
import javax.annotation.Nonnull
|
||||
import javax.inject.Inject
|
||||
@@ -47,11 +52,29 @@ class MainFrameController {
|
||||
private volatile Core core
|
||||
|
||||
@ControllerAction
|
||||
void search() {
|
||||
void clearSearch() {
|
||||
def searchField = builder.getVariable("search-field")
|
||||
searchField.setSelectedItem(null)
|
||||
searchField.requestFocus()
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void search(ActionEvent evt) {
|
||||
if (evt?.getActionCommand() == null)
|
||||
return
|
||||
def cardsPanel = builder.getVariable("cards-panel")
|
||||
cardsPanel.getLayout().show(cardsPanel, "search window")
|
||||
|
||||
def search = builder.getVariable("search-field").text
|
||||
def searchField = builder.getVariable("search-field")
|
||||
def search = searchField.getSelectedItem()
|
||||
searchField.model.addElement(search)
|
||||
performSearch(search)
|
||||
}
|
||||
|
||||
private void performSearch(String search) {
|
||||
|
||||
model.sessionRestored = true
|
||||
|
||||
search = search.trim()
|
||||
if (search.length() == 0)
|
||||
return
|
||||
@@ -77,21 +100,28 @@ class MainFrameController {
|
||||
}
|
||||
|
||||
def searchEvent
|
||||
byte [] payload
|
||||
if (hashSearch) {
|
||||
searchEvent = new SearchEvent(searchHash : root, uuid : uuid, oobInfohash: true, compressedResults : true)
|
||||
payload = root
|
||||
} else {
|
||||
// this can be improved a lot
|
||||
def replaced = search.toLowerCase().trim().replaceAll(SplitPattern.SPLIT_PATTERN, " ")
|
||||
def terms = replaced.split(" ")
|
||||
def nonEmpty = []
|
||||
terms.each { if (it.length() > 0) nonEmpty << it }
|
||||
searchEvent = new SearchEvent(searchTerms : nonEmpty, uuid : uuid, oobInfohash: true,
|
||||
searchComments : core.muOptions.searchComments, compressedResults : true)
|
||||
payload = String.join(" ",nonEmpty).getBytes(StandardCharsets.UTF_8)
|
||||
searchEvent = new SearchEvent(searchTerms : nonEmpty, uuid : uuid, oobInfohash: true,
|
||||
searchComments : core.muOptions.searchComments, compressedResults : true)
|
||||
}
|
||||
boolean firstHop = core.muOptions.allowUntrusted || core.muOptions.searchExtraHop
|
||||
|
||||
Signature sig = DSAEngine.getInstance().sign(payload, core.spk)
|
||||
|
||||
core.eventBus.publish(new QueryEvent(searchEvent : searchEvent, firstHop : firstHop,
|
||||
replyTo: core.me.destination, receivedOn: core.me.destination,
|
||||
originator : core.me))
|
||||
replyTo: core.me.destination, receivedOn: core.me.destination,
|
||||
originator : core.me, sig : sig.data))
|
||||
|
||||
}
|
||||
|
||||
void search(String infoHash, String tabTitle) {
|
||||
@@ -297,12 +327,17 @@ class MainFrameController {
|
||||
void clearUploads() {
|
||||
model.uploads.removeAll { it.finished }
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void restoreSession() {
|
||||
model.sessionRestored = true
|
||||
view.settings.openTabs.each {
|
||||
performSearch(it)
|
||||
}
|
||||
}
|
||||
|
||||
void saveMuWireSettings() {
|
||||
File f = new File(core.home, "MuWire.properties")
|
||||
f.withOutputStream {
|
||||
core.muOptions.write(it)
|
||||
}
|
||||
core.saveMuSettings()
|
||||
}
|
||||
|
||||
void mvcGroupInit(Map<String, String> args) {
|
||||
|
@@ -10,6 +10,7 @@ import java.util.logging.Level
|
||||
|
||||
import javax.annotation.Nonnull
|
||||
import javax.swing.JFileChooser
|
||||
import javax.swing.JOptionPane
|
||||
|
||||
import com.muwire.core.Core
|
||||
import com.muwire.core.MuWireSettings
|
||||
@@ -20,12 +21,14 @@ class OptionsController {
|
||||
OptionsModel model
|
||||
@MVCMember @Nonnull
|
||||
OptionsView view
|
||||
|
||||
Core core
|
||||
MuWireSettings settings
|
||||
UISettings uiSettings
|
||||
|
||||
@ControllerAction
|
||||
void save() {
|
||||
String text
|
||||
Core core = application.context.get("core")
|
||||
MuWireSettings settings = application.context.get("muwire-settings")
|
||||
|
||||
def i2pProps = core.i2pOptions
|
||||
|
||||
@@ -137,14 +140,10 @@ class OptionsController {
|
||||
model.trustListInterval = trustListInterval
|
||||
settings.trustListInterval = Integer.parseInt(trustListInterval)
|
||||
|
||||
File settingsFile = new File(core.home, "MuWire.properties")
|
||||
settingsFile.withOutputStream {
|
||||
settings.write(it)
|
||||
}
|
||||
core.saveMuSettings()
|
||||
|
||||
// UI Setttings
|
||||
|
||||
UISettings uiSettings = application.context.get("ui-settings")
|
||||
text = view.lnfField.text
|
||||
model.lnf = text
|
||||
uiSettings.lnf = text
|
||||
@@ -172,16 +171,24 @@ class OptionsController {
|
||||
model.clearUploads = clearUploads
|
||||
uiSettings.clearUploads = clearUploads
|
||||
|
||||
boolean storeSearchHistory = view.storeSearchHistoryCheckbox.model.isSelected()
|
||||
model.storeSearchHistory = storeSearchHistory
|
||||
uiSettings.storeSearchHistory = storeSearchHistory
|
||||
|
||||
uiSettings.exitOnClose = model.exitOnClose
|
||||
if (model.closeDecisionMade)
|
||||
uiSettings.closeWarning = false
|
||||
|
||||
saveUISettings()
|
||||
|
||||
cancel()
|
||||
}
|
||||
|
||||
private void saveUISettings() {
|
||||
File uiSettingsFile = new File(core.home, "gui.properties")
|
||||
uiSettingsFile.withOutputStream {
|
||||
uiSettings.write(it)
|
||||
}
|
||||
|
||||
cancel()
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
@@ -234,4 +241,11 @@ class OptionsController {
|
||||
model.exitOnClose = false
|
||||
model.closeDecisionMade = true
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void clearHistory() {
|
||||
uiSettings.searchHistory.clear()
|
||||
saveUISettings()
|
||||
JOptionPane.showMessageDialog(null, "Search history has been cleared")
|
||||
}
|
||||
}
|
@@ -117,6 +117,7 @@ class Initialize extends AbstractLifecycleHandler {
|
||||
|
||||
def guiPropsFile = new File(home, "gui.properties")
|
||||
UISettings uiSettings
|
||||
int rowHeight = 15
|
||||
if (guiPropsFile.exists()) {
|
||||
Properties props = new Properties()
|
||||
guiPropsFile.withInputStream { props.load(it) }
|
||||
@@ -149,7 +150,7 @@ class Initialize extends AbstractLifecycleHandler {
|
||||
} else {
|
||||
fontSize = uiSettings.fontSize
|
||||
}
|
||||
|
||||
rowHeight = fontSize + 3
|
||||
FontUIResource font = new FontUIResource(fontName, Font.PLAIN, fontSize)
|
||||
|
||||
def keys = lnf.getDefaults().keys()
|
||||
@@ -183,6 +184,7 @@ class Initialize extends AbstractLifecycleHandler {
|
||||
}
|
||||
}
|
||||
|
||||
application.context.put("row-height", rowHeight)
|
||||
application.context.put("ui-settings", uiSettings)
|
||||
}
|
||||
|
||||
|
@@ -41,9 +41,9 @@ class Ready extends AbstractLifecycleHandler {
|
||||
def propsFile = new File(home, "MuWire.properties")
|
||||
if (propsFile.exists()) {
|
||||
log.info("loading existing props file")
|
||||
propsFile.withInputStream {
|
||||
propsFile.withReader("UTF-8", {
|
||||
props.load(it)
|
||||
}
|
||||
})
|
||||
props = new MuWireSettings(props)
|
||||
if (props.incompleteLocation == null)
|
||||
props.incompleteLocation = new File(home, "incompletes")
|
||||
@@ -90,9 +90,9 @@ class Ready extends AbstractLifecycleHandler {
|
||||
props.downloadLocation = chooser.getSelectedFile()
|
||||
}
|
||||
|
||||
propsFile.withOutputStream {
|
||||
propsFile.withPrintWriter("UTF-8", {
|
||||
props.write(it)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Core core
|
||||
|
@@ -0,0 +1,39 @@
|
||||
package com.muwire.gui
|
||||
|
||||
import javax.swing.tree.DefaultMutableTreeNode
|
||||
import javax.swing.tree.DefaultTreeModel
|
||||
import javax.swing.tree.MutableTreeNode
|
||||
|
||||
import com.muwire.core.Core
|
||||
import com.muwire.core.files.FileTree
|
||||
|
||||
import griffon.core.artifact.GriffonModel
|
||||
import griffon.transform.Observable
|
||||
import griffon.metadata.ArtifactProviderFor
|
||||
|
||||
@ArtifactProviderFor(GriffonModel)
|
||||
class AdvancedSharingModel {
|
||||
def watchedDirectories = []
|
||||
def treeRoot
|
||||
def negativeTree
|
||||
|
||||
Core core
|
||||
|
||||
void mvcGroupInit(Map<String,String> args) {
|
||||
watchedDirectories.addAll(core.muOptions.watchedDirectories)
|
||||
|
||||
treeRoot = new DefaultMutableTreeNode()
|
||||
negativeTree = new DefaultTreeModel(treeRoot)
|
||||
copyTree(treeRoot, core.fileManager.negativeTree.root)
|
||||
}
|
||||
|
||||
private void copyTree(DefaultMutableTreeNode jtreeNode, FileTree.TreeNode fileTreeNode) {
|
||||
jtreeNode.setUserObject(fileTreeNode.file?.getName())
|
||||
fileTreeNode.children.each {
|
||||
MutableTreeNode newChild = new DefaultMutableTreeNode()
|
||||
jtreeNode.add(newChild)
|
||||
copyTree(newChild, it)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -88,6 +88,8 @@ class MainFrameModel {
|
||||
def trusted = []
|
||||
def distrusted = []
|
||||
def subscriptions = []
|
||||
|
||||
boolean sessionRestored
|
||||
|
||||
@Observable int connections
|
||||
@Observable String me
|
||||
@@ -255,7 +257,7 @@ class MainFrameModel {
|
||||
|
||||
void onAllFilesLoadedEvent(AllFilesLoadedEvent e) {
|
||||
runInsideUIAsync {
|
||||
core.muOptions.watchedDirectories.each { core.eventBus.publish(new DirectoryWatchedEvent(directory : new File(it))) }
|
||||
core.muOptions.watchedDirectories.each { core.eventBus.publish(new FileSharedEvent(file : new File(it))) }
|
||||
|
||||
core.muOptions.trustSubscriptions.each {
|
||||
core.eventBus.publish(new TrustSubscriptionEvent(persona : it, subscribe : true))
|
||||
@@ -295,6 +297,8 @@ class MainFrameModel {
|
||||
runInsideUIAsync {
|
||||
connections = core.connectionManager.getConnections().size()
|
||||
|
||||
view.showRestoreOrEmpty()
|
||||
|
||||
if (connections > 0) {
|
||||
def topPanel = builder.getVariable("top-panel")
|
||||
topPanel.getLayout().show(topPanel, "top-search-panel")
|
||||
|
@@ -21,6 +21,7 @@ class OptionsModel {
|
||||
@Observable int speedSmoothSeconds
|
||||
@Observable int totalUploadSlots
|
||||
@Observable int uploadSlotsPerUser
|
||||
@Observable boolean storeSearchHistory
|
||||
|
||||
// i2p options
|
||||
@Observable String inboundLength
|
||||
@@ -90,6 +91,7 @@ class OptionsModel {
|
||||
showSearchHashes = uiSettings.showSearchHashes
|
||||
clearUploads = uiSettings.clearUploads
|
||||
exitOnClose = uiSettings.exitOnClose
|
||||
storeSearchHistory = uiSettings.storeSearchHistory
|
||||
|
||||
if (core.router != null) {
|
||||
inBw = String.valueOf(settings.inBw)
|
||||
|
@@ -0,0 +1,82 @@
|
||||
package com.muwire.gui
|
||||
|
||||
import griffon.core.artifact.GriffonView
|
||||
import griffon.inject.MVCMember
|
||||
import griffon.metadata.ArtifactProviderFor
|
||||
|
||||
import javax.swing.JDialog
|
||||
import javax.swing.JTabbedPane
|
||||
import javax.swing.JTree
|
||||
import javax.swing.SwingConstants
|
||||
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.event.WindowAdapter
|
||||
import java.awt.event.WindowEvent
|
||||
|
||||
import javax.annotation.Nonnull
|
||||
|
||||
@ArtifactProviderFor(GriffonView)
|
||||
class AdvancedSharingView {
|
||||
@MVCMember @Nonnull
|
||||
FactoryBuilderSupport builder
|
||||
@MVCMember @Nonnull
|
||||
AdvancedSharingModel model
|
||||
|
||||
def mainFrame
|
||||
def dialog
|
||||
def watchedDirsPanel
|
||||
def negativeTreePanel
|
||||
|
||||
def watchedDirsTable
|
||||
|
||||
void initUI() {
|
||||
mainFrame = application.windowManager.findWindow("main-frame")
|
||||
int rowHeight = application.context.get("row-height")
|
||||
dialog = new JDialog(mainFrame,"Advanced Sharing",true)
|
||||
dialog.setResizable(true)
|
||||
|
||||
watchedDirsPanel = builder.panel {
|
||||
borderLayout()
|
||||
panel (constraints : BorderLayout.NORTH) {
|
||||
label(text : "Directories watched for file changes")
|
||||
}
|
||||
scrollPane( constraints : BorderLayout.CENTER ) {
|
||||
watchedDirsTable = table(autoCreateRowSorter : true, rowHeight : rowHeight) {
|
||||
tableModel(list : model.watchedDirectories) {
|
||||
closureColumn(header : "Directory", type : String, read : {it})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
negativeTreePanel = builder.panel {
|
||||
borderLayout()
|
||||
panel(constraints : BorderLayout.NORTH) {
|
||||
label(text : "Files which are explicitly not shared")
|
||||
}
|
||||
scrollPane( constraints : BorderLayout.CENTER ) {
|
||||
def jtree = new JTree(model.negativeTree)
|
||||
tree(rootVisible : false, rowHeight : rowHeight,jtree)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mvcGroupInit(Map<String,String> args) {
|
||||
def tabbedPane = new JTabbedPane()
|
||||
tabbedPane.addTab("Watched Directories", watchedDirsPanel)
|
||||
tabbedPane.addTab("Negative Tree", negativeTreePanel)
|
||||
|
||||
dialog.with {
|
||||
getContentPane().add(tabbedPane)
|
||||
pack()
|
||||
setLocationRelativeTo(mainFrame)
|
||||
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
|
||||
addWindowListener(new WindowAdapter() {
|
||||
public void windowClosed(WindowEvent e) {
|
||||
mvcGroup.destroy()
|
||||
}
|
||||
})
|
||||
show()
|
||||
}
|
||||
}
|
||||
}
|
@@ -38,6 +38,7 @@ class ContentPanelView {
|
||||
|
||||
void initUI() {
|
||||
mainFrame = application.windowManager.findWindow("main-frame")
|
||||
int rowHeight = application.context.get("row-height")
|
||||
dialog = new JDialog(mainFrame, "Content Control Panel", true)
|
||||
|
||||
mainPanel = builder.panel {
|
||||
@@ -48,7 +49,7 @@ class ContentPanelView {
|
||||
label(text : "Rules")
|
||||
}
|
||||
scrollPane (constraints : BorderLayout.CENTER) {
|
||||
rulesTable = table(id : "rules-table", autoCreateRowSorter : true) {
|
||||
rulesTable = table(id : "rules-table", autoCreateRowSorter : true, rowHeight : rowHeight) {
|
||||
tableModel(list : model.rules) {
|
||||
closureColumn(header: "Term", type:String, read: {row -> row.getTerm()})
|
||||
closureColumn(header: "Regex?", type:Boolean, read: {row -> row instanceof RegexMatcher})
|
||||
@@ -74,7 +75,7 @@ class ContentPanelView {
|
||||
label(text : "Hits")
|
||||
}
|
||||
scrollPane(constraints : BorderLayout.CENTER) {
|
||||
hitsTable = table(id : "hits-table", autoCreateRowSorter : true) {
|
||||
hitsTable = table(id : "hits-table", autoCreateRowSorter : true, rowHeight : rowHeight) {
|
||||
tableModel(list : model.hits) {
|
||||
closureColumn(header : "Searcher", type : String, read : {row -> row.persona.getHumanReadableName()})
|
||||
closureColumn(header : "Keywords", type : String, read : {row -> row.keywords.join(" ")})
|
||||
|
@@ -11,6 +11,7 @@ import net.i2p.data.DataHelper
|
||||
import javax.swing.BorderFactory
|
||||
import javax.swing.Box
|
||||
import javax.swing.BoxLayout
|
||||
import javax.swing.JComboBox
|
||||
import javax.swing.JFileChooser
|
||||
import javax.swing.JFrame
|
||||
import javax.swing.JLabel
|
||||
@@ -24,6 +25,10 @@ import javax.swing.SwingConstants
|
||||
import javax.swing.SwingUtilities
|
||||
import javax.swing.TransferHandler
|
||||
import javax.swing.border.Border
|
||||
import javax.swing.event.DocumentEvent
|
||||
import javax.swing.event.DocumentListener
|
||||
import javax.swing.event.TreeExpansionEvent
|
||||
import javax.swing.event.TreeExpansionListener
|
||||
import javax.swing.table.DefaultTableCellRenderer
|
||||
import javax.swing.tree.TreeNode
|
||||
import javax.swing.tree.TreePath
|
||||
@@ -67,11 +72,14 @@ class MainFrameView {
|
||||
def lastDownloadSortEvent
|
||||
def lastSharedSortEvent
|
||||
def trustTablesSortEvents = [:]
|
||||
def expansionListener = new TreeExpansions()
|
||||
|
||||
|
||||
UISettings settings
|
||||
|
||||
void initUI() {
|
||||
settings = application.context.get("ui-settings")
|
||||
int rowHeight = application.context.get("row-height")
|
||||
builder.with {
|
||||
application(size : [1024,768], id: 'main-frame',
|
||||
locationRelativeTo : null,
|
||||
@@ -89,11 +97,12 @@ class MainFrameView {
|
||||
menuItem("Exit", actionPerformed : {closeApplication()})
|
||||
}
|
||||
menu (text : "Options") {
|
||||
menuItem("Configuration", actionPerformed : {mvcGroup.createMVCGroup("Options")})
|
||||
menuItem("Content Control", actionPerformed : {
|
||||
def env = [:]
|
||||
env["core"] = model.core
|
||||
mvcGroup.createMVCGroup("content-panel", env)
|
||||
menuItem("Configuration", actionPerformed : {
|
||||
def params = [:]
|
||||
params['core'] = application.context.get("core")
|
||||
params['settings'] = params['core'].muOptions
|
||||
params['uiSettings'] = settings
|
||||
mvcGroup.createMVCGroup("Options", params)
|
||||
})
|
||||
}
|
||||
menu (text : "Status") {
|
||||
@@ -102,6 +111,18 @@ class MainFrameView {
|
||||
menuItem("I2P", enabled : bind {model.routerPresent}, actionPerformed: {mvcGroup.createMVCGroup("i-2-p-status")})
|
||||
menuItem("System", actionPerformed : {mvcGroup.createMVCGroup("system-status")})
|
||||
}
|
||||
menu (text : "Tools") {
|
||||
menuItem("Content Control", actionPerformed : {
|
||||
def env = [:]
|
||||
env["core"] = model.core
|
||||
mvcGroup.createMVCGroup("content-panel", env)
|
||||
})
|
||||
menuItem("Advanced Sharing", actionPerformed : {
|
||||
def env = [:]
|
||||
env["core"] = model.core
|
||||
mvcGroup.createMVCGroup("advanced-sharing",env)
|
||||
})
|
||||
}
|
||||
}
|
||||
borderLayout()
|
||||
panel (border: etchedBorder(), constraints : BorderLayout.NORTH) {
|
||||
@@ -124,20 +145,39 @@ class MainFrameView {
|
||||
panel(constraints: BorderLayout.CENTER) {
|
||||
borderLayout()
|
||||
label(" Enter search here:", constraints: BorderLayout.WEST) // TODO: fix this
|
||||
textField(id: "search-field", constraints: BorderLayout.CENTER, action : searchAction)
|
||||
|
||||
def searchFieldModel = new SearchFieldModel(settings, new File(application.context.get("muwire-home")))
|
||||
JComboBox myComboBox = new SearchField(searchFieldModel)
|
||||
myComboBox.setAction(searchAction)
|
||||
widget(id: "search-field", constraints: BorderLayout.CENTER, myComboBox)
|
||||
|
||||
}
|
||||
panel( constraints: BorderLayout.EAST) {
|
||||
button(text: "Search", searchAction)
|
||||
button(text : "", icon : imageIcon("/close_tab.png"), clearSearchAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
panel (id: "cards-panel", constraints : BorderLayout.CENTER) {
|
||||
cardLayout()
|
||||
panel (constraints : "search window") {
|
||||
borderLayout()
|
||||
tabbedPane(id : "result-tabs", constraints: BorderLayout.CENTER)
|
||||
panel (id : "search window", constraints : "search window") {
|
||||
cardLayout()
|
||||
panel (constraints : "tabs-panel") {
|
||||
borderLayout()
|
||||
tabbedPane(id : "result-tabs", constraints: BorderLayout.CENTER)
|
||||
}
|
||||
panel(constraints : "restore session") {
|
||||
borderLayout()
|
||||
panel (constraints : BorderLayout.CENTER) {
|
||||
gridBagLayout()
|
||||
label(text : "Saved Tabs:", constraints : gbc(gridx : 0, gridy : 0))
|
||||
scrollPane (constraints : gbc(gridx : 0, gridy : 1)) {
|
||||
list(items : new ArrayList(settings.openTabs))
|
||||
}
|
||||
button(text : "Restore Session", constraints : gbc(gridx :0, gridy : 2), restoreSessionAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
panel (constraints: "downloads window") {
|
||||
gridLayout(rows : 1, cols : 1)
|
||||
@@ -145,7 +185,7 @@ class MainFrameView {
|
||||
panel {
|
||||
borderLayout()
|
||||
scrollPane (constraints : BorderLayout.CENTER) {
|
||||
downloadsTable = table(id : "downloads-table", autoCreateRowSorter : true) {
|
||||
downloadsTable = table(id : "downloads-table", autoCreateRowSorter : true, rowHeight : rowHeight) {
|
||||
tableModel(list: model.downloads) {
|
||||
closureColumn(header: "Name", preferredWidth: 300, type: String, read : {row -> row.downloader.file.getName()})
|
||||
closureColumn(header: "Status", preferredWidth: 50, type: String, read : {row -> row.downloader.getCurrentState().toString()})
|
||||
@@ -224,7 +264,7 @@ class MainFrameView {
|
||||
panel (constraints : "shared files table") {
|
||||
borderLayout()
|
||||
scrollPane(constraints : BorderLayout.CENTER) {
|
||||
table(id : "shared-files-table", autoCreateRowSorter: true) {
|
||||
table(id : "shared-files-table", autoCreateRowSorter: true, rowHeight : rowHeight) {
|
||||
tableModel(list : model.shared) {
|
||||
closureColumn(header : "Name", preferredWidth : 500, type : String, read : {row -> row.getCachedPath()})
|
||||
closureColumn(header : "Size", preferredWidth : 50, type : Long, read : {row -> row.getCachedLength() })
|
||||
@@ -240,7 +280,7 @@ class MainFrameView {
|
||||
scrollPane(constraints : BorderLayout.CENTER) {
|
||||
def jtree = new JTree(model.sharedTree)
|
||||
jtree.setCellRenderer(new SharedTreeRenderer())
|
||||
tree(id : "shared-files-tree", rootVisible : false, expandsSelectedPaths: true, jtree)
|
||||
tree(id : "shared-files-tree", rowHeight : rowHeight, rootVisible : false, expandsSelectedPaths: true, jtree)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -270,7 +310,7 @@ class MainFrameView {
|
||||
label("Uploads")
|
||||
}
|
||||
scrollPane (constraints : BorderLayout.CENTER) {
|
||||
table(id : "uploads-table") {
|
||||
table(id : "uploads-table", rowHeight : rowHeight) {
|
||||
tableModel(list : model.uploads) {
|
||||
closureColumn(header : "Name", type : String, read : {row -> row.uploader.getName() })
|
||||
closureColumn(header : "Progress", type : String, read : { row ->
|
||||
@@ -316,7 +356,7 @@ class MainFrameView {
|
||||
label("Connections")
|
||||
}
|
||||
scrollPane(constraints : BorderLayout.CENTER) {
|
||||
table(id : "connections-table") {
|
||||
table(id : "connections-table", rowHeight : rowHeight) {
|
||||
tableModel(list : model.connectionList) {
|
||||
closureColumn(header : "Destination", preferredWidth: 250, type: String, read : { row -> row.destination.toBase32() })
|
||||
closureColumn(header : "Direction", preferredWidth: 20, type: String, read : { row ->
|
||||
@@ -335,7 +375,7 @@ class MainFrameView {
|
||||
label("Incoming searches")
|
||||
}
|
||||
scrollPane(constraints : BorderLayout.CENTER) {
|
||||
table(id : "searches-table") {
|
||||
table(id : "searches-table", rowHeight : rowHeight) {
|
||||
tableModel(list : model.searches) {
|
||||
closureColumn(header : "Keywords", type : String, read : {
|
||||
sanitized = it.search.replace('<', ' ')
|
||||
@@ -368,7 +408,7 @@ class MainFrameView {
|
||||
panel (border : etchedBorder()){
|
||||
borderLayout()
|
||||
scrollPane(constraints : BorderLayout.CENTER) {
|
||||
table(id : "trusted-table", autoCreateRowSorter : true) {
|
||||
table(id : "trusted-table", autoCreateRowSorter : true, rowHeight : rowHeight) {
|
||||
tableModel(list : model.trusted) {
|
||||
closureColumn(header : "Trusted Users", type : String, read : { it.getHumanReadableName() } )
|
||||
}
|
||||
@@ -384,7 +424,7 @@ class MainFrameView {
|
||||
panel (border : etchedBorder()){
|
||||
borderLayout()
|
||||
scrollPane(constraints : BorderLayout.CENTER) {
|
||||
table(id : "distrusted-table", autoCreateRowSorter : true) {
|
||||
table(id : "distrusted-table", autoCreateRowSorter : true, rowHeight : rowHeight) {
|
||||
tableModel(list : model.distrusted) {
|
||||
closureColumn(header: "Distrusted Users", type : String, read : { it.getHumanReadableName() } )
|
||||
}
|
||||
@@ -403,7 +443,7 @@ class MainFrameView {
|
||||
label(text : "Trust List Subscriptions")
|
||||
}
|
||||
scrollPane(constraints : BorderLayout.CENTER) {
|
||||
table(id : "subscription-table", autoCreateRowSorter : true) {
|
||||
table(id : "subscription-table", autoCreateRowSorter : true, rowHeight : rowHeight) {
|
||||
tableModel(list : model.subscriptions) {
|
||||
closureColumn(header : "Name", preferredWidth: 200, type: String, read : {it.persona.getHumanReadableName()})
|
||||
closureColumn(header : "Trusted", preferredWidth : 20, type: Integer, read : {it.good.size()})
|
||||
@@ -473,6 +513,10 @@ class MainFrameView {
|
||||
}
|
||||
}})
|
||||
|
||||
// search field
|
||||
def searchField = builder.getVariable("search-field")
|
||||
|
||||
// downloads table
|
||||
def downloadsTable = builder.getVariable("downloads-table")
|
||||
def selectionModel = downloadsTable.getSelectionModel()
|
||||
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
|
||||
@@ -590,6 +634,8 @@ class MainFrameView {
|
||||
model.addCommentButtonEnabled = selectedNode != null
|
||||
|
||||
})
|
||||
|
||||
sharedFilesTree.addTreeExpansionListener(expansionListener)
|
||||
|
||||
// searches table
|
||||
def searchesTable = builder.getVariable("searches-table")
|
||||
@@ -695,6 +741,9 @@ class MainFrameView {
|
||||
|
||||
// show tree by default
|
||||
showSharedFilesTree.call()
|
||||
|
||||
// show search panel by default
|
||||
showSearchWindow.call()
|
||||
}
|
||||
|
||||
private static void showPopupMenu(JPopupMenu menu, MouseEvent event) {
|
||||
@@ -839,10 +888,23 @@ class MainFrameView {
|
||||
|
||||
showPopupMenu(menu, e)
|
||||
}
|
||||
|
||||
void showRestoreOrEmpty() {
|
||||
def searchWindow = builder.getVariable("search window")
|
||||
String id
|
||||
if (!model.sessionRestored && !settings.openTabs.isEmpty())
|
||||
id = model.connections > 0 ? "restore session" : "tabs-panel"
|
||||
else
|
||||
id = "tabs-panel"
|
||||
searchWindow.getLayout().show(searchWindow, id)
|
||||
}
|
||||
|
||||
def showSearchWindow = {
|
||||
def cardsPanel = builder.getVariable("cards-panel")
|
||||
cardsPanel.getLayout().show(cardsPanel, "search window")
|
||||
|
||||
showRestoreOrEmpty()
|
||||
|
||||
model.searchesPaneButtonEnabled = false
|
||||
model.downloadsPaneButtonEnabled = true
|
||||
model.uploadsPaneButtonEnabled = true
|
||||
@@ -930,16 +992,31 @@ class MainFrameView {
|
||||
public void refreshSharedFiles() {
|
||||
def tree = builder.getVariable("shared-files-tree")
|
||||
TreePath[] selectedPaths = tree.getSelectionPaths()
|
||||
Set<TreePath> expanded = new HashSet<>(expansionListener.expandedPaths)
|
||||
|
||||
model.sharedTree.nodeStructureChanged(model.treeRoot)
|
||||
|
||||
expanded.each { tree.expandPath(it) }
|
||||
tree.setSelectionPaths(selectedPaths)
|
||||
|
||||
builder.getVariable("shared-files-table").model.fireTableDataChanged()
|
||||
}
|
||||
|
||||
private void closeApplication() {
|
||||
Core core = application.getContext().get("core")
|
||||
|
||||
def tabbedPane = builder.getVariable("result-tabs")
|
||||
settings.openTabs.clear()
|
||||
int count = tabbedPane.getTabCount()
|
||||
for (int i = 0; i < count; i++)
|
||||
settings.openTabs.add(tabbedPane.getTitleAt(i))
|
||||
|
||||
File uiPropsFile = new File(core.home, "gui.properties")
|
||||
uiPropsFile.withOutputStream { settings.write(it) }
|
||||
|
||||
def mainFrame = builder.getVariable("main-frame")
|
||||
mainFrame.setVisible(false)
|
||||
application.getWindowManager().findWindow("shutdown-window").setVisible(true)
|
||||
Core core = application.getContext().get("core")
|
||||
if (core != null) {
|
||||
Thread t = new Thread({
|
||||
core.shutdown()
|
||||
@@ -948,4 +1025,19 @@ class MainFrameView {
|
||||
t.start()
|
||||
}
|
||||
}
|
||||
|
||||
private static class TreeExpansions implements TreeExpansionListener {
|
||||
private final Set<TreePath> expandedPaths = new HashSet<>()
|
||||
|
||||
|
||||
@Override
|
||||
public void treeExpanded(TreeExpansionEvent event) {
|
||||
expandedPaths.add(event.getPath())
|
||||
}
|
||||
|
||||
@Override
|
||||
public void treeCollapsed(TreeExpansionEvent event) {
|
||||
expandedPaths.remove(event.getPath())
|
||||
}
|
||||
}
|
||||
}
|
@@ -61,6 +61,7 @@ class OptionsView {
|
||||
def excludeLocalResultCheckbox
|
||||
def showSearchHashesCheckbox
|
||||
def clearUploadsCheckbox
|
||||
def storeSearchHistoryCheckbox
|
||||
|
||||
def inBwField
|
||||
def outBwField
|
||||
@@ -196,8 +197,17 @@ class OptionsView {
|
||||
constraints : gbc(gridx : 3, gridy : 2, anchor : GridBagConstraints.LINE_END))
|
||||
|
||||
}
|
||||
panel (border : titledBorder(title : "Other Settings", border : etchedBorder(), titlePosition : TitledBorder.TOP),
|
||||
panel (border : titledBorder(title : "Search Settings", border : etchedBorder(), titlePosition : TitledBorder.TOP),
|
||||
constraints : gbc(gridx : 0, gridy : 1, fill : GridBagConstraints.HORIZONTAL, weightx : 100)) {
|
||||
gridBagLayout()
|
||||
label(text : "Remember search history", constraints: gbc(gridx: 0, gridy:0, anchor : GridBagConstraints.LINE_START, weightx: 100))
|
||||
storeSearchHistoryCheckbox = checkBox(selected : bind {model.storeSearchHistory},
|
||||
constraints : gbc(gridx : 1, gridy:0, anchor : GridBagConstraints.LINE_END))
|
||||
button(text : "Clear history", constraints : gbc(gridx : 1, gridy : 1, anchor : GridBagConstraints.LINE_END), clearHistoryAction)
|
||||
|
||||
}
|
||||
panel (border : titledBorder(title : "Other Settings", border : etchedBorder(), titlePosition : TitledBorder.TOP),
|
||||
constraints : gbc(gridx : 0, gridy : 2, fill : GridBagConstraints.HORIZONTAL, weightx : 100)) {
|
||||
gridBagLayout()
|
||||
label(text : "Automatically clear cancelled downloads", constraints: gbc(gridx: 0, gridy:0, anchor : GridBagConstraints.LINE_START, weightx: 100))
|
||||
clearCancelledDownloadsCheckbox = checkBox(selected : bind {model.clearCancelledDownloads},
|
||||
@@ -221,7 +231,7 @@ class OptionsView {
|
||||
radioButton(text : "Exit", selected : bind {model.exitOnClose}, buttonGroup : closeBehaviorGroup, exitOnCloseAction)
|
||||
}
|
||||
}
|
||||
panel (constraints : gbc(gridx: 0, gridy: 2, weighty: 100))
|
||||
panel (constraints : gbc(gridx: 0, gridy: 3, weighty: 100))
|
||||
}
|
||||
bandwidth = builder.panel {
|
||||
gridBagLayout()
|
||||
|
@@ -49,6 +49,7 @@ class SearchTabView {
|
||||
def sequentialDownloadCheckbox
|
||||
|
||||
void initUI() {
|
||||
int rowHeight = application.context.get("row-height")
|
||||
builder.with {
|
||||
def resultsTable
|
||||
def sendersTable
|
||||
@@ -59,7 +60,7 @@ class SearchTabView {
|
||||
panel {
|
||||
borderLayout()
|
||||
scrollPane (constraints : BorderLayout.CENTER) {
|
||||
sendersTable = table(id : "senders-table", autoCreateRowSorter : true) {
|
||||
sendersTable = table(id : "senders-table", autoCreateRowSorter : true, rowHeight : rowHeight) {
|
||||
tableModel(list : model.senders) {
|
||||
closureColumn(header : "Sender", preferredWidth : 500, type: String, read : {row -> row.getHumanReadableName()})
|
||||
closureColumn(header : "Results", preferredWidth : 20, type: Integer, read : {row -> model.sendersBucket[row].size()})
|
||||
@@ -85,7 +86,7 @@ class SearchTabView {
|
||||
panel {
|
||||
borderLayout()
|
||||
scrollPane (constraints : BorderLayout.CENTER) {
|
||||
resultsTable = table(id : "results-table", autoCreateRowSorter : true) {
|
||||
resultsTable = table(id : "results-table", autoCreateRowSorter : true, rowHeight : rowHeight) {
|
||||
tableModel(list: model.results) {
|
||||
closureColumn(header: "Name", preferredWidth: 350, type: String, read : {row -> row.name.replace('<','_')})
|
||||
closureColumn(header: "Size", preferredWidth: 20, type: Long, read : {row -> row.size})
|
||||
|
@@ -29,6 +29,7 @@ class TrustListView {
|
||||
|
||||
void initUI() {
|
||||
mainFrame = application.windowManager.findWindow("main-frame")
|
||||
int rowHeight = application.context.get("row-height")
|
||||
dialog = new JDialog(mainFrame, model.trustList.persona.getHumanReadableName(), true)
|
||||
mainPanel = builder.panel {
|
||||
borderLayout()
|
||||
@@ -46,7 +47,7 @@ class TrustListView {
|
||||
panel {
|
||||
borderLayout()
|
||||
scrollPane (constraints : BorderLayout.CENTER){
|
||||
table(id : "trusted-table", autoCreateRowSorter : true) {
|
||||
table(id : "trusted-table", autoCreateRowSorter : true, rowHeight : rowHeight) {
|
||||
tableModel(list : model.trusted) {
|
||||
closureColumn(header: "Trusted Users", type : String, read : {it.getHumanReadableName()})
|
||||
closureColumn(header: "Your Trust", type : String, read : {model.trustService.getLevel(it.destination).toString()})
|
||||
@@ -62,7 +63,7 @@ class TrustListView {
|
||||
panel {
|
||||
borderLayout()
|
||||
scrollPane (constraints : BorderLayout.CENTER ){
|
||||
table(id : "distrusted-table", autoCreateRowSorter : true) {
|
||||
table(id : "distrusted-table", autoCreateRowSorter : true, rowHeight : rowHeight) {
|
||||
tableModel(list : model.distrusted) {
|
||||
closureColumn(header: "Distrusted Users", type : String, read : {it.getHumanReadableName()})
|
||||
closureColumn(header: "Your Trust", type : String, read : {model.trustService.getLevel(it.destination).toString()})
|
||||
|
14
gui/src/main/groovy/com/muwire/gui/SearchField.groovy
Normal file
14
gui/src/main/groovy/com/muwire/gui/SearchField.groovy
Normal file
@@ -0,0 +1,14 @@
|
||||
package com.muwire.gui
|
||||
|
||||
import java.awt.event.KeyEvent
|
||||
|
||||
import javax.swing.JComboBox
|
||||
|
||||
class SearchField extends JComboBox {
|
||||
SearchField(SearchFieldModel model) {
|
||||
super()
|
||||
setEditable(true)
|
||||
setModel(model)
|
||||
setEditor(new SearchFieldEditor(model, this))
|
||||
}
|
||||
}
|
47
gui/src/main/groovy/com/muwire/gui/SearchFieldEditor.groovy
Normal file
47
gui/src/main/groovy/com/muwire/gui/SearchFieldEditor.groovy
Normal file
@@ -0,0 +1,47 @@
|
||||
package com.muwire.gui
|
||||
|
||||
import javax.swing.JTextField
|
||||
import javax.swing.SwingUtilities
|
||||
import javax.swing.event.DocumentEvent
|
||||
import javax.swing.event.DocumentListener
|
||||
import javax.swing.plaf.basic.BasicComboBoxEditor
|
||||
|
||||
class SearchFieldEditor extends BasicComboBoxEditor {
|
||||
|
||||
private final SearchFieldModel model
|
||||
private final SearchField field
|
||||
|
||||
SearchFieldEditor(SearchFieldModel model, SearchField field) {
|
||||
super()
|
||||
this.model = model
|
||||
this.field = field
|
||||
def action = field.getAction()
|
||||
field.setAction(null)
|
||||
editor.setAction(action)
|
||||
editor.getDocument().addDocumentListener(new DocumentListener() {
|
||||
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
SwingUtilities.invokeLater({
|
||||
field.hidePopup()
|
||||
if (model.onKeyStroke(editor.text))
|
||||
field.showPopup()
|
||||
})
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
SwingUtilities.invokeLater({
|
||||
field.hidePopup()
|
||||
if (model.onKeyStroke(editor.text))
|
||||
field.showPopup()
|
||||
})
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
100
gui/src/main/groovy/com/muwire/gui/SearchFieldModel.groovy
Normal file
100
gui/src/main/groovy/com/muwire/gui/SearchFieldModel.groovy
Normal file
@@ -0,0 +1,100 @@
|
||||
package com.muwire.gui
|
||||
|
||||
import javax.swing.AbstractListModel
|
||||
import javax.swing.MutableComboBoxModel
|
||||
|
||||
class SearchFieldModel extends AbstractListModel implements MutableComboBoxModel {
|
||||
private final UISettings uiSettings
|
||||
private final File settingsFile
|
||||
private final List<String> objects = new ArrayList<>()
|
||||
private String selectedObject
|
||||
|
||||
SearchFieldModel(UISettings uiSettings, File home) {
|
||||
super()
|
||||
this.uiSettings = uiSettings
|
||||
this.settingsFile = new File(home, "gui.properties")
|
||||
uiSettings.searchHistory.each { objects.add(it) }
|
||||
fireIntervalAdded(this, 0, objects.size() - 1)
|
||||
}
|
||||
|
||||
public void addElement(Object string) {
|
||||
if (string == null)
|
||||
return
|
||||
if (uiSettings.storeSearchHistory) {
|
||||
if (!uiSettings.searchHistory.add(string))
|
||||
return
|
||||
settingsFile.withOutputStream { uiSettings.write(it) }
|
||||
}
|
||||
objects.add(string);
|
||||
fireIntervalAdded(this,objects.size()-1, objects.size()-1);
|
||||
if ( objects.size() == 1 && selectedObject == null && string != null ) {
|
||||
setSelectedItem( string );
|
||||
}
|
||||
}
|
||||
|
||||
boolean onKeyStroke(String selected) {
|
||||
selectedObject = selected
|
||||
if (selected == null || selected.length() == 0) {
|
||||
objects.clear()
|
||||
uiSettings.searchHistory.each { objects.add(it) }
|
||||
return true
|
||||
}
|
||||
|
||||
objects.clear()
|
||||
|
||||
Set<String> matching = new HashSet<>(uiSettings.searchHistory)
|
||||
matching.retainAll { it.contains(selected) }
|
||||
|
||||
matching.each {
|
||||
objects.add(it)
|
||||
}
|
||||
Collections.sort(objects)
|
||||
if (!objects.isEmpty()) {
|
||||
fireIntervalAdded(this, 0, objects.size() - 1)
|
||||
return true
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelectedItem(Object anObject) {
|
||||
if ((selectedObject != null && !selectedObject.equals( anObject )) ||
|
||||
selectedObject == null && anObject != null) {
|
||||
selectedObject = anObject;
|
||||
fireContentsChanged(this, -1, -1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getSelectedItem() {
|
||||
selectedObject
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
objects.size()
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getElementAt(int index) {
|
||||
if ( index >= 0 && index < objects.size() )
|
||||
return objects.get(index);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeElement(Object obj) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertElementAt(Object item, int index) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeElementAt(int index) {
|
||||
|
||||
}
|
||||
}
|
@@ -1,5 +1,7 @@
|
||||
package com.muwire.gui
|
||||
|
||||
import com.muwire.core.util.DataUtil
|
||||
|
||||
class UISettings {
|
||||
|
||||
String lnf
|
||||
@@ -14,6 +16,9 @@ class UISettings {
|
||||
boolean closeWarning
|
||||
boolean exitOnClose
|
||||
boolean clearUploads
|
||||
boolean storeSearchHistory
|
||||
Set<String> searchHistory
|
||||
Set<String> openTabs
|
||||
|
||||
UISettings(Properties props) {
|
||||
lnf = props.getProperty("lnf", "system")
|
||||
@@ -28,6 +33,10 @@ class UISettings {
|
||||
closeWarning = Boolean.parseBoolean(props.getProperty("closeWarning","true"))
|
||||
exitOnClose = Boolean.parseBoolean(props.getProperty("exitOnClose","false"))
|
||||
clearUploads = Boolean.parseBoolean(props.getProperty("clearUploads","false"))
|
||||
storeSearchHistory = Boolean.parseBoolean(props.getProperty("storeSearchHistory","true"))
|
||||
|
||||
searchHistory = DataUtil.readEncodedSet(props, "searchHistory")
|
||||
openTabs = DataUtil.readEncodedSet(props, "openTabs")
|
||||
}
|
||||
|
||||
void write(OutputStream out) throws IOException {
|
||||
@@ -43,9 +52,12 @@ class UISettings {
|
||||
props.setProperty("closeWarning", String.valueOf(closeWarning))
|
||||
props.setProperty("exitOnClose", String.valueOf(exitOnClose))
|
||||
props.setProperty("clearUploads", String.valueOf(clearUploads))
|
||||
props.setProperty("storeSearchHistory", String.valueOf(storeSearchHistory))
|
||||
if (font != null)
|
||||
props.setProperty("font", font)
|
||||
|
||||
DataUtil.writeEncodedSet(searchHistory, "searchHistory", props)
|
||||
DataUtil.writeEncodedSet(openTabs, "openTabs", props)
|
||||
|
||||
props.store(out, "UI Properties")
|
||||
}
|
||||
|
30
logging/0_logging.properties
Normal file
30
logging/0_logging.properties
Normal file
@@ -0,0 +1,30 @@
|
||||
############################################################
|
||||
# Default Logging Configuration File
|
||||
#
|
||||
# You can use a different file by specifying a filename
|
||||
# with the java.util.logging.config.file system property.
|
||||
# For example java -Djava.util.logging.config.file=myfile
|
||||
############################################################
|
||||
|
||||
############################################################
|
||||
# Global properties
|
||||
############################################################
|
||||
|
||||
# "handlers" specifies a comma separated list of log Handler
|
||||
# classes. These handlers will be installed during VM startup.
|
||||
# Note that these classes must be on the system classpath.
|
||||
# By default we only configure a ConsoleHandler, which will only
|
||||
# show messages at the INFO and above levels.
|
||||
handlers= java.util.logging.FileHandler
|
||||
|
||||
# To also add the FileHandler, use the following line instead.
|
||||
#handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler
|
||||
|
||||
# Default global logging level.
|
||||
# This specifies which kinds of events are logged across
|
||||
# all loggers. For any given facility this global level
|
||||
# can be overriden by a facility specific level
|
||||
# Note that the ConsoleHandler also has a separate level
|
||||
# setting to limit messages printed to the console.
|
||||
.level= OFF
|
||||
|
Reference in New Issue
Block a user