Compare commits
50 Commits
muwire-0.5
...
muwire-0.5
Author | SHA1 | Date | |
---|---|---|---|
![]() |
54073af933 | ||
![]() |
a32903fc8c | ||
![]() |
e40520be46 | ||
![]() |
97482b949a | ||
![]() |
92ee107312 | ||
![]() |
2e8082af64 | ||
![]() |
8da5a428c9 | ||
![]() |
fd46b3c7d6 | ||
![]() |
eea3b2563b | ||
![]() |
50719f3828 | ||
![]() |
01a45a89a8 | ||
![]() |
66bd249ed3 | ||
![]() |
265cd6ee15 | ||
![]() |
1dc88cb96b | ||
![]() |
3e10d497b1 | ||
![]() |
9a0b3bb9d6 | ||
![]() |
a1fe3c01b9 | ||
![]() |
ab323db62a | ||
![]() |
d954387e41 | ||
![]() |
ea9db21a18 | ||
![]() |
136cf89c9b | ||
![]() |
46de1baf88 | ||
![]() |
13f7b8563c | ||
![]() |
9c15208f3a | ||
![]() |
a9ce9d96b3 | ||
![]() |
4d2a5a8018 | ||
![]() |
8395047386 | ||
![]() |
cb23aa44f0 | ||
![]() |
dbcb8508b8 | ||
![]() |
47d406d93b | ||
![]() |
e06f1805c2 | ||
![]() |
2b04374e23 | ||
![]() |
383addbc37 | ||
![]() |
cc39cd7f8e | ||
![]() |
83665d7524 | ||
![]() |
94340480b4 | ||
![]() |
8850d49c63 | ||
![]() |
f0f9d840f0 | ||
![]() |
7f4cd4f331 | ||
![]() |
e6162503f6 | ||
![]() |
7a5d71dc36 | ||
![]() |
6fa39a5e35 | ||
![]() |
c5ae804f61 | ||
![]() |
d7695b448d | ||
![]() |
946d9c8f32 | ||
![]() |
02441ca1e3 | ||
![]() |
5fa21b2360 | ||
![]() |
d4c08f4fe6 | ||
![]() |
942de287c6 | ||
![]() |
d0299f80c6 |
@@ -2,7 +2,7 @@ subprojects {
|
|||||||
apply plugin: 'groovy'
|
apply plugin: 'groovy'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile 'net.i2p:i2p:0.9.42'
|
compile "net.i2p:i2p:${i2pVersion}"
|
||||||
compile 'org.codehaus.groovy:groovy-all:2.4.15'
|
compile 'org.codehaus.groovy:groovy-all:2.4.15'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -35,7 +35,7 @@ class Cli {
|
|||||||
|
|
||||||
Core core
|
Core core
|
||||||
try {
|
try {
|
||||||
core = new Core(props, home, "0.5.0")
|
core = new Core(props, home, "0.5.2")
|
||||||
} catch (Exception bad) {
|
} catch (Exception bad) {
|
||||||
bad.printStackTrace(System.out)
|
bad.printStackTrace(System.out)
|
||||||
println "Failed to initialize core, exiting"
|
println "Failed to initialize core, exiting"
|
||||||
|
@@ -53,7 +53,7 @@ class CliDownloader {
|
|||||||
|
|
||||||
Core core
|
Core core
|
||||||
try {
|
try {
|
||||||
core = new Core(props, home, "0.5.0")
|
core = new Core(props, home, "0.5.2")
|
||||||
} catch (Exception bad) {
|
} catch (Exception bad) {
|
||||||
bad.printStackTrace(System.out)
|
bad.printStackTrace(System.out)
|
||||||
println "Failed to initialize core, exiting"
|
println "Failed to initialize core, exiting"
|
||||||
|
@@ -2,9 +2,9 @@ apply plugin : 'application'
|
|||||||
mainClassName = 'com.muwire.core.Core'
|
mainClassName = 'com.muwire.core.Core'
|
||||||
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties']
|
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties']
|
||||||
dependencies {
|
dependencies {
|
||||||
compile 'net.i2p:router:0.9.42'
|
compile "net.i2p:router:${i2pVersion}"
|
||||||
compile 'net.i2p.client:mstreaming:0.9.42'
|
compile "net.i2p.client:mstreaming:${i2pVersion}"
|
||||||
compile 'net.i2p.client:streaming:0.9.42'
|
compile "net.i2p.client:streaming:${i2pVersion}"
|
||||||
|
|
||||||
testCompile 'org.junit.jupiter:junit-jupiter-api:5.4.2'
|
testCompile 'org.junit.jupiter:junit-jupiter-api:5.4.2'
|
||||||
testCompile 'junit:junit:4.12'
|
testCompile 'junit:junit:4.12'
|
||||||
|
@@ -28,6 +28,7 @@ import com.muwire.core.files.FileSharedEvent
|
|||||||
import com.muwire.core.files.FileUnsharedEvent
|
import com.muwire.core.files.FileUnsharedEvent
|
||||||
import com.muwire.core.files.HasherService
|
import com.muwire.core.files.HasherService
|
||||||
import com.muwire.core.files.PersisterService
|
import com.muwire.core.files.PersisterService
|
||||||
|
import com.muwire.core.files.UICommentEvent
|
||||||
import com.muwire.core.files.UIPersistFilesEvent
|
import com.muwire.core.files.UIPersistFilesEvent
|
||||||
import com.muwire.core.files.AllFilesLoadedEvent
|
import com.muwire.core.files.AllFilesLoadedEvent
|
||||||
import com.muwire.core.files.DirectoryUnsharedEvent
|
import com.muwire.core.files.DirectoryUnsharedEvent
|
||||||
@@ -36,11 +37,13 @@ import com.muwire.core.hostcache.CacheClient
|
|||||||
import com.muwire.core.hostcache.HostCache
|
import com.muwire.core.hostcache.HostCache
|
||||||
import com.muwire.core.hostcache.HostDiscoveredEvent
|
import com.muwire.core.hostcache.HostDiscoveredEvent
|
||||||
import com.muwire.core.mesh.MeshManager
|
import com.muwire.core.mesh.MeshManager
|
||||||
|
import com.muwire.core.search.BrowseManager
|
||||||
import com.muwire.core.search.QueryEvent
|
import com.muwire.core.search.QueryEvent
|
||||||
import com.muwire.core.search.ResultsEvent
|
import com.muwire.core.search.ResultsEvent
|
||||||
import com.muwire.core.search.ResultsSender
|
import com.muwire.core.search.ResultsSender
|
||||||
import com.muwire.core.search.SearchEvent
|
import com.muwire.core.search.SearchEvent
|
||||||
import com.muwire.core.search.SearchManager
|
import com.muwire.core.search.SearchManager
|
||||||
|
import com.muwire.core.search.UIBrowseEvent
|
||||||
import com.muwire.core.search.UIResultBatchEvent
|
import com.muwire.core.search.UIResultBatchEvent
|
||||||
import com.muwire.core.trust.TrustEvent
|
import com.muwire.core.trust.TrustEvent
|
||||||
import com.muwire.core.trust.TrustService
|
import com.muwire.core.trust.TrustService
|
||||||
@@ -216,6 +219,7 @@ public class Core {
|
|||||||
eventBus.register(FileUnsharedEvent.class, fileManager)
|
eventBus.register(FileUnsharedEvent.class, fileManager)
|
||||||
eventBus.register(SearchEvent.class, fileManager)
|
eventBus.register(SearchEvent.class, fileManager)
|
||||||
eventBus.register(DirectoryUnsharedEvent.class, fileManager)
|
eventBus.register(DirectoryUnsharedEvent.class, fileManager)
|
||||||
|
eventBus.register(UICommentEvent.class, fileManager)
|
||||||
|
|
||||||
log.info("initializing mesh manager")
|
log.info("initializing mesh manager")
|
||||||
MeshManager meshManager = new MeshManager(fileManager, home, props)
|
MeshManager meshManager = new MeshManager(fileManager, home, props)
|
||||||
@@ -253,7 +257,7 @@ public class Core {
|
|||||||
I2PConnector i2pConnector = new I2PConnector(socketManager)
|
I2PConnector i2pConnector = new I2PConnector(socketManager)
|
||||||
|
|
||||||
log.info "initializing results sender"
|
log.info "initializing results sender"
|
||||||
ResultsSender resultsSender = new ResultsSender(eventBus, i2pConnector, me)
|
ResultsSender resultsSender = new ResultsSender(eventBus, i2pConnector, me, props)
|
||||||
|
|
||||||
log.info "initializing search manager"
|
log.info "initializing search manager"
|
||||||
SearchManager searchManager = new SearchManager(eventBus, me, resultsSender)
|
SearchManager searchManager = new SearchManager(eventBus, me, resultsSender)
|
||||||
@@ -279,7 +283,7 @@ public class Core {
|
|||||||
log.info("initializing acceptor")
|
log.info("initializing acceptor")
|
||||||
I2PAcceptor i2pAcceptor = new I2PAcceptor(socketManager)
|
I2PAcceptor i2pAcceptor = new I2PAcceptor(socketManager)
|
||||||
connectionAcceptor = new ConnectionAcceptor(eventBus, connectionManager, props,
|
connectionAcceptor = new ConnectionAcceptor(eventBus, connectionManager, props,
|
||||||
i2pAcceptor, hostCache, trustService, searchManager, uploadManager, connectionEstablisher)
|
i2pAcceptor, hostCache, trustService, searchManager, uploadManager, fileManager, connectionEstablisher)
|
||||||
|
|
||||||
log.info("initializing directory watcher")
|
log.info("initializing directory watcher")
|
||||||
directoryWatcher = new DirectoryWatcher(eventBus, fileManager, home, props)
|
directoryWatcher = new DirectoryWatcher(eventBus, fileManager, home, props)
|
||||||
@@ -288,7 +292,7 @@ public class Core {
|
|||||||
eventBus.register(DirectoryUnsharedEvent.class, directoryWatcher)
|
eventBus.register(DirectoryUnsharedEvent.class, directoryWatcher)
|
||||||
|
|
||||||
log.info("initializing hasher service")
|
log.info("initializing hasher service")
|
||||||
hasherService = new HasherService(new FileHasher(), eventBus, fileManager)
|
hasherService = new HasherService(new FileHasher(), eventBus, fileManager, props)
|
||||||
eventBus.register(FileSharedEvent.class, hasherService)
|
eventBus.register(FileSharedEvent.class, hasherService)
|
||||||
eventBus.register(FileUnsharedEvent.class, hasherService)
|
eventBus.register(FileUnsharedEvent.class, hasherService)
|
||||||
eventBus.register(DirectoryUnsharedEvent.class, hasherService)
|
eventBus.register(DirectoryUnsharedEvent.class, hasherService)
|
||||||
@@ -302,6 +306,11 @@ public class Core {
|
|||||||
contentManager = new ContentManager()
|
contentManager = new ContentManager()
|
||||||
eventBus.register(ContentControlEvent.class, contentManager)
|
eventBus.register(ContentControlEvent.class, contentManager)
|
||||||
eventBus.register(QueryEvent.class, contentManager)
|
eventBus.register(QueryEvent.class, contentManager)
|
||||||
|
|
||||||
|
log.info("initializing browse manager")
|
||||||
|
BrowseManager browseManager = new BrowseManager(i2pConnector, eventBus)
|
||||||
|
eventBus.register(UIBrowseEvent.class, browseManager)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startServices() {
|
public void startServices() {
|
||||||
@@ -366,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()
|
core.startServices()
|
||||||
|
|
||||||
// ... at the end, sleep or execute script
|
// ... at the end, sleep or execute script
|
||||||
|
@@ -22,12 +22,17 @@ class MuWireSettings {
|
|||||||
String updateType
|
String updateType
|
||||||
String nickname
|
String nickname
|
||||||
File downloadLocation
|
File downloadLocation
|
||||||
|
File incompleteLocation
|
||||||
CrawlerResponse crawlerResponse
|
CrawlerResponse crawlerResponse
|
||||||
boolean shareDownloadedFiles
|
boolean shareDownloadedFiles
|
||||||
|
boolean shareHiddenFiles
|
||||||
|
boolean searchComments
|
||||||
|
boolean browseFiles
|
||||||
Set<String> watchedDirectories
|
Set<String> watchedDirectories
|
||||||
float downloadSequentialRatio
|
float downloadSequentialRatio
|
||||||
int hostClearInterval, hostHopelessInterval, hostRejectInterval
|
int hostClearInterval, hostHopelessInterval, hostRejectInterval
|
||||||
int meshExpiration
|
int meshExpiration
|
||||||
|
int speedSmoothSeconds
|
||||||
boolean embeddedRouter
|
boolean embeddedRouter
|
||||||
int inBw, outBw
|
int inBw, outBw
|
||||||
Set<String> watchedKeywords
|
Set<String> watchedKeywords
|
||||||
@@ -47,11 +52,15 @@ class MuWireSettings {
|
|||||||
nickname = props.getProperty("nickname","MuWireUser")
|
nickname = props.getProperty("nickname","MuWireUser")
|
||||||
downloadLocation = new File((String)props.getProperty("downloadLocation",
|
downloadLocation = new File((String)props.getProperty("downloadLocation",
|
||||||
System.getProperty("user.home")))
|
System.getProperty("user.home")))
|
||||||
|
String incompleteLocationProp = props.getProperty("incompleteLocation")
|
||||||
|
if (incompleteLocationProp != null)
|
||||||
|
incompleteLocation = new File(incompleteLocationProp)
|
||||||
downloadRetryInterval = Integer.parseInt(props.getProperty("downloadRetryInterval","60"))
|
downloadRetryInterval = Integer.parseInt(props.getProperty("downloadRetryInterval","60"))
|
||||||
updateCheckInterval = Integer.parseInt(props.getProperty("updateCheckInterval","24"))
|
updateCheckInterval = Integer.parseInt(props.getProperty("updateCheckInterval","24"))
|
||||||
autoDownloadUpdate = Boolean.parseBoolean(props.getProperty("autoDownloadUpdate","true"))
|
autoDownloadUpdate = Boolean.parseBoolean(props.getProperty("autoDownloadUpdate","true"))
|
||||||
updateType = props.getProperty("updateType","jar")
|
updateType = props.getProperty("updateType","jar")
|
||||||
shareDownloadedFiles = Boolean.parseBoolean(props.getProperty("shareDownloadedFiles","true"))
|
shareDownloadedFiles = Boolean.parseBoolean(props.getProperty("shareDownloadedFiles","true"))
|
||||||
|
shareHiddenFiles = Boolean.parseBoolean(props.getProperty("shareHiddenFiles","false"))
|
||||||
downloadSequentialRatio = Float.valueOf(props.getProperty("downloadSequentialRatio","0.8"))
|
downloadSequentialRatio = Float.valueOf(props.getProperty("downloadSequentialRatio","0.8"))
|
||||||
hostClearInterval = Integer.valueOf(props.getProperty("hostClearInterval","15"))
|
hostClearInterval = Integer.valueOf(props.getProperty("hostClearInterval","15"))
|
||||||
hostHopelessInterval = Integer.valueOf(props.getProperty("hostHopelessInterval", "1440"))
|
hostHopelessInterval = Integer.valueOf(props.getProperty("hostHopelessInterval", "1440"))
|
||||||
@@ -60,6 +69,9 @@ class MuWireSettings {
|
|||||||
embeddedRouter = Boolean.valueOf(props.getProperty("embeddedRouter","false"))
|
embeddedRouter = Boolean.valueOf(props.getProperty("embeddedRouter","false"))
|
||||||
inBw = Integer.valueOf(props.getProperty("inBw","256"))
|
inBw = Integer.valueOf(props.getProperty("inBw","256"))
|
||||||
outBw = Integer.valueOf(props.getProperty("outBw","128"))
|
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")
|
watchedDirectories = readEncodedSet(props, "watchedDirectories")
|
||||||
watchedKeywords = readEncodedSet(props, "watchedKeywords")
|
watchedKeywords = readEncodedSet(props, "watchedKeywords")
|
||||||
@@ -85,11 +97,14 @@ class MuWireSettings {
|
|||||||
props.setProperty("crawlerResponse", crawlerResponse.toString())
|
props.setProperty("crawlerResponse", crawlerResponse.toString())
|
||||||
props.setProperty("nickname", nickname)
|
props.setProperty("nickname", nickname)
|
||||||
props.setProperty("downloadLocation", downloadLocation.getAbsolutePath())
|
props.setProperty("downloadLocation", downloadLocation.getAbsolutePath())
|
||||||
|
if (incompleteLocation != null)
|
||||||
|
props.setProperty("incompleteLocation", incompleteLocation.getAbsolutePath())
|
||||||
props.setProperty("downloadRetryInterval", String.valueOf(downloadRetryInterval))
|
props.setProperty("downloadRetryInterval", String.valueOf(downloadRetryInterval))
|
||||||
props.setProperty("updateCheckInterval", String.valueOf(updateCheckInterval))
|
props.setProperty("updateCheckInterval", String.valueOf(updateCheckInterval))
|
||||||
props.setProperty("autoDownloadUpdate", String.valueOf(autoDownloadUpdate))
|
props.setProperty("autoDownloadUpdate", String.valueOf(autoDownloadUpdate))
|
||||||
props.setProperty("updateType",String.valueOf(updateType))
|
props.setProperty("updateType",String.valueOf(updateType))
|
||||||
props.setProperty("shareDownloadedFiles", String.valueOf(shareDownloadedFiles))
|
props.setProperty("shareDownloadedFiles", String.valueOf(shareDownloadedFiles))
|
||||||
|
props.setProperty("shareHiddenFiles", String.valueOf(shareHiddenFiles))
|
||||||
props.setProperty("downloadSequentialRatio", String.valueOf(downloadSequentialRatio))
|
props.setProperty("downloadSequentialRatio", String.valueOf(downloadSequentialRatio))
|
||||||
props.setProperty("hostClearInterval", String.valueOf(hostClearInterval))
|
props.setProperty("hostClearInterval", String.valueOf(hostClearInterval))
|
||||||
props.setProperty("hostHopelessInterval", String.valueOf(hostHopelessInterval))
|
props.setProperty("hostHopelessInterval", String.valueOf(hostHopelessInterval))
|
||||||
@@ -98,6 +113,9 @@ class MuWireSettings {
|
|||||||
props.setProperty("embeddedRouter", String.valueOf(embeddedRouter))
|
props.setProperty("embeddedRouter", String.valueOf(embeddedRouter))
|
||||||
props.setProperty("inBw", String.valueOf(inBw))
|
props.setProperty("inBw", String.valueOf(inBw))
|
||||||
props.setProperty("outBw", String.valueOf(outBw))
|
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(watchedDirectories, "watchedDirectories", props)
|
||||||
writeEncodedSet(watchedKeywords, "watchedKeywords", props)
|
writeEncodedSet(watchedKeywords, "watchedKeywords", props)
|
||||||
|
@@ -132,6 +132,8 @@ abstract class Connection implements Closeable {
|
|||||||
query.firstHop = e.firstHop
|
query.firstHop = e.firstHop
|
||||||
query.keywords = e.searchEvent.getSearchTerms()
|
query.keywords = e.searchEvent.getSearchTerms()
|
||||||
query.oobInfohash = e.searchEvent.oobInfohash
|
query.oobInfohash = e.searchEvent.oobInfohash
|
||||||
|
query.searchComments = e.searchEvent.searchComments
|
||||||
|
query.compressedResults = e.searchEvent.compressedResults
|
||||||
if (e.searchEvent.searchHash != null)
|
if (e.searchEvent.searchHash != null)
|
||||||
query.infohash = Base64.encode(e.searchEvent.searchHash)
|
query.infohash = Base64.encode(e.searchEvent.searchHash)
|
||||||
query.replyTo = e.replyTo.toBase64()
|
query.replyTo = e.replyTo.toBase64()
|
||||||
@@ -209,11 +211,19 @@ abstract class Connection implements Closeable {
|
|||||||
boolean oob = false
|
boolean oob = false
|
||||||
if (search.oobInfohash != null)
|
if (search.oobInfohash != null)
|
||||||
oob = search.oobInfohash
|
oob = search.oobInfohash
|
||||||
|
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,
|
SearchEvent searchEvent = new SearchEvent(searchTerms : search.keywords,
|
||||||
searchHash : infohash,
|
searchHash : infohash,
|
||||||
uuid : uuid,
|
uuid : uuid,
|
||||||
oobInfohash : oob)
|
oobInfohash : oob,
|
||||||
|
searchComments : searchComments,
|
||||||
|
compressedResults : compressedResults)
|
||||||
QueryEvent event = new QueryEvent ( searchEvent : searchEvent,
|
QueryEvent event = new QueryEvent ( searchEvent : searchEvent,
|
||||||
replyTo : replyTo,
|
replyTo : replyTo,
|
||||||
originator : originator,
|
originator : originator,
|
||||||
|
@@ -5,11 +5,15 @@ import java.util.concurrent.ExecutorService
|
|||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.logging.Level
|
import java.util.logging.Level
|
||||||
import java.util.zip.DeflaterOutputStream
|
import java.util.zip.DeflaterOutputStream
|
||||||
|
import java.util.zip.GZIPInputStream
|
||||||
|
import java.util.zip.GZIPOutputStream
|
||||||
import java.util.zip.InflaterInputStream
|
import java.util.zip.InflaterInputStream
|
||||||
|
|
||||||
|
import com.muwire.core.Constants
|
||||||
import com.muwire.core.EventBus
|
import com.muwire.core.EventBus
|
||||||
import com.muwire.core.MuWireSettings
|
import com.muwire.core.MuWireSettings
|
||||||
import com.muwire.core.Persona
|
import com.muwire.core.Persona
|
||||||
|
import com.muwire.core.files.FileManager
|
||||||
import com.muwire.core.hostcache.HostCache
|
import com.muwire.core.hostcache.HostCache
|
||||||
import com.muwire.core.trust.TrustLevel
|
import com.muwire.core.trust.TrustLevel
|
||||||
import com.muwire.core.trust.TrustService
|
import com.muwire.core.trust.TrustService
|
||||||
@@ -17,6 +21,7 @@ import com.muwire.core.upload.UploadManager
|
|||||||
import com.muwire.core.util.DataUtil
|
import com.muwire.core.util.DataUtil
|
||||||
import com.muwire.core.search.InvalidSearchResultException
|
import com.muwire.core.search.InvalidSearchResultException
|
||||||
import com.muwire.core.search.ResultsParser
|
import com.muwire.core.search.ResultsParser
|
||||||
|
import com.muwire.core.search.ResultsSender
|
||||||
import com.muwire.core.search.SearchManager
|
import com.muwire.core.search.SearchManager
|
||||||
import com.muwire.core.search.UIResultBatchEvent
|
import com.muwire.core.search.UIResultBatchEvent
|
||||||
import com.muwire.core.search.UIResultEvent
|
import com.muwire.core.search.UIResultEvent
|
||||||
@@ -25,6 +30,7 @@ import com.muwire.core.search.UnexpectedResultsException
|
|||||||
import groovy.json.JsonOutput
|
import groovy.json.JsonOutput
|
||||||
import groovy.json.JsonSlurper
|
import groovy.json.JsonSlurper
|
||||||
import groovy.util.logging.Log
|
import groovy.util.logging.Log
|
||||||
|
import net.i2p.data.Base64
|
||||||
|
|
||||||
@Log
|
@Log
|
||||||
class ConnectionAcceptor {
|
class ConnectionAcceptor {
|
||||||
@@ -37,6 +43,7 @@ class ConnectionAcceptor {
|
|||||||
final TrustService trustService
|
final TrustService trustService
|
||||||
final SearchManager searchManager
|
final SearchManager searchManager
|
||||||
final UploadManager uploadManager
|
final UploadManager uploadManager
|
||||||
|
final FileManager fileManager
|
||||||
final ConnectionEstablisher establisher
|
final ConnectionEstablisher establisher
|
||||||
|
|
||||||
final ExecutorService acceptorThread
|
final ExecutorService acceptorThread
|
||||||
@@ -47,7 +54,7 @@ class ConnectionAcceptor {
|
|||||||
ConnectionAcceptor(EventBus eventBus, UltrapeerConnectionManager manager,
|
ConnectionAcceptor(EventBus eventBus, UltrapeerConnectionManager manager,
|
||||||
MuWireSettings settings, I2PAcceptor acceptor, HostCache hostCache,
|
MuWireSettings settings, I2PAcceptor acceptor, HostCache hostCache,
|
||||||
TrustService trustService, SearchManager searchManager, UploadManager uploadManager,
|
TrustService trustService, SearchManager searchManager, UploadManager uploadManager,
|
||||||
ConnectionEstablisher establisher) {
|
FileManager fileManager, ConnectionEstablisher establisher) {
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.manager = manager
|
this.manager = manager
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
@@ -55,6 +62,7 @@ class ConnectionAcceptor {
|
|||||||
this.hostCache = hostCache
|
this.hostCache = hostCache
|
||||||
this.trustService = trustService
|
this.trustService = trustService
|
||||||
this.searchManager = searchManager
|
this.searchManager = searchManager
|
||||||
|
this.fileManager = fileManager
|
||||||
this.uploadManager = uploadManager
|
this.uploadManager = uploadManager
|
||||||
this.establisher = establisher
|
this.establisher = establisher
|
||||||
|
|
||||||
@@ -126,9 +134,15 @@ class ConnectionAcceptor {
|
|||||||
case (byte)'P':
|
case (byte)'P':
|
||||||
processPOST(e)
|
processPOST(e)
|
||||||
break
|
break
|
||||||
|
case (byte)'R':
|
||||||
|
processRESULTS(e)
|
||||||
|
break
|
||||||
case (byte)'T':
|
case (byte)'T':
|
||||||
processTRUST(e)
|
processTRUST(e)
|
||||||
break
|
break
|
||||||
|
case (byte)'B':
|
||||||
|
processBROWSE(e)
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
throw new Exception("Invalid read $read")
|
throw new Exception("Invalid read $read")
|
||||||
}
|
}
|
||||||
@@ -229,7 +243,7 @@ class ConnectionAcceptor {
|
|||||||
|
|
||||||
Persona sender = new Persona(dis)
|
Persona sender = new Persona(dis)
|
||||||
if (sender.destination != e.getDestination())
|
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()
|
int nResults = dis.readUnsignedShort()
|
||||||
UIResultEvent[] results = new UIResultEvent[nResults]
|
UIResultEvent[] results = new UIResultEvent[nResults]
|
||||||
for (int i = 0; i < nResults; i++) {
|
for (int i = 0; i < nResults; i++) {
|
||||||
@@ -247,7 +261,108 @@ 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]
|
||||||
|
DataInputStream dis = new DataInputStream(e.getInputStream())
|
||||||
|
dis.readFully(rowse)
|
||||||
|
if (rowse != "ROWSE\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
|
throw new IOException("Invalid BROWSE connection")
|
||||||
|
String header
|
||||||
|
while ((header = DataUtil.readTillRN(dis)) != ""); // ignore headers for now
|
||||||
|
|
||||||
|
OutputStream os = e.getOutputStream()
|
||||||
|
if (!settings.browseFiles) {
|
||||||
|
os.write("403 Not Allowed\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
|
os.flush()
|
||||||
|
e.close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
os.write("200 OK\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
|
|
||||||
|
def sharedFiles = fileManager.getSharedFiles().values()
|
||||||
|
|
||||||
|
os.write("Count: ${sharedFiles.size()}\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
|
|
||||||
|
DataOutputStream dos = new DataOutputStream(new GZIPOutputStream(os))
|
||||||
|
JsonOutput jsonOutput = new JsonOutput()
|
||||||
|
sharedFiles.each {
|
||||||
|
def obj = ResultsSender.sharedFileToObj(it, false)
|
||||||
|
def json = jsonOutput.toJson(obj)
|
||||||
|
dos.writeShort((short)json.length())
|
||||||
|
dos.write(json.getBytes(StandardCharsets.US_ASCII))
|
||||||
|
}
|
||||||
|
dos.flush()
|
||||||
|
dos.close()
|
||||||
|
} finally {
|
||||||
|
e.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void processTRUST(Endpoint e) {
|
private void processTRUST(Endpoint e) {
|
||||||
|
try {
|
||||||
byte[] RUST = new byte[6]
|
byte[] RUST = new byte[6]
|
||||||
DataInputStream dis = new DataInputStream(e.getInputStream())
|
DataInputStream dis = new DataInputStream(e.getInputStream())
|
||||||
dis.readFully(RUST)
|
dis.readFully(RUST)
|
||||||
@@ -283,7 +398,9 @@ class ConnectionAcceptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dos.flush()
|
dos.flush()
|
||||||
|
} finally {
|
||||||
e.close()
|
e.close()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -34,7 +34,7 @@ public class DownloadManager {
|
|||||||
private final MuWireSettings muSettings
|
private final MuWireSettings muSettings
|
||||||
private final I2PConnector connector
|
private final I2PConnector connector
|
||||||
private final Executor executor
|
private final Executor executor
|
||||||
private final File incompletes, home
|
private final File home
|
||||||
private final Persona me
|
private final Persona me
|
||||||
|
|
||||||
private final Map<InfoHash, Downloader> downloaders = new ConcurrentHashMap<>()
|
private final Map<InfoHash, Downloader> downloaders = new ConcurrentHashMap<>()
|
||||||
@@ -46,12 +46,9 @@ public class DownloadManager {
|
|||||||
this.meshManager = meshManager
|
this.meshManager = meshManager
|
||||||
this.muSettings = muSettings
|
this.muSettings = muSettings
|
||||||
this.connector = connector
|
this.connector = connector
|
||||||
this.incompletes = new File(home,"incompletes")
|
|
||||||
this.home = home
|
this.home = home
|
||||||
this.me = me
|
this.me = me
|
||||||
|
|
||||||
incompletes.mkdir()
|
|
||||||
|
|
||||||
this.executor = Executors.newCachedThreadPool({ r ->
|
this.executor = Executors.newCachedThreadPool({ r ->
|
||||||
Thread rv = new Thread(r)
|
Thread rv = new Thread(r)
|
||||||
rv.setName("download-worker")
|
rv.setName("download-worker")
|
||||||
@@ -63,6 +60,11 @@ public class DownloadManager {
|
|||||||
|
|
||||||
public void onUIDownloadEvent(UIDownloadEvent e) {
|
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 size = e.result[0].size
|
||||||
def infohash = e.result[0].infohash
|
def infohash = e.result[0].infohash
|
||||||
def pieceSize = e.result[0].pieceSize
|
def pieceSize = e.result[0].pieceSize
|
||||||
@@ -127,6 +129,12 @@ public class DownloadManager {
|
|||||||
if (json.sequential != null)
|
if (json.sequential != null)
|
||||||
sequential = json.sequential
|
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)
|
Pieces pieces = getPieces(infoHash, (long)json.length, json.pieceSizePow2, sequential)
|
||||||
|
|
||||||
def downloader = new Downloader(eventBus, this, me, file, (long)json.length,
|
def downloader = new Downloader(eventBus, this, me, file, (long)json.length,
|
||||||
@@ -195,6 +203,8 @@ public class DownloadManager {
|
|||||||
|
|
||||||
json.sequential = downloader.pieces.ratio == 0f
|
json.sequential = downloader.pieces.ratio == 0f
|
||||||
|
|
||||||
|
json.incompletes = Base64.encode(DataUtil.encodei18nString(downloader.incompletes.getAbsolutePath()))
|
||||||
|
|
||||||
writer.println(JsonOutput.toJson(json))
|
writer.println(JsonOutput.toJson(json))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -27,6 +27,7 @@ import net.i2p.util.ConcurrentHashSet
|
|||||||
|
|
||||||
@Log
|
@Log
|
||||||
public class Downloader {
|
public class Downloader {
|
||||||
|
|
||||||
public enum DownloadState { CONNECTING, HASHLIST, DOWNLOADING, FAILED, CANCELLED, PAUSED, FINISHED }
|
public enum DownloadState { CONNECTING, HASHLIST, DOWNLOADING, FAILED, CANCELLED, PAUSED, FINISHED }
|
||||||
private enum WorkerState { CONNECTING, HASHLIST, DOWNLOADING, FINISHED}
|
private enum WorkerState { CONNECTING, HASHLIST, DOWNLOADING, FINISHED}
|
||||||
|
|
||||||
@@ -48,6 +49,7 @@ public class Downloader {
|
|||||||
private final I2PConnector connector
|
private final I2PConnector connector
|
||||||
private final Set<Destination> destinations
|
private final Set<Destination> destinations
|
||||||
private final int nPieces
|
private final int nPieces
|
||||||
|
private final File incompletes
|
||||||
private final File piecesFile
|
private final File piecesFile
|
||||||
private final File incompleteFile
|
private final File incompleteFile
|
||||||
final int pieceSizePow2
|
final int pieceSizePow2
|
||||||
@@ -76,16 +78,13 @@ public class Downloader {
|
|||||||
this.length = length
|
this.length = length
|
||||||
this.connector = connector
|
this.connector = connector
|
||||||
this.destinations = destinations
|
this.destinations = destinations
|
||||||
|
this.incompletes = incompletes
|
||||||
this.piecesFile = new File(incompletes, file.getName()+".pieces")
|
this.piecesFile = new File(incompletes, file.getName()+".pieces")
|
||||||
this.incompleteFile = new File(incompletes, file.getName()+".part")
|
this.incompleteFile = new File(incompletes, file.getName()+".part")
|
||||||
this.pieceSizePow2 = pieceSizePow2
|
this.pieceSizePow2 = pieceSizePow2
|
||||||
this.pieceSize = 1 << pieceSizePow2
|
this.pieceSize = 1 << pieceSizePow2
|
||||||
this.pieces = pieces
|
this.pieces = pieces
|
||||||
this.nPieces = pieces.nPieces
|
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() {
|
public synchronized InfoHash getInfoHash() {
|
||||||
@@ -146,6 +145,12 @@ public class Downloader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (speedArr.size() != downloadManager.muSettings.speedSmoothSeconds) {
|
||||||
|
speedArr.clear()
|
||||||
|
downloadManager.muSettings.speedSmoothSeconds.times { speedArr.add(0) }
|
||||||
|
speedPos = 0
|
||||||
|
}
|
||||||
|
|
||||||
// normalize to speedArr.size
|
// normalize to speedArr.size
|
||||||
currSpeed /= speedArr.size()
|
currSpeed /= speedArr.size()
|
||||||
|
|
||||||
|
@@ -8,8 +8,10 @@ import com.muwire.core.UILoadedEvent
|
|||||||
import com.muwire.core.search.ResultsEvent
|
import com.muwire.core.search.ResultsEvent
|
||||||
import com.muwire.core.search.SearchEvent
|
import com.muwire.core.search.SearchEvent
|
||||||
import com.muwire.core.search.SearchIndex
|
import com.muwire.core.search.SearchIndex
|
||||||
|
import com.muwire.core.util.DataUtil
|
||||||
|
|
||||||
import groovy.util.logging.Log
|
import groovy.util.logging.Log
|
||||||
|
import net.i2p.data.Base64
|
||||||
|
|
||||||
@Log
|
@Log
|
||||||
class FileManager {
|
class FileManager {
|
||||||
@@ -20,6 +22,7 @@ class FileManager {
|
|||||||
final Map<InfoHash, Set<SharedFile>> rootToFiles = Collections.synchronizedMap(new HashMap<>())
|
final Map<InfoHash, Set<SharedFile>> rootToFiles = Collections.synchronizedMap(new HashMap<>())
|
||||||
final Map<File, SharedFile> fileToSharedFile = Collections.synchronizedMap(new HashMap<>())
|
final Map<File, SharedFile> fileToSharedFile = Collections.synchronizedMap(new HashMap<>())
|
||||||
final Map<String, Set<File>> nameToFiles = new HashMap<>()
|
final Map<String, Set<File>> nameToFiles = new HashMap<>()
|
||||||
|
final Map<String, Set<File>> commentToFile = new HashMap<>()
|
||||||
final SearchIndex index = new SearchIndex()
|
final SearchIndex index = new SearchIndex()
|
||||||
|
|
||||||
FileManager(EventBus eventBus, MuWireSettings settings) {
|
FileManager(EventBus eventBus, MuWireSettings settings) {
|
||||||
@@ -62,6 +65,18 @@ class FileManager {
|
|||||||
}
|
}
|
||||||
existingFiles.add(sf.getFile())
|
existingFiles.add(sf.getFile())
|
||||||
|
|
||||||
|
String comment = sf.getComment()
|
||||||
|
if (comment != null) {
|
||||||
|
comment = DataUtil.readi18nString(Base64.decode(comment))
|
||||||
|
index.add(comment)
|
||||||
|
Set<File> existingComment = commentToFile.get(comment)
|
||||||
|
if(existingComment == null) {
|
||||||
|
existingComment = new HashSet<>()
|
||||||
|
commentToFile.put(comment, existingComment)
|
||||||
|
}
|
||||||
|
existingComment.add(sf.getFile())
|
||||||
|
}
|
||||||
|
|
||||||
index.add(name)
|
index.add(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,9 +102,45 @@ class FileManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String comment = sf.getComment()
|
||||||
|
if (comment != null) {
|
||||||
|
Set<File> existingComment = commentToFile.get(comment)
|
||||||
|
if (existingComment != null) {
|
||||||
|
existingComment.remove(sf.getFile())
|
||||||
|
if (existingComment.isEmpty()) {
|
||||||
|
commentToFile.remove(comment)
|
||||||
|
index.remove(comment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
index.remove(name)
|
index.remove(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onUICommentEvent(UICommentEvent e) {
|
||||||
|
if (e.oldComment != null) {
|
||||||
|
def comment = DataUtil.readi18nString(Base64.decode(e.oldComment))
|
||||||
|
Set<File> existingFiles = commentToFile.get(comment)
|
||||||
|
existingFiles.remove(e.sharedFile.getFile())
|
||||||
|
if (existingFiles.isEmpty()) {
|
||||||
|
commentToFile.remove(comment)
|
||||||
|
index.remove(comment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String comment = e.sharedFile.getComment()
|
||||||
|
comment = DataUtil.readi18nString(Base64.decode(comment))
|
||||||
|
if (comment != null) {
|
||||||
|
index.add(comment)
|
||||||
|
Set<File> existingComment = commentToFile.get(comment)
|
||||||
|
if(existingComment == null) {
|
||||||
|
existingComment = new HashSet<>()
|
||||||
|
commentToFile.put(comment, existingComment)
|
||||||
|
}
|
||||||
|
existingComment.add(e.sharedFile.getFile())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Map<File, SharedFile> getSharedFiles() {
|
Map<File, SharedFile> getSharedFiles() {
|
||||||
synchronized(fileToSharedFile) {
|
synchronized(fileToSharedFile) {
|
||||||
return new HashMap<>(fileToSharedFile)
|
return new HashMap<>(fileToSharedFile)
|
||||||
@@ -112,10 +163,15 @@ class FileManager {
|
|||||||
} else {
|
} else {
|
||||||
def names = index.search e.searchTerms
|
def names = index.search e.searchTerms
|
||||||
Set<File> files = new HashSet<>()
|
Set<File> files = new HashSet<>()
|
||||||
names.each { files.addAll nameToFiles.getOrDefault(it, []) }
|
names.each {
|
||||||
|
files.addAll nameToFiles.getOrDefault(it, [])
|
||||||
|
if (e.searchComments)
|
||||||
|
files.addAll commentToFile.getOrDefault(it, [])
|
||||||
|
}
|
||||||
Set<SharedFile> sharedFiles = new HashSet<>()
|
Set<SharedFile> sharedFiles = new HashSet<>()
|
||||||
files.each { sharedFiles.add fileToSharedFile[it] }
|
files.each { sharedFiles.add fileToSharedFile[it] }
|
||||||
files = filter(sharedFiles, e.oobInfohash)
|
files = filter(sharedFiles, e.oobInfohash)
|
||||||
|
|
||||||
if (!sharedFiles.isEmpty())
|
if (!sharedFiles.isEmpty())
|
||||||
re = new ResultsEvent(results: sharedFiles.asList(), uuid: e.uuid, searchEvent: e)
|
re = new ResultsEvent(results: sharedFiles.asList(), uuid: e.uuid, searchEvent: e)
|
||||||
|
|
||||||
|
@@ -4,6 +4,7 @@ import java.util.concurrent.Executor
|
|||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
import com.muwire.core.EventBus
|
import com.muwire.core.EventBus
|
||||||
|
import com.muwire.core.MuWireSettings
|
||||||
import com.muwire.core.SharedFile
|
import com.muwire.core.SharedFile
|
||||||
|
|
||||||
class HasherService {
|
class HasherService {
|
||||||
@@ -12,12 +13,14 @@ class HasherService {
|
|||||||
final EventBus eventBus
|
final EventBus eventBus
|
||||||
final FileManager fileManager
|
final FileManager fileManager
|
||||||
final Set<File> hashed = new HashSet<>()
|
final Set<File> hashed = new HashSet<>()
|
||||||
|
final MuWireSettings settings
|
||||||
Executor executor
|
Executor executor
|
||||||
|
|
||||||
HasherService(FileHasher hasher, EventBus eventBus, FileManager fileManager) {
|
HasherService(FileHasher hasher, EventBus eventBus, FileManager fileManager, MuWireSettings settings) {
|
||||||
this.hasher = hasher
|
this.hasher = hasher
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.fileManager = fileManager
|
this.fileManager = fileManager
|
||||||
|
this.settings = settings
|
||||||
}
|
}
|
||||||
|
|
||||||
void start() {
|
void start() {
|
||||||
@@ -26,6 +29,8 @@ class HasherService {
|
|||||||
|
|
||||||
void onFileSharedEvent(FileSharedEvent evt) {
|
void onFileSharedEvent(FileSharedEvent evt) {
|
||||||
File canonical = evt.file.getCanonicalFile()
|
File canonical = evt.file.getCanonicalFile()
|
||||||
|
if (!settings.shareHiddenFiles && canonical.isHidden())
|
||||||
|
return
|
||||||
if (fileManager.fileToSharedFile.containsKey(canonical))
|
if (fileManager.fileToSharedFile.containsKey(canonical))
|
||||||
return
|
return
|
||||||
if (hashed.add(canonical))
|
if (hashed.add(canonical))
|
||||||
|
@@ -0,0 +1,9 @@
|
|||||||
|
package com.muwire.core.files
|
||||||
|
|
||||||
|
import com.muwire.core.Event
|
||||||
|
import com.muwire.core.SharedFile
|
||||||
|
|
||||||
|
class UICommentEvent extends Event {
|
||||||
|
SharedFile sharedFile
|
||||||
|
String oldComment
|
||||||
|
}
|
@@ -106,6 +106,22 @@ class HostCache extends Service {
|
|||||||
rv[0..n-1]
|
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() {
|
void load() {
|
||||||
if (storage.exists()) {
|
if (storage.exists()) {
|
||||||
JsonSlurper slurper = new JsonSlurper()
|
JsonSlurper slurper = new JsonSlurper()
|
||||||
|
@@ -0,0 +1,87 @@
|
|||||||
|
package com.muwire.core.search
|
||||||
|
|
||||||
|
import com.muwire.core.Constants
|
||||||
|
import com.muwire.core.EventBus
|
||||||
|
import com.muwire.core.connection.Endpoint
|
||||||
|
import com.muwire.core.connection.I2PConnector
|
||||||
|
import com.muwire.core.util.DataUtil
|
||||||
|
|
||||||
|
import groovy.json.JsonSlurper
|
||||||
|
import groovy.util.logging.Log
|
||||||
|
|
||||||
|
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 {
|
||||||
|
|
||||||
|
private final I2PConnector connector
|
||||||
|
private final EventBus eventBus
|
||||||
|
|
||||||
|
private final Executor browserThread = Executors.newSingleThreadExecutor()
|
||||||
|
|
||||||
|
BrowseManager(I2PConnector connector, EventBus eventBus) {
|
||||||
|
this.connector = connector
|
||||||
|
this.eventBus = eventBus
|
||||||
|
}
|
||||||
|
|
||||||
|
void onUIBrowseEvent(UIBrowseEvent e) {
|
||||||
|
browserThread.execute({
|
||||||
|
Endpoint endpoint = null
|
||||||
|
try {
|
||||||
|
eventBus.publish(new BrowseStatusEvent(status : BrowseStatus.CONNECTING))
|
||||||
|
endpoint = connector.connect(e.host.destination)
|
||||||
|
OutputStream os = endpoint.getOutputStream()
|
||||||
|
os.write("BROWSE\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
|
|
||||||
|
InputStream is = endpoint.getInputStream()
|
||||||
|
String code = DataUtil.readTillRN(is)
|
||||||
|
if (!code.startsWith("200"))
|
||||||
|
throw new IOException("Invalid code $code")
|
||||||
|
|
||||||
|
// 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("Count"))
|
||||||
|
throw new IOException("No count header")
|
||||||
|
|
||||||
|
int results = Integer.parseInt(headers['Count'])
|
||||||
|
|
||||||
|
// at this stage, start pulling the results
|
||||||
|
eventBus.publish(new BrowseStatusEvent(status : BrowseStatus.FETCHING, totalResults : results))
|
||||||
|
|
||||||
|
JsonSlurper slurper = new JsonSlurper()
|
||||||
|
DataInputStream dis = new DataInputStream(new GZIPInputStream(is))
|
||||||
|
UUID uuid = UUID.randomUUID()
|
||||||
|
for (int i = 0; i < results; i++) {
|
||||||
|
int size = dis.readUnsignedShort()
|
||||||
|
byte [] tmp = new byte[size]
|
||||||
|
dis.readFully(tmp)
|
||||||
|
def json = slurper.parse(tmp)
|
||||||
|
UIResultEvent result = ResultsParser.parse(e.host, uuid, json)
|
||||||
|
eventBus.publish(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
eventBus.publish(new BrowseStatusEvent(status : BrowseStatus.FINISHED))
|
||||||
|
|
||||||
|
} catch (Exception bad) {
|
||||||
|
log.log(Level.WARNING, "browse failed", bad)
|
||||||
|
eventBus.publish(new BrowseStatusEvent(status : BrowseStatus.FAILED))
|
||||||
|
} finally {
|
||||||
|
endpoint?.close()
|
||||||
|
}
|
||||||
|
} as Runnable)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,5 @@
|
|||||||
|
package com.muwire.core.search;
|
||||||
|
|
||||||
|
public enum BrowseStatus {
|
||||||
|
CONNECTING, FETCHING, FINISHED, FAILED
|
||||||
|
}
|
@@ -0,0 +1,8 @@
|
|||||||
|
package com.muwire.core.search
|
||||||
|
|
||||||
|
import com.muwire.core.Event
|
||||||
|
|
||||||
|
class BrowseStatusEvent extends Event {
|
||||||
|
BrowseStatus status
|
||||||
|
int totalResults
|
||||||
|
}
|
@@ -95,6 +95,10 @@ class ResultsParser {
|
|||||||
if (json.comment != null)
|
if (json.comment != null)
|
||||||
comment = DataUtil.readi18nString(Base64.decode(json.comment))
|
comment = DataUtil.readi18nString(Base64.decode(json.comment))
|
||||||
|
|
||||||
|
boolean browse = false
|
||||||
|
if (json.browse != null)
|
||||||
|
browse = json.browse
|
||||||
|
|
||||||
return new UIResultEvent( sender : p,
|
return new UIResultEvent( sender : p,
|
||||||
name : name,
|
name : name,
|
||||||
size : size,
|
size : size,
|
||||||
@@ -102,6 +106,7 @@ class ResultsParser {
|
|||||||
pieceSize : pieceSize,
|
pieceSize : pieceSize,
|
||||||
sources : sources,
|
sources : sources,
|
||||||
comment : comment,
|
comment : comment,
|
||||||
|
browse : browse,
|
||||||
uuid: uuid)
|
uuid: uuid)
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new InvalidSearchResultException("parsing search result failed",e)
|
throw new InvalidSearchResultException("parsing search result failed",e)
|
||||||
|
@@ -14,10 +14,12 @@ import java.util.concurrent.ThreadFactory
|
|||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import java.util.logging.Level
|
import java.util.logging.Level
|
||||||
import java.util.stream.Collectors
|
import java.util.stream.Collectors
|
||||||
|
import java.util.zip.GZIPOutputStream
|
||||||
|
|
||||||
import com.muwire.core.DownloadedFile
|
import com.muwire.core.DownloadedFile
|
||||||
import com.muwire.core.EventBus
|
import com.muwire.core.EventBus
|
||||||
import com.muwire.core.InfoHash
|
import com.muwire.core.InfoHash
|
||||||
|
import com.muwire.core.MuWireSettings
|
||||||
|
|
||||||
import groovy.json.JsonOutput
|
import groovy.json.JsonOutput
|
||||||
import groovy.util.logging.Log
|
import groovy.util.logging.Log
|
||||||
@@ -43,16 +45,19 @@ class ResultsSender {
|
|||||||
private final I2PConnector connector
|
private final I2PConnector connector
|
||||||
private final Persona me
|
private final Persona me
|
||||||
private final EventBus eventBus
|
private final EventBus eventBus
|
||||||
|
private final MuWireSettings settings
|
||||||
|
|
||||||
ResultsSender(EventBus eventBus, I2PConnector connector, Persona me) {
|
ResultsSender(EventBus eventBus, I2PConnector connector, Persona me, MuWireSettings settings) {
|
||||||
this.connector = connector;
|
this.connector = connector;
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.me = me
|
this.me = me
|
||||||
|
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")
|
log.info("Sending $results.length results for uuid $uuid to ${target.toBase32()} oobInfohash : $oobInfohash")
|
||||||
if (target.equals(me.destination)) {
|
if (target.equals(me.destination)) {
|
||||||
|
def uiResultEvents = []
|
||||||
results.each {
|
results.each {
|
||||||
long length = it.getFile().length()
|
long length = it.getFile().length()
|
||||||
int pieceSize = it.getPieceSize()
|
int pieceSize = it.getPieceSize()
|
||||||
@@ -74,11 +79,12 @@ class ResultsSender {
|
|||||||
sources : suggested,
|
sources : suggested,
|
||||||
comment : comment
|
comment : comment
|
||||||
)
|
)
|
||||||
eventBus.publish(uiResultEvent)
|
uiResultEvents << uiResultEvent
|
||||||
}
|
}
|
||||||
|
eventBus.publish(new UIResultBatchEvent(uuid : uuid, results : uiResultEvents))
|
||||||
} else {
|
} else {
|
||||||
executor.execute(new ResultSendJob(uuid : uuid, results : results,
|
executor.execute(new ResultSendJob(uuid : uuid, results : results,
|
||||||
target: target, oobInfohash : oobInfohash))
|
target: target, oobInfohash : oobInfohash, compressedResults : compressedResults))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,13 +93,14 @@ class ResultsSender {
|
|||||||
SharedFile [] results
|
SharedFile [] results
|
||||||
Destination target
|
Destination target
|
||||||
boolean oobInfohash
|
boolean oobInfohash
|
||||||
|
boolean compressedResults
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
byte [] tmp = new byte[InfoHash.SIZE]
|
|
||||||
JsonOutput jsonOutput = new JsonOutput()
|
JsonOutput jsonOutput = new JsonOutput()
|
||||||
Endpoint endpoint = null;
|
Endpoint endpoint = null;
|
||||||
|
if (!compressedResults) {
|
||||||
try {
|
try {
|
||||||
endpoint = connector.connect(target)
|
endpoint = connector.connect(target)
|
||||||
DataOutputStream os = new DataOutputStream(endpoint.getOutputStream())
|
DataOutputStream os = new DataOutputStream(endpoint.getOutputStream())
|
||||||
@@ -101,36 +108,7 @@ class ResultsSender {
|
|||||||
me.write(os)
|
me.write(os)
|
||||||
os.writeShort((short)results.length)
|
os.writeShort((short)results.length)
|
||||||
results.each {
|
results.each {
|
||||||
byte [] name = it.getFile().getName().getBytes(StandardCharsets.UTF_8)
|
def obj = sharedFileToObj(it, settings.browseFiles)
|
||||||
def baos = new ByteArrayOutputStream()
|
|
||||||
def daos = new DataOutputStream(baos)
|
|
||||||
daos.writeShort((short) name.length)
|
|
||||||
daos.write(name)
|
|
||||||
daos.flush()
|
|
||||||
String encodedName = Base64.encode(baos.toByteArray())
|
|
||||||
def obj = [:]
|
|
||||||
obj.type = "Result"
|
|
||||||
obj.version = oobInfohash ? 2 : 1
|
|
||||||
obj.name = encodedName
|
|
||||||
obj.infohash = Base64.encode(it.getInfoHash().getRoot())
|
|
||||||
obj.size = it.getFile().length()
|
|
||||||
obj.pieceSize = it.getPieceSize()
|
|
||||||
if (!oobInfohash) {
|
|
||||||
byte [] hashList = it.getInfoHash().getHashList()
|
|
||||||
def hashListB64 = []
|
|
||||||
for (int i = 0; i < hashList.length / InfoHash.SIZE; i++) {
|
|
||||||
System.arraycopy(hashList, InfoHash.SIZE * i, tmp, 0, InfoHash.SIZE)
|
|
||||||
hashListB64 << Base64.encode(tmp)
|
|
||||||
}
|
|
||||||
obj.hashList = hashListB64
|
|
||||||
}
|
|
||||||
|
|
||||||
if (it instanceof DownloadedFile)
|
|
||||||
obj.sources = it.sources.stream().map({dest -> dest.toBase64()}).collect(Collectors.toSet())
|
|
||||||
|
|
||||||
if (it.getComment() != null)
|
|
||||||
obj.comment = it.getComment()
|
|
||||||
|
|
||||||
def json = jsonOutput.toJson(obj)
|
def json = jsonOutput.toJson(obj)
|
||||||
os.writeShort((short)json.length())
|
os.writeShort((short)json.length())
|
||||||
os.write(json.getBytes(StandardCharsets.US_ASCII))
|
os.write(json.getBytes(StandardCharsets.US_ASCII))
|
||||||
@@ -139,9 +117,55 @@ class ResultsSender {
|
|||||||
} finally {
|
} finally {
|
||||||
endpoint?.close()
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.log(Level.WARNING, "problem sending results",e)
|
log.log(Level.WARNING, "problem sending results",e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static def sharedFileToObj(SharedFile sf, boolean browseFiles) {
|
||||||
|
byte [] name = sf.getFile().getName().getBytes(StandardCharsets.UTF_8)
|
||||||
|
def baos = new ByteArrayOutputStream()
|
||||||
|
def daos = new DataOutputStream(baos)
|
||||||
|
daos.writeShort((short) name.length)
|
||||||
|
daos.write(name)
|
||||||
|
daos.flush()
|
||||||
|
String encodedName = Base64.encode(baos.toByteArray())
|
||||||
|
def obj = [:]
|
||||||
|
obj.type = "Result"
|
||||||
|
obj.version = 2
|
||||||
|
obj.name = encodedName
|
||||||
|
obj.infohash = Base64.encode(sf.getInfoHash().getRoot())
|
||||||
|
obj.size = sf.getCachedLength()
|
||||||
|
obj.pieceSize = sf.getPieceSize()
|
||||||
|
|
||||||
|
if (sf instanceof DownloadedFile)
|
||||||
|
obj.sources = sf.sources.stream().map({dest -> dest.toBase64()}).collect(Collectors.toSet())
|
||||||
|
|
||||||
|
if (sf.getComment() != null)
|
||||||
|
obj.comment = sf.getComment()
|
||||||
|
|
||||||
|
obj.browse = browseFiles
|
||||||
|
obj
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,11 +9,13 @@ class SearchEvent extends Event {
|
|||||||
byte [] searchHash
|
byte [] searchHash
|
||||||
UUID uuid
|
UUID uuid
|
||||||
boolean oobInfohash
|
boolean oobInfohash
|
||||||
|
boolean searchComments
|
||||||
|
boolean compressedResults
|
||||||
|
|
||||||
String toString() {
|
String toString() {
|
||||||
def infoHash = null
|
def infoHash = null
|
||||||
if (searchHash != null)
|
if (searchHash != null)
|
||||||
infoHash = new InfoHash(searchHash)
|
infoHash = new InfoHash(searchHash)
|
||||||
"searchTerms: $searchTerms searchHash:$infoHash, uuid:$uuid oobInfohash:$oobInfohash"
|
"searchTerms: $searchTerms searchHash:$infoHash, uuid:$uuid oobInfohash:$oobInfohash searchComments:$searchComments compressedResults:$compressedResults"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -44,7 +44,7 @@ public class SearchManager {
|
|||||||
log.info("No results for search uuid $event.uuid")
|
log.info("No results for search uuid $event.uuid")
|
||||||
return
|
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) {
|
boolean hasLocalSearch(UUID uuid) {
|
||||||
|
@@ -0,0 +1,8 @@
|
|||||||
|
package com.muwire.core.search
|
||||||
|
|
||||||
|
import com.muwire.core.Event
|
||||||
|
import com.muwire.core.Persona
|
||||||
|
|
||||||
|
class UIBrowseEvent extends Event {
|
||||||
|
Persona host
|
||||||
|
}
|
@@ -15,6 +15,7 @@ class UIResultEvent extends Event {
|
|||||||
InfoHash infohash
|
InfoHash infohash
|
||||||
int pieceSize
|
int pieceSize
|
||||||
String comment
|
String comment
|
||||||
|
boolean browse
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
|
@@ -8,4 +8,6 @@ public class Constants {
|
|||||||
|
|
||||||
public static final int MAX_HEADER_SIZE = 0x1 << 14;
|
public static final int MAX_HEADER_SIZE = 0x1 << 14;
|
||||||
public static final int MAX_HEADERS = 16;
|
public static final int MAX_HEADERS = 16;
|
||||||
|
|
||||||
|
public static final int MAX_RESULTS = 0x1 << 16;
|
||||||
}
|
}
|
||||||
|
@@ -25,7 +25,8 @@ class HasherServiceTest {
|
|||||||
void before() {
|
void before() {
|
||||||
eventBus = new EventBus()
|
eventBus = new EventBus()
|
||||||
hasher = new FileHasher()
|
hasher = new FileHasher()
|
||||||
service = new HasherService(hasher, eventBus, new FileManager(eventBus, new MuWireSettings()))
|
def props = new MuWireSettings()
|
||||||
|
service = new HasherService(hasher, eventBus, new FileManager(eventBus, props), props)
|
||||||
eventBus.register(FileHashedEvent.class, listener)
|
eventBus.register(FileHashedEvent.class, listener)
|
||||||
eventBus.register(FileSharedEvent.class, service)
|
eventBus.register(FileSharedEvent.class, service)
|
||||||
service.start()
|
service.start()
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
group = com.muwire
|
group = com.muwire
|
||||||
version = 0.5.0
|
version = 0.5.2
|
||||||
|
i2pVersion = 0.9.42
|
||||||
groovyVersion = 2.4.15
|
groovyVersion = 2.4.15
|
||||||
slf4jVersion = 1.7.25
|
slf4jVersion = 1.7.25
|
||||||
spockVersion = 1.1-groovy-2.4
|
spockVersion = 1.1-groovy-2.4
|
||||||
@@ -12,7 +13,6 @@ targetCompatibility=1.8
|
|||||||
# plugin properties
|
# plugin properties
|
||||||
author = zab@mail.i2p
|
author = zab@mail.i2p
|
||||||
signer = zab@mail.i2p
|
signer = zab@mail.i2p
|
||||||
i2pVersion=0.9.41
|
|
||||||
keystorePassword=changeit
|
keystorePassword=changeit
|
||||||
websiteURL=http://muwire.i2p
|
websiteURL=http://muwire.i2p
|
||||||
updateURLsu3=http://muwire.i2p/MuWire.su3
|
updateURLsu3=http://muwire.i2p/MuWire.su3
|
||||||
|
@@ -58,7 +58,11 @@ dependencies {
|
|||||||
compile project(":core")
|
compile project(":core")
|
||||||
compile "org.codehaus.griffon:griffon-guice:${griffon.version}"
|
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"
|
runtime "javax.annotation:javax.annotation-api:1.3.2"
|
||||||
|
|
||||||
testCompile "org.codehaus.griffon:griffon-fest-test:${griffon.version}"
|
testCompile "org.codehaus.griffon:griffon-fest-test:${griffon.version}"
|
||||||
|
BIN
gui/griffon-app/.DS_Store
vendored
BIN
gui/griffon-app/.DS_Store
vendored
Binary file not shown.
@@ -56,4 +56,9 @@ mvcGroups {
|
|||||||
view = 'com.muwire.gui.AddCommentView'
|
view = 'com.muwire.gui.AddCommentView'
|
||||||
controller = 'com.muwire.gui.AddCommentController'
|
controller = 'com.muwire.gui.AddCommentController'
|
||||||
}
|
}
|
||||||
|
'browse' {
|
||||||
|
model = 'com.muwire.gui.BrowseModel'
|
||||||
|
view = 'com.muwire.gui.BrowseView'
|
||||||
|
controller = 'com.muwire.gui.BrowseController'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,6 +8,8 @@ import net.i2p.data.Base64
|
|||||||
|
|
||||||
import javax.annotation.Nonnull
|
import javax.annotation.Nonnull
|
||||||
|
|
||||||
|
import com.muwire.core.Core
|
||||||
|
import com.muwire.core.files.UICommentEvent
|
||||||
import com.muwire.core.util.DataUtil
|
import com.muwire.core.util.DataUtil
|
||||||
|
|
||||||
@ArtifactProviderFor(GriffonController)
|
@ArtifactProviderFor(GriffonController)
|
||||||
@@ -17,6 +19,8 @@ class AddCommentController {
|
|||||||
@MVCMember @Nonnull
|
@MVCMember @Nonnull
|
||||||
AddCommentView view
|
AddCommentView view
|
||||||
|
|
||||||
|
Core core
|
||||||
|
|
||||||
@ControllerAction
|
@ControllerAction
|
||||||
void save() {
|
void save() {
|
||||||
String comment = view.textarea.getText()
|
String comment = view.textarea.getText()
|
||||||
@@ -25,7 +29,9 @@ class AddCommentController {
|
|||||||
else
|
else
|
||||||
comment = Base64.encode(DataUtil.encodei18nString(comment))
|
comment = Base64.encode(DataUtil.encodei18nString(comment))
|
||||||
model.selectedFiles.each {
|
model.selectedFiles.each {
|
||||||
|
def event = new UICommentEvent(sharedFile : it, oldComment : it.getComment())
|
||||||
it.setComment(comment)
|
it.setComment(comment)
|
||||||
|
core.eventBus.publish(event)
|
||||||
}
|
}
|
||||||
mvcGroup.parentGroup.view.refreshSharedFiles()
|
mvcGroup.parentGroup.view.refreshSharedFiles()
|
||||||
cancel()
|
cancel()
|
||||||
|
@@ -0,0 +1,99 @@
|
|||||||
|
package com.muwire.gui
|
||||||
|
|
||||||
|
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.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
|
||||||
|
|
||||||
|
@ArtifactProviderFor(GriffonController)
|
||||||
|
class BrowseController {
|
||||||
|
@MVCMember @Nonnull
|
||||||
|
BrowseModel model
|
||||||
|
@MVCMember @Nonnull
|
||||||
|
BrowseView view
|
||||||
|
|
||||||
|
EventBus eventBus
|
||||||
|
|
||||||
|
|
||||||
|
void register() {
|
||||||
|
eventBus.register(BrowseStatusEvent.class, this)
|
||||||
|
eventBus.register(UIResultEvent.class, this)
|
||||||
|
eventBus.publish(new UIBrowseEvent(host : model.host))
|
||||||
|
}
|
||||||
|
|
||||||
|
void mvcGroupDestroy() {
|
||||||
|
eventBus.unregister(BrowseStatusEvent.class, this)
|
||||||
|
eventBus.unregister(UIResultEvent.class, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ControllerAction
|
||||||
|
void dismiss() {
|
||||||
|
view.dialog.setVisible(false)
|
||||||
|
mvcGroup.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
@ControllerAction
|
||||||
|
void download() {
|
||||||
|
def selectedResults = view.selectedResults()
|
||||||
|
if (selectedResults == null || selectedResults.isEmpty())
|
||||||
|
return
|
||||||
|
selectedResults.removeAll {
|
||||||
|
!mvcGroup.parentGroup.parentGroup.model.canDownload(it.infohash)
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedResults.each { result ->
|
||||||
|
def file = new File(application.context.get("muwire-settings").downloadLocation, result.name)
|
||||||
|
eventBus.publish(new UIDownloadEvent(
|
||||||
|
result : [result],
|
||||||
|
sources : [model.host.destination],
|
||||||
|
target : file,
|
||||||
|
sequential : mvcGroup.parentGroup.view.sequentialDownloadCheckbox.model.isSelected()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
mvcGroup.parentGroup.parentGroup.view.showDownloadsWindow.call()
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
@ControllerAction
|
||||||
|
void viewComment() {
|
||||||
|
def selectedResults = view.selectedResults()
|
||||||
|
if (selectedResults == null || selectedResults.size() != 1)
|
||||||
|
return
|
||||||
|
def result = selectedResults[0]
|
||||||
|
if (result.comment == null)
|
||||||
|
return
|
||||||
|
|
||||||
|
String groupId = Base64.encode(result.infohash.getRoot())
|
||||||
|
Map<String,Object> params = new HashMap<>()
|
||||||
|
params['result'] = result
|
||||||
|
|
||||||
|
mvcGroup.createMVCGroup("show-comment", groupId, params)
|
||||||
|
}
|
||||||
|
}
|
@@ -78,14 +78,15 @@ class MainFrameController {
|
|||||||
|
|
||||||
def searchEvent
|
def searchEvent
|
||||||
if (hashSearch) {
|
if (hashSearch) {
|
||||||
searchEvent = new SearchEvent(searchHash : root, uuid : uuid, oobInfohash: true)
|
searchEvent = new SearchEvent(searchHash : root, uuid : uuid, oobInfohash: true, compressedResults : true)
|
||||||
} else {
|
} else {
|
||||||
// this can be improved a lot
|
// this can be improved a lot
|
||||||
def replaced = search.toLowerCase().trim().replaceAll(SplitPattern.SPLIT_PATTERN, " ")
|
def replaced = search.toLowerCase().trim().replaceAll(SplitPattern.SPLIT_PATTERN, " ")
|
||||||
def terms = replaced.split(" ")
|
def terms = replaced.split(" ")
|
||||||
def nonEmpty = []
|
def nonEmpty = []
|
||||||
terms.each { if (it.length() > 0) nonEmpty << it }
|
terms.each { if (it.length() > 0) nonEmpty << it }
|
||||||
searchEvent = new SearchEvent(searchTerms : nonEmpty, uuid : uuid, oobInfohash: true)
|
searchEvent = new SearchEvent(searchTerms : nonEmpty, uuid : uuid, oobInfohash: true,
|
||||||
|
searchComments : core.muOptions.searchComments, compressedResults : true)
|
||||||
}
|
}
|
||||||
boolean firstHop = core.muOptions.allowUntrusted || core.muOptions.searchExtraHop
|
boolean firstHop = core.muOptions.allowUntrusted || core.muOptions.searchExtraHop
|
||||||
core.eventBus.publish(new QueryEvent(searchEvent : searchEvent, firstHop : firstHop,
|
core.eventBus.publish(new QueryEvent(searchEvent : searchEvent, firstHop : firstHop,
|
||||||
@@ -288,6 +289,7 @@ class MainFrameController {
|
|||||||
|
|
||||||
Map<String, Object> params = new HashMap<>()
|
Map<String, Object> params = new HashMap<>()
|
||||||
params['selectedFiles'] = selectedFiles
|
params['selectedFiles'] = selectedFiles
|
||||||
|
params['core'] = core
|
||||||
mvcGroup.createMVCGroup("add-comment", "Add Comment", params)
|
mvcGroup.createMVCGroup("add-comment", "Add Comment", params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -31,6 +31,9 @@ class MuWireStatusController {
|
|||||||
model.outgoingConnections = outgoing
|
model.outgoingConnections = outgoing
|
||||||
|
|
||||||
model.knownHosts = core.hostCache.hosts.size()
|
model.knownHosts = core.hostCache.hosts.size()
|
||||||
|
model.failingHosts = core.hostCache.countFailingHosts()
|
||||||
|
model.hopelessHosts = core.hostCache.countHopelessHosts()
|
||||||
|
|
||||||
|
|
||||||
model.sharedFiles = core.fileManager.fileToSharedFile.size()
|
model.sharedFiles = core.fileManager.fileToSharedFile.size()
|
||||||
|
|
||||||
|
@@ -70,6 +70,10 @@ class OptionsController {
|
|||||||
model.updateCheckInterval = text
|
model.updateCheckInterval = text
|
||||||
settings.updateCheckInterval = Integer.valueOf(text)
|
settings.updateCheckInterval = Integer.valueOf(text)
|
||||||
|
|
||||||
|
boolean searchComments = view.searchCommentsCheckbox.model.isSelected()
|
||||||
|
model.searchComments = searchComments
|
||||||
|
settings.searchComments = searchComments
|
||||||
|
|
||||||
boolean autoDownloadUpdate = view.autoDownloadUpdateCheckbox.model.isSelected()
|
boolean autoDownloadUpdate = view.autoDownloadUpdateCheckbox.model.isSelected()
|
||||||
model.autoDownloadUpdate = autoDownloadUpdate
|
model.autoDownloadUpdate = autoDownloadUpdate
|
||||||
settings.autoDownloadUpdate = autoDownloadUpdate
|
settings.autoDownloadUpdate = autoDownloadUpdate
|
||||||
@@ -79,9 +83,24 @@ class OptionsController {
|
|||||||
model.shareDownloadedFiles = shareDownloaded
|
model.shareDownloadedFiles = shareDownloaded
|
||||||
settings.shareDownloadedFiles = shareDownloaded
|
settings.shareDownloadedFiles = shareDownloaded
|
||||||
|
|
||||||
|
boolean shareHidden = view.shareHiddenCheckbox.model.isSelected()
|
||||||
|
model.shareHiddenFiles = shareHidden
|
||||||
|
settings.shareHiddenFiles = shareHidden
|
||||||
|
|
||||||
|
boolean browseFiles = view.browseFilesCheckbox.model.isSelected()
|
||||||
|
model.browseFiles = browseFiles
|
||||||
|
settings.browseFiles = browseFiles
|
||||||
|
|
||||||
|
text = view.speedSmoothSecondsField.text
|
||||||
|
model.speedSmoothSeconds = Integer.valueOf(text)
|
||||||
|
settings.speedSmoothSeconds = Integer.valueOf(text)
|
||||||
|
|
||||||
String downloadLocation = model.downloadLocation
|
String downloadLocation = model.downloadLocation
|
||||||
settings.downloadLocation = new File(downloadLocation)
|
settings.downloadLocation = new File(downloadLocation)
|
||||||
|
|
||||||
|
String incompleteLocation = model.incompleteLocation
|
||||||
|
settings.incompleteLocation = new File(incompleteLocation)
|
||||||
|
|
||||||
if (settings.embeddedRouter) {
|
if (settings.embeddedRouter) {
|
||||||
text = view.inBwField.text
|
text = view.inBwField.text
|
||||||
model.inBw = text
|
model.inBw = text
|
||||||
@@ -124,9 +143,8 @@ class OptionsController {
|
|||||||
model.font = text
|
model.font = text
|
||||||
uiSettings.font = text
|
uiSettings.font = text
|
||||||
|
|
||||||
// boolean showMonitor = view.monitorCheckbox.model.isSelected()
|
uiSettings.autoFontSize = model.automaticFontSize
|
||||||
// model.showMonitor = showMonitor
|
uiSettings.fontSize = Integer.parseInt(view.fontSizeField.text)
|
||||||
// uiSettings.showMonitor = showMonitor
|
|
||||||
|
|
||||||
boolean clearCancelledDownloads = view.clearCancelledDownloadsCheckbox.model.isSelected()
|
boolean clearCancelledDownloads = view.clearCancelledDownloadsCheckbox.model.isSelected()
|
||||||
model.clearCancelledDownloads = clearCancelledDownloads
|
model.clearCancelledDownloads = clearCancelledDownloads
|
||||||
@@ -140,10 +158,6 @@ class OptionsController {
|
|||||||
model.excludeLocalResult = excludeLocalResult
|
model.excludeLocalResult = excludeLocalResult
|
||||||
uiSettings.excludeLocalResult = excludeLocalResult
|
uiSettings.excludeLocalResult = excludeLocalResult
|
||||||
|
|
||||||
// boolean showSearchHashes = view.showSearchHashesCheckbox.model.isSelected()
|
|
||||||
// model.showSearchHashes = showSearchHashes
|
|
||||||
// uiSettings.showSearchHashes = showSearchHashes
|
|
||||||
|
|
||||||
File uiSettingsFile = new File(core.home, "gui.properties")
|
File uiSettingsFile = new File(core.home, "gui.properties")
|
||||||
uiSettingsFile.withOutputStream {
|
uiSettingsFile.withOutputStream {
|
||||||
uiSettings.write(it)
|
uiSettings.write(it)
|
||||||
@@ -168,4 +182,26 @@ class OptionsController {
|
|||||||
if (rv == JFileChooser.APPROVE_OPTION)
|
if (rv == JFileChooser.APPROVE_OPTION)
|
||||||
model.downloadLocation = chooser.getSelectedFile().getAbsolutePath()
|
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() {
|
||||||
|
model.automaticFontSize = true
|
||||||
|
model.customFontSize = 12
|
||||||
|
}
|
||||||
|
|
||||||
|
@ControllerAction
|
||||||
|
void customFontAction() {
|
||||||
|
model.automaticFontSize = false
|
||||||
|
}
|
||||||
}
|
}
|
@@ -4,9 +4,12 @@ import griffon.core.artifact.GriffonController
|
|||||||
import griffon.core.controller.ControllerAction
|
import griffon.core.controller.ControllerAction
|
||||||
import griffon.inject.MVCMember
|
import griffon.inject.MVCMember
|
||||||
import griffon.metadata.ArtifactProviderFor
|
import griffon.metadata.ArtifactProviderFor
|
||||||
|
import net.i2p.data.Base64
|
||||||
|
|
||||||
import javax.annotation.Nonnull
|
import javax.annotation.Nonnull
|
||||||
|
|
||||||
import com.muwire.core.Core
|
import com.muwire.core.Core
|
||||||
|
import com.muwire.core.Persona
|
||||||
import com.muwire.core.download.UIDownloadEvent
|
import com.muwire.core.download.UIDownloadEvent
|
||||||
import com.muwire.core.search.UIResultEvent
|
import com.muwire.core.search.UIResultEvent
|
||||||
import com.muwire.core.trust.TrustEvent
|
import com.muwire.core.trust.TrustEvent
|
||||||
@@ -85,4 +88,37 @@ class SearchTabController {
|
|||||||
def sender = model.senders[row]
|
def sender = model.senders[row]
|
||||||
core.eventBus.publish( new TrustEvent(persona : sender, level : TrustLevel.NEUTRAL))
|
core.eventBus.publish( new TrustEvent(persona : sender, level : TrustLevel.NEUTRAL))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ControllerAction
|
||||||
|
void browse() {
|
||||||
|
int selectedSender = view.selectedSenderRow()
|
||||||
|
if (selectedSender < 0)
|
||||||
|
return
|
||||||
|
Persona sender = model.senders[selectedSender]
|
||||||
|
|
||||||
|
String groupId = sender.getHumanReadableName()
|
||||||
|
Map<String,Object> params = new HashMap<>()
|
||||||
|
params['host'] = sender
|
||||||
|
params['eventBus'] = core.eventBus
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
@@ -10,15 +10,19 @@ import com.muwire.gui.UISettings
|
|||||||
|
|
||||||
import javax.annotation.Nonnull
|
import javax.annotation.Nonnull
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.swing.JLabel
|
||||||
import javax.swing.JTable
|
import javax.swing.JTable
|
||||||
import javax.swing.LookAndFeel
|
import javax.swing.LookAndFeel
|
||||||
import javax.swing.UIManager
|
import javax.swing.UIManager
|
||||||
|
import javax.swing.plaf.FontUIResource
|
||||||
|
|
||||||
import static griffon.util.GriffonApplicationUtils.isMacOSX
|
import static griffon.util.GriffonApplicationUtils.isMacOSX
|
||||||
import static groovy.swing.SwingBuilder.lookAndFeel
|
import static groovy.swing.SwingBuilder.lookAndFeel
|
||||||
|
|
||||||
import java.awt.Font
|
import java.awt.Font
|
||||||
|
import java.awt.Toolkit
|
||||||
import java.util.logging.Level
|
import java.util.logging.Level
|
||||||
|
import java.util.logging.LogManager
|
||||||
|
|
||||||
@Log
|
@Log
|
||||||
class Initialize extends AbstractLifecycleHandler {
|
class Initialize extends AbstractLifecycleHandler {
|
||||||
@@ -29,6 +33,16 @@ class Initialize extends AbstractLifecycleHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
void execute() {
|
void execute() {
|
||||||
|
|
||||||
|
if (System.getProperty("java.util.logging.config.file") == null) {
|
||||||
|
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.SEVERE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log.info "Loading home dir"
|
log.info "Loading home dir"
|
||||||
def portableHome = System.getProperty("portable.home")
|
def portableHome = System.getProperty("portable.home")
|
||||||
def home = portableHome == null ?
|
def home = portableHome == null ?
|
||||||
@@ -52,25 +66,43 @@ class Initialize extends AbstractLifecycleHandler {
|
|||||||
guiPropsFile.withInputStream { props.load(it) }
|
guiPropsFile.withInputStream { props.load(it) }
|
||||||
uiSettings = new UISettings(props)
|
uiSettings = new UISettings(props)
|
||||||
|
|
||||||
|
def lnf
|
||||||
log.info("settting user-specified lnf $uiSettings.lnf")
|
log.info("settting user-specified lnf $uiSettings.lnf")
|
||||||
try {
|
try {
|
||||||
lookAndFeel(uiSettings.lnf)
|
lnf = lookAndFeel(uiSettings.lnf)
|
||||||
} catch (Throwable bad) {
|
} catch (Throwable bad) {
|
||||||
log.log(Level.WARNING,"couldn't set desired look and feeel, switching to defaults", bad)
|
log.log(Level.WARNING,"couldn't set desired look and feel, switching to defaults", bad)
|
||||||
uiSettings.lnf = lookAndFeel("system","gtk","metal").getID()
|
lnf = lookAndFeel("system","gtk","metal")
|
||||||
|
uiSettings.lnf = lnf.getID()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uiSettings.font != null) {
|
if (uiSettings.font != null || uiSettings.autoFontSize || uiSettings.fontSize > 0) {
|
||||||
log.info("setting user-specified font $uiSettings.font")
|
|
||||||
Font font = new Font(uiSettings.font, Font.PLAIN, 12)
|
FontUIResource defaultFont = lnf.getDefaults().getFont("Label.font")
|
||||||
def defaults = UIManager.getDefaults()
|
|
||||||
defaults.put("Button.font", font)
|
String fontName
|
||||||
defaults.put("RadioButton.font", font)
|
if (uiSettings.font != null)
|
||||||
defaults.put("Label.font", font)
|
fontName = uiSettings.font
|
||||||
defaults.put("CheckBox.font", font)
|
else
|
||||||
defaults.put("Table.font", font)
|
fontName = defaultFont.getName()
|
||||||
defaults.put("TableHeader.font", font)
|
|
||||||
// TODO: add others
|
int fontSize = defaultFont.getSize()
|
||||||
|
if (uiSettings.autoFontSize) {
|
||||||
|
int resolution = Toolkit.getDefaultToolkit().getScreenResolution()
|
||||||
|
fontSize = resolution / 9;
|
||||||
|
} else {
|
||||||
|
fontSize = uiSettings.fontSize
|
||||||
|
}
|
||||||
|
|
||||||
|
FontUIResource font = new FontUIResource(fontName, Font.PLAIN, fontSize)
|
||||||
|
|
||||||
|
def keys = lnf.getDefaults().keys()
|
||||||
|
while(keys.hasMoreElements()) {
|
||||||
|
def key = keys.nextElement()
|
||||||
|
def value = lnf.getDefaults().get(key)
|
||||||
|
if (value instanceof FontUIResource)
|
||||||
|
lnf.getDefaults().put(key, font)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Properties props = new Properties()
|
Properties props = new Properties()
|
||||||
|
@@ -45,9 +45,12 @@ class Ready extends AbstractLifecycleHandler {
|
|||||||
props.load(it)
|
props.load(it)
|
||||||
}
|
}
|
||||||
props = new MuWireSettings(props)
|
props = new MuWireSettings(props)
|
||||||
|
if (props.incompleteLocation == null)
|
||||||
|
props.incompleteLocation = new File(home, "incompletes")
|
||||||
} else {
|
} else {
|
||||||
log.info("creating new properties")
|
log.info("creating new properties")
|
||||||
props = new MuWireSettings()
|
props = new MuWireSettings()
|
||||||
|
props.incompleteLocation = new File(home, "incompletes")
|
||||||
props.embeddedRouter = Boolean.parseBoolean(System.getProperties().getProperty("embeddedRouter"))
|
props.embeddedRouter = Boolean.parseBoolean(System.getProperties().getProperty("embeddedRouter"))
|
||||||
props.updateType = System.getProperty("updateType","jar")
|
props.updateType = System.getProperty("updateType","jar")
|
||||||
def nickname
|
def nickname
|
||||||
|
21
gui/griffon-app/models/com/muwire/gui/BrowseModel.groovy
Normal file
21
gui/griffon-app/models/com/muwire/gui/BrowseModel.groovy
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package com.muwire.gui
|
||||||
|
|
||||||
|
import com.muwire.core.Persona
|
||||||
|
|
||||||
|
import griffon.core.artifact.GriffonModel
|
||||||
|
import griffon.transform.Observable
|
||||||
|
import griffon.metadata.ArtifactProviderFor
|
||||||
|
|
||||||
|
import com.muwire.core.search.BrowseStatus
|
||||||
|
|
||||||
|
@ArtifactProviderFor(GriffonModel)
|
||||||
|
class BrowseModel {
|
||||||
|
Persona host
|
||||||
|
@Observable BrowseStatus status
|
||||||
|
@Observable boolean downloadActionEnabled
|
||||||
|
@Observable boolean viewCommentActionEnabled
|
||||||
|
@Observable int totalResults
|
||||||
|
@Observable int resultCount
|
||||||
|
|
||||||
|
def results = []
|
||||||
|
}
|
@@ -181,7 +181,6 @@ class MainFrameModel {
|
|||||||
core = e.getNewValue()
|
core = e.getNewValue()
|
||||||
routerPresent = core.router != null
|
routerPresent = core.router != null
|
||||||
me = core.me.getHumanReadableName()
|
me = core.me.getHumanReadableName()
|
||||||
core.eventBus.register(UIResultEvent.class, this)
|
|
||||||
core.eventBus.register(UIResultBatchEvent.class, this)
|
core.eventBus.register(UIResultBatchEvent.class, this)
|
||||||
core.eventBus.register(DownloadStartedEvent.class, this)
|
core.eventBus.register(DownloadStartedEvent.class, this)
|
||||||
core.eventBus.register(ConnectionEvent.class, this)
|
core.eventBus.register(ConnectionEvent.class, this)
|
||||||
|
@@ -16,6 +16,8 @@ class MuWireStatusModel {
|
|||||||
@Observable int incomingConnections
|
@Observable int incomingConnections
|
||||||
@Observable int outgoingConnections
|
@Observable int outgoingConnections
|
||||||
@Observable int knownHosts
|
@Observable int knownHosts
|
||||||
|
@Observable int failingHosts
|
||||||
|
@Observable int hopelessHosts
|
||||||
@Observable int sharedFiles
|
@Observable int sharedFiles
|
||||||
@Observable int downloads
|
@Observable int downloads
|
||||||
|
|
||||||
|
@@ -13,7 +13,12 @@ class OptionsModel {
|
|||||||
@Observable String updateCheckInterval
|
@Observable String updateCheckInterval
|
||||||
@Observable boolean autoDownloadUpdate
|
@Observable boolean autoDownloadUpdate
|
||||||
@Observable boolean shareDownloadedFiles
|
@Observable boolean shareDownloadedFiles
|
||||||
|
@Observable boolean shareHiddenFiles
|
||||||
@Observable String downloadLocation
|
@Observable String downloadLocation
|
||||||
|
@Observable String incompleteLocation
|
||||||
|
@Observable boolean searchComments
|
||||||
|
@Observable boolean browseFiles
|
||||||
|
@Observable int speedSmoothSeconds
|
||||||
|
|
||||||
// i2p options
|
// i2p options
|
||||||
@Observable String inboundLength
|
@Observable String inboundLength
|
||||||
@@ -27,6 +32,8 @@ class OptionsModel {
|
|||||||
@Observable boolean showMonitor
|
@Observable boolean showMonitor
|
||||||
@Observable String lnf
|
@Observable String lnf
|
||||||
@Observable String font
|
@Observable String font
|
||||||
|
@Observable boolean automaticFontSize
|
||||||
|
@Observable int customFontSize
|
||||||
@Observable boolean clearCancelledDownloads
|
@Observable boolean clearCancelledDownloads
|
||||||
@Observable boolean clearFinishedDownloads
|
@Observable boolean clearFinishedDownloads
|
||||||
@Observable boolean excludeLocalResult
|
@Observable boolean excludeLocalResult
|
||||||
@@ -49,7 +56,12 @@ class OptionsModel {
|
|||||||
updateCheckInterval = settings.updateCheckInterval
|
updateCheckInterval = settings.updateCheckInterval
|
||||||
autoDownloadUpdate = settings.autoDownloadUpdate
|
autoDownloadUpdate = settings.autoDownloadUpdate
|
||||||
shareDownloadedFiles = settings.shareDownloadedFiles
|
shareDownloadedFiles = settings.shareDownloadedFiles
|
||||||
|
shareHiddenFiles = settings.shareHiddenFiles
|
||||||
downloadLocation = settings.downloadLocation.getAbsolutePath()
|
downloadLocation = settings.downloadLocation.getAbsolutePath()
|
||||||
|
incompleteLocation = settings.incompleteLocation.getAbsolutePath()
|
||||||
|
searchComments = settings.searchComments
|
||||||
|
browseFiles = settings.browseFiles
|
||||||
|
speedSmoothSeconds = settings.speedSmoothSeconds
|
||||||
|
|
||||||
Core core = application.context.get("core")
|
Core core = application.context.get("core")
|
||||||
inboundLength = core.i2pOptions["inbound.length"]
|
inboundLength = core.i2pOptions["inbound.length"]
|
||||||
@@ -63,6 +75,8 @@ class OptionsModel {
|
|||||||
showMonitor = uiSettings.showMonitor
|
showMonitor = uiSettings.showMonitor
|
||||||
lnf = uiSettings.lnf
|
lnf = uiSettings.lnf
|
||||||
font = uiSettings.font
|
font = uiSettings.font
|
||||||
|
automaticFontSize = uiSettings.autoFontSize
|
||||||
|
customFontSize = uiSettings.fontSize
|
||||||
clearCancelledDownloads = uiSettings.clearCancelledDownloads
|
clearCancelledDownloads = uiSettings.clearCancelledDownloads
|
||||||
clearFinishedDownloads = uiSettings.clearFinishedDownloads
|
clearFinishedDownloads = uiSettings.clearFinishedDownloads
|
||||||
excludeLocalResult = uiSettings.excludeLocalResult
|
excludeLocalResult = uiSettings.excludeLocalResult
|
||||||
|
@@ -21,6 +21,8 @@ class SearchTabModel {
|
|||||||
|
|
||||||
@Observable boolean downloadActionEnabled
|
@Observable boolean downloadActionEnabled
|
||||||
@Observable boolean trustButtonsEnabled
|
@Observable boolean trustButtonsEnabled
|
||||||
|
@Observable boolean browseActionEnabled
|
||||||
|
@Observable boolean viewCommentActionEnabled
|
||||||
|
|
||||||
Core core
|
Core core
|
||||||
UISettings uiSettings
|
UISettings uiSettings
|
||||||
|
201
gui/griffon-app/views/com/muwire/gui/BrowseView.groovy
Normal file
201
gui/griffon-app/views/com/muwire/gui/BrowseView.groovy
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull
|
||||||
|
|
||||||
|
@ArtifactProviderFor(GriffonView)
|
||||||
|
class BrowseView {
|
||||||
|
@MVCMember @Nonnull
|
||||||
|
FactoryBuilderSupport builder
|
||||||
|
@MVCMember @Nonnull
|
||||||
|
BrowseModel model
|
||||||
|
@MVCMember @Nonnull
|
||||||
|
BrowseController controller
|
||||||
|
|
||||||
|
def mainFrame
|
||||||
|
def dialog
|
||||||
|
def p
|
||||||
|
def resultsTable
|
||||||
|
def lastSortEvent
|
||||||
|
void initUI() {
|
||||||
|
mainFrame = application.windowManager.findWindow("main-frame")
|
||||||
|
dialog = new JDialog(mainFrame, model.host.getHumanReadableName(), true)
|
||||||
|
dialog.setResizable(true)
|
||||||
|
|
||||||
|
p = builder.panel {
|
||||||
|
borderLayout()
|
||||||
|
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) {
|
||||||
|
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})
|
||||||
|
closureColumn(header: "Comments", preferredWidth: 20, type: Boolean, read : {row -> row.comment != null})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panel (constraints : BorderLayout.SOUTH) {
|
||||||
|
button(text : "Download", enabled : bind {model.downloadActionEnabled}, downloadAction)
|
||||||
|
button(text : "View Comment", enabled : bind{model.viewCommentActionEnabled}, viewCommentAction)
|
||||||
|
button(text : "Dismiss", dismissAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def centerRenderer = new DefaultTableCellRenderer()
|
||||||
|
centerRenderer.setHorizontalAlignment(JLabel.CENTER)
|
||||||
|
resultsTable.setDefaultRenderer(Integer.class,centerRenderer)
|
||||||
|
|
||||||
|
resultsTable.columnModel.getColumn(1).setCellRenderer(new SizeRenderer())
|
||||||
|
|
||||||
|
|
||||||
|
resultsTable.rowSorter.addRowSorterListener({evt -> lastSortEvent = evt})
|
||||||
|
resultsTable.rowSorter.setSortsOnUpdates(true)
|
||||||
|
def selectionModel = resultsTable.getSelectionModel()
|
||||||
|
selectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
|
||||||
|
selectionModel.addListSelectionListener({
|
||||||
|
int[] rows = resultsTable.getSelectedRows()
|
||||||
|
if (rows.length == 0) {
|
||||||
|
model.downloadActionEnabled = false
|
||||||
|
model.viewCommentActionEnabled = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastSortEvent != null) {
|
||||||
|
for (int i = 0; i < rows.length; i ++) {
|
||||||
|
rows[i] = resultsTable.rowSorter.convertRowIndexToModel(rows[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean downloadActionEnabled = true
|
||||||
|
if (rows.length == 1 && model.results[rows[0]].comment != null)
|
||||||
|
model.viewCommentActionEnabled = true
|
||||||
|
else
|
||||||
|
model.viewCommentActionEnabled = false
|
||||||
|
|
||||||
|
rows.each {
|
||||||
|
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()
|
||||||
|
|
||||||
|
dialog.getContentPane().add(p)
|
||||||
|
dialog.setSize(700, 400)
|
||||||
|
dialog.setLocationRelativeTo(mainFrame)
|
||||||
|
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
|
||||||
|
dialog.addWindowListener( new WindowAdapter() {
|
||||||
|
public void windowClosed(WindowEvent e) {
|
||||||
|
mvcGroup.destroy()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
dialog.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def selectedResults() {
|
||||||
|
int [] rows = resultsTable.getSelectedRows()
|
||||||
|
if (rows.length == 0)
|
||||||
|
return null
|
||||||
|
if (lastSortEvent != null) {
|
||||||
|
for (int i = 0; i < rows.length; i ++) {
|
||||||
|
rows[i] = resultsTable.rowSorter.convertRowIndexToModel(rows[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<UIResultEvent> rv = new ArrayList<>()
|
||||||
|
for (Integer i : rows)
|
||||||
|
rv << model.results[i]
|
||||||
|
rv
|
||||||
|
}
|
||||||
|
}
|
@@ -7,8 +7,10 @@ import griffon.metadata.ArtifactProviderFor
|
|||||||
import javax.swing.JDialog
|
import javax.swing.JDialog
|
||||||
import javax.swing.JPanel
|
import javax.swing.JPanel
|
||||||
import javax.swing.SwingConstants
|
import javax.swing.SwingConstants
|
||||||
|
import javax.swing.border.TitledBorder
|
||||||
|
|
||||||
import java.awt.BorderLayout
|
import java.awt.BorderLayout
|
||||||
|
import java.awt.GridBagConstraints
|
||||||
import java.awt.event.WindowAdapter
|
import java.awt.event.WindowAdapter
|
||||||
import java.awt.event.WindowEvent
|
import java.awt.event.WindowEvent
|
||||||
|
|
||||||
@@ -33,24 +35,40 @@ class I2PStatusView {
|
|||||||
|
|
||||||
panel = builder.panel {
|
panel = builder.panel {
|
||||||
gridBagLayout()
|
gridBagLayout()
|
||||||
label(text : "Network status", constraints : gbc(gridx:0, gridy:0))
|
panel(border : titledBorder(title : "General", border : etchedBorder(), titlePosition : TitledBorder.TOP),
|
||||||
label(text : bind {model.networkStatus}, constraints : gbc(gridx: 1, gridy:0))
|
constraints : gbc(gridx: 0, gridy : 0, fill : GridBagConstraints.HORIZONTAL, weightx: 100)) {
|
||||||
label(text: "Floodfill", constraints : gbc(gridx: 0, gridy : 1))
|
gridBagLayout()
|
||||||
label(text : bind {model.floodfill}, constraints : gbc(gridx:1, gridy:1))
|
label(text : "Network status", constraints : gbc(gridx:0, gridy:0, anchor : GridBagConstraints.LINE_START, weightx: 100))
|
||||||
label(text : "NTCP Connections", constraints : gbc(gridx:0, gridy:2))
|
label(text : bind {model.networkStatus}, constraints : gbc(gridx: 1, gridy:0, anchor : GridBagConstraints.LINE_END))
|
||||||
label(text : bind {model.ntcpConnections}, constraints : gbc(gridx: 1, gridy:2))
|
label(text: "Floodfill", constraints : gbc(gridx: 0, gridy : 1, anchor: GridBagConstraints.LINE_START, weightx: 100))
|
||||||
label(text : "SSU Connections", constraints : gbc(gridx:0, gridy:3))
|
label(text : bind {model.floodfill}, constraints : gbc(gridx:1, gridy:1, anchor : GridBagConstraints.LINE_END))
|
||||||
label(text : bind {model.ssuConnections}, constraints : gbc(gridx: 1, gridy:3))
|
label(text : "Active Peers", constraints : gbc(gridx:0, gridy:2, anchor : GridBagConstraints.LINE_START, weightx: 100))
|
||||||
label(text : "Participating Tunnels", constraints : gbc(gridx:0, gridy:4))
|
label(text : bind {model.activePeers}, constraints : gbc(gridx: 1, gridy:2, anchor : GridBagConstraints.LINE_END))
|
||||||
label(text : bind {model.participatingTunnels}, constraints : gbc(gridx: 1, gridy:4))
|
}
|
||||||
label(text : "Participating Bandwidth", constraints : gbc(gridx:0, gridy:5))
|
panel(border : titledBorder(title : "Connections", border : etchedBorder(), titlePosition : TitledBorder.TOP),
|
||||||
label(text : bind {model.participatingBW}, constraints : gbc(gridx: 1, gridy:5))
|
constraints : gbc(gridx: 0, gridy: 1, fill : GridBagConstraints.HORIZONTAL, weightx: 100)) {
|
||||||
label(text : "Active Peers", constraints : gbc(gridx:0, gridy:6))
|
gridBagLayout()
|
||||||
label(text : bind {model.activePeers}, constraints : gbc(gridx: 1, gridy:6))
|
label(text : "NTCP", constraints : gbc(gridx:0, gridy:0, anchor: GridBagConstraints.LINE_START, weightx: 100))
|
||||||
label(text : "Receive Bps (15 seconds)", constraints : gbc(gridx:0, gridy:7))
|
label(text : bind {model.ntcpConnections}, constraints : gbc(gridx: 1, gridy:0, anchor : GridBagConstraints.LINE_END))
|
||||||
label(text : bind {model.receiveBps}, constraints : gbc(gridx: 1, gridy:7))
|
label(text : "SSU", constraints : gbc(gridx:0, gridy:1, anchor: GridBagConstraints.LINE_START, weightx: 100))
|
||||||
label(text : "Send Bps (15 seconds)", constraints : gbc(gridx:0, gridy:8))
|
label(text : bind {model.ssuConnections}, constraints : gbc(gridx: 1, gridy:1, anchor : GridBagConstraints.LINE_END))
|
||||||
label(text : bind {model.sendBps}, constraints : gbc(gridx: 1, gridy:8))
|
}
|
||||||
|
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 {
|
buttonsPanel = builder.panel {
|
||||||
|
@@ -141,6 +141,15 @@ class MainFrameView {
|
|||||||
closureColumn(header: "Speed", preferredWidth: 50, type:String, read :{row ->
|
closureColumn(header: "Speed", preferredWidth: 50, type:String, read :{row ->
|
||||||
DataHelper.formatSize2Decimal(row.downloader.speed(), false) + "B/sec"
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -217,7 +226,7 @@ class MainFrameView {
|
|||||||
scrollPane(constraints : BorderLayout.CENTER) {
|
scrollPane(constraints : BorderLayout.CENTER) {
|
||||||
def jtree = new JTree(model.sharedTree)
|
def jtree = new JTree(model.sharedTree)
|
||||||
jtree.setCellRenderer(new SharedTreeRenderer())
|
jtree.setCellRenderer(new SharedTreeRenderer())
|
||||||
tree(id : "shared-files-tree", rootVisible : false, jtree)
|
tree(id : "shared-files-tree", rootVisible : false, expandsSelectedPaths: true, jtree)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -717,9 +726,12 @@ class MainFrameView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int selectedDownloaderRow() {
|
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)
|
if (lastDownloadSortEvent != null)
|
||||||
selected = lastDownloadSortEvent.convertPreviousRowIndexToModel(selected)
|
selected = downloadsTable.rowSorter.convertRowIndexToModel(selected)
|
||||||
selected
|
selected
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -852,7 +864,7 @@ class MainFrameView {
|
|||||||
|
|
||||||
def shareFiles = {
|
def shareFiles = {
|
||||||
def chooser = new JFileChooser()
|
def chooser = new JFileChooser()
|
||||||
chooser.setFileHidingEnabled(false)
|
chooser.setFileHidingEnabled(!model.core.muOptions.shareHiddenFiles)
|
||||||
chooser.setDialogTitle("Select file to share")
|
chooser.setDialogTitle("Select file to share")
|
||||||
chooser.setFileSelectionMode(JFileChooser.FILES_ONLY)
|
chooser.setFileSelectionMode(JFileChooser.FILES_ONLY)
|
||||||
chooser.setMultiSelectionEnabled(true)
|
chooser.setMultiSelectionEnabled(true)
|
||||||
@@ -876,7 +888,10 @@ class MainFrameView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void refreshSharedFiles() {
|
public void refreshSharedFiles() {
|
||||||
|
def tree = builder.getVariable("shared-files-tree")
|
||||||
|
TreePath[] selectedPaths = tree.getSelectionPaths()
|
||||||
model.sharedTree.nodeStructureChanged(model.treeRoot)
|
model.sharedTree.nodeStructureChanged(model.treeRoot)
|
||||||
|
tree.setSelectionPaths(selectedPaths)
|
||||||
builder.getVariable("shared-files-table").model.fireTableDataChanged()
|
builder.getVariable("shared-files-table").model.fireTableDataChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -7,10 +7,12 @@ import griffon.metadata.ArtifactProviderFor
|
|||||||
import javax.swing.JDialog
|
import javax.swing.JDialog
|
||||||
import javax.swing.JPanel
|
import javax.swing.JPanel
|
||||||
import javax.swing.SwingConstants
|
import javax.swing.SwingConstants
|
||||||
|
import javax.swing.border.TitledBorder
|
||||||
|
|
||||||
import com.muwire.core.Core
|
import com.muwire.core.Core
|
||||||
|
|
||||||
import java.awt.BorderLayout
|
import java.awt.BorderLayout
|
||||||
|
import java.awt.GridBagConstraints
|
||||||
import java.awt.event.WindowAdapter
|
import java.awt.event.WindowAdapter
|
||||||
import java.awt.event.WindowEvent
|
import java.awt.event.WindowEvent
|
||||||
|
|
||||||
@@ -35,16 +37,32 @@ class MuWireStatusView {
|
|||||||
|
|
||||||
panel = builder.panel {
|
panel = builder.panel {
|
||||||
gridBagLayout()
|
gridBagLayout()
|
||||||
label(text : "Incoming connections", constraints : gbc(gridx:0, gridy:0))
|
panel(border : titledBorder(title : "Connections", border : etchedBorder(), titlePosition : TitledBorder.TOP),
|
||||||
label(text : bind {model.incomingConnections}, constraints : gbc(gridx:1, gridy:0))
|
constraints : gbc(gridx : 0, gridy: 0, fill : GridBagConstraints.HORIZONTAL, weightx: 100)) {
|
||||||
label(text : "Outgoing connections", constraints : gbc(gridx:0, gridy:1))
|
gridBagLayout()
|
||||||
label(text : bind {model.outgoingConnections}, constraints : gbc(gridx:1, gridy:1))
|
label(text : "Incoming", constraints : gbc(gridx:0, gridy:0, anchor : GridBagConstraints.LINE_START, weightx : 100))
|
||||||
label(text : "Known hosts", constraints : gbc(gridx:0, gridy:2))
|
label(text : bind {model.incomingConnections}, constraints : gbc(gridx:1, gridy:0, anchor : GridBagConstraints.LINE_END))
|
||||||
label(text : bind {model.knownHosts}, constraints : gbc(gridx:1, gridy:2))
|
label(text : "Outgoing", constraints : gbc(gridx:0, gridy:1, anchor : GridBagConstraints.LINE_START, weightx : 100))
|
||||||
label(text : "Shared files", constraints : gbc(gridx:0, gridy:3))
|
label(text : bind {model.outgoingConnections}, constraints : gbc(gridx:1, gridy:1, anchor : GridBagConstraints.LINE_END))
|
||||||
label(text : bind {model.sharedFiles}, constraints : gbc(gridx:1, gridy:3))
|
}
|
||||||
label(text : "Downloads", constraints : gbc(gridx:0, gridy:4))
|
panel(border : titledBorder(title : "Hosts", border : etchedBorder(), titlePosition : TitledBorder.TOP),
|
||||||
label(text : bind {model.downloads}, constraints : gbc(gridx:1, gridy:4))
|
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 {
|
buttonsPanel = builder.panel {
|
||||||
gridBagLayout()
|
gridBagLayout()
|
||||||
@@ -60,7 +78,8 @@ class MuWireStatusView {
|
|||||||
statusPanel.add(buttonsPanel, BorderLayout.SOUTH)
|
statusPanel.add(buttonsPanel, BorderLayout.SOUTH)
|
||||||
|
|
||||||
dialog.getContentPane().add(statusPanel)
|
dialog.getContentPane().add(statusPanel)
|
||||||
dialog.pack()
|
dialog.setSize(200,300)
|
||||||
|
dialog.setResizable(false)
|
||||||
dialog.setLocationRelativeTo(mainFrame)
|
dialog.setLocationRelativeTo(mainFrame)
|
||||||
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
|
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
|
||||||
dialog.addWindowListener(new WindowAdapter() {
|
dialog.addWindowListener(new WindowAdapter() {
|
||||||
|
@@ -3,15 +3,18 @@ package com.muwire.gui
|
|||||||
import griffon.core.artifact.GriffonView
|
import griffon.core.artifact.GriffonView
|
||||||
import griffon.inject.MVCMember
|
import griffon.inject.MVCMember
|
||||||
import griffon.metadata.ArtifactProviderFor
|
import griffon.metadata.ArtifactProviderFor
|
||||||
|
import groovy.swing.factory.TitledBorderFactory
|
||||||
|
|
||||||
import javax.swing.JDialog
|
import javax.swing.JDialog
|
||||||
import javax.swing.JPanel
|
import javax.swing.JPanel
|
||||||
import javax.swing.JTabbedPane
|
import javax.swing.JTabbedPane
|
||||||
import javax.swing.SwingConstants
|
import javax.swing.SwingConstants
|
||||||
|
import javax.swing.border.TitledBorder
|
||||||
|
|
||||||
import com.muwire.core.Core
|
import com.muwire.core.Core
|
||||||
|
|
||||||
import java.awt.BorderLayout
|
import java.awt.BorderLayout
|
||||||
|
import java.awt.GridBagConstraints
|
||||||
import java.awt.event.WindowAdapter
|
import java.awt.event.WindowAdapter
|
||||||
import java.awt.event.WindowEvent
|
import java.awt.event.WindowEvent
|
||||||
|
|
||||||
@@ -35,6 +38,10 @@ class OptionsView {
|
|||||||
def updateField
|
def updateField
|
||||||
def autoDownloadUpdateCheckbox
|
def autoDownloadUpdateCheckbox
|
||||||
def shareDownloadedCheckbox
|
def shareDownloadedCheckbox
|
||||||
|
def shareHiddenCheckbox
|
||||||
|
def searchCommentsCheckbox
|
||||||
|
def browseFilesCheckbox
|
||||||
|
def speedSmoothSecondsField
|
||||||
|
|
||||||
def inboundLengthField
|
def inboundLengthField
|
||||||
def inboundQuantityField
|
def inboundQuantityField
|
||||||
@@ -46,6 +53,7 @@ class OptionsView {
|
|||||||
def lnfField
|
def lnfField
|
||||||
def monitorCheckbox
|
def monitorCheckbox
|
||||||
def fontField
|
def fontField
|
||||||
|
def fontSizeField
|
||||||
def clearCancelledDownloadsCheckbox
|
def clearCancelledDownloadsCheckbox
|
||||||
def clearFinishedDownloadsCheckbox
|
def clearFinishedDownloadsCheckbox
|
||||||
def excludeLocalResultCheckbox
|
def excludeLocalResultCheckbox
|
||||||
@@ -69,83 +77,156 @@ class OptionsView {
|
|||||||
d.setResizable(false)
|
d.setResizable(false)
|
||||||
p = builder.panel {
|
p = builder.panel {
|
||||||
gridBagLayout()
|
gridBagLayout()
|
||||||
label(text : "Retry failed downloads every", constraints : gbc(gridx: 0, gridy: 0))
|
panel (border : titledBorder(title : "Search Settings", border : etchedBorder(), titlePosition : TitledBorder.TOP),
|
||||||
retryField = textField(text : bind { model.downloadRetryInterval }, columns : 2, constraints : gbc(gridx: 1, gridy: 0))
|
constraints : gbc(gridx: 0, gridy : 0, fill : GridBagConstraints.HORIZONTAL)) {
|
||||||
label(text : "seconds", constraints : gbc(gridx : 2, gridy: 0))
|
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 : "Check for updates every", constraints : gbc(gridx : 0, gridy: 1))
|
panel (border : titledBorder(title : "Download Settings", border : etchedBorder(), titlePosition : TitledBorder.TOP,
|
||||||
updateField = textField(text : bind {model.updateCheckInterval }, columns : 2, constraints : gbc(gridx : 1, gridy: 1))
|
constraints : gbc(gridx : 0, gridy : 1, fill : GridBagConstraints.HORIZONTAL))) {
|
||||||
label(text : "hours", constraints : gbc(gridx: 2, gridy : 1))
|
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 : "Download updates automatically", constraints: gbc(gridx :0, gridy : 2))
|
label(text : "Save downloaded files to:", constraints: gbc(gridx:0, gridy:1, anchor : GridBagConstraints.LINE_START))
|
||||||
autoDownloadUpdateCheckbox = checkBox(selected : bind {model.autoDownloadUpdate}, constraints : gbc(gridx:1, gridy : 2))
|
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 : "Share downloaded files", constraints : gbc(gridx : 0, gridy:3))
|
label(text : "Store incomplete files in:", constraints: gbc(gridx:0, gridy:2, anchor : GridBagConstraints.LINE_START))
|
||||||
shareDownloadedCheckbox = checkBox(selected : bind {model.shareDownloadedFiles}, constraints : gbc(gridx :1, gridy:3))
|
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 : "Save downloaded files to:", constraints: gbc(gridx:0, gridy:4))
|
panel (border : titledBorder(title : "Sharing Settings", border : etchedBorder(), titlePosition : TitledBorder.TOP,
|
||||||
button(text : "Choose", constraints : gbc(gridx : 1, gridy:4), downloadLocationAction)
|
constraints : gbc(gridx : 0, gridy : 2, fill : GridBagConstraints.HORIZONTAL))) {
|
||||||
label(text : bind {model.downloadLocation}, constraints: gbc(gridx:0, gridy:5, gridwidth:2))
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
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 : "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 {
|
i = builder.panel {
|
||||||
gridBagLayout()
|
gridBagLayout()
|
||||||
label(text : "Changing these settings requires a restart", constraints : gbc(gridx : 0, gridy : 0, gridwidth: 2))
|
label(text : "Changing any I2P settings requires a restart", constraints : gbc(gridx:0, gridy : 0))
|
||||||
label(text : "Inbound Length", constraints : gbc(gridx:0, gridy:1))
|
panel (border : titledBorder(title : "Tunnel Settings", border : etchedBorder(), titlePosition: TitledBorder.TOP,
|
||||||
inboundLengthField = textField(text : bind {model.inboundLength}, columns : 2, constraints : gbc(gridx:1, gridy:1))
|
constraints : gbc(gridx: 0, gridy: 1, fill : GridBagConstraints.HORIZONTAL, weightx : 100))) {
|
||||||
label(text : "Inbound Quantity", constraints : gbc(gridx:0, gridy:2))
|
gridBagLayout()
|
||||||
inboundQuantityField = textField(text : bind {model.inboundQuantity}, columns : 2, constraints : gbc(gridx:1, gridy:2))
|
label(text : "Inbound length", constraints : gbc(gridx:0, gridy:0, anchor : GridBagConstraints.LINE_START, weightx : 100))
|
||||||
label(text : "Outbound Length", constraints : gbc(gridx:0, gridy:3))
|
inboundLengthField = textField(text : bind {model.inboundLength}, columns : 2, constraints : gbc(gridx:1, gridy:0,
|
||||||
outboundLengthField = textField(text : bind {model.outboundLength}, columns : 2, constraints : gbc(gridx:1, gridy:3))
|
anchor : GridBagConstraints.LINE_END))
|
||||||
label(text : "Outbound Quantity", constraints : gbc(gridx:0, gridy:4))
|
label(text : "Inbound quantity", constraints : gbc(gridx:0, gridy:1, anchor : GridBagConstraints.LINE_START, weightx : 100))
|
||||||
outboundQuantityField = textField(text : bind {model.outboundQuantity}, columns : 2, constraints : gbc(gridx:1, gridy:4))
|
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")
|
Core core = application.context.get("core")
|
||||||
if (core.router != null) {
|
if (core.router != null) {
|
||||||
label(text : "TCP Port", constraints : gbc(gridx :0, gridy: 5))
|
panel(border : titledBorder(title : "Port Settings", border : etchedBorder(), titlePosition : TitledBorder.TOP,
|
||||||
i2pNTCPPortField = textField(text : bind {model.i2pNTCPPort}, columns : 4, constraints : gbc(gridx:1, gridy:5))
|
constraints : gbc(gridx: 0, gridy : 2, fill : GridBagConstraints.HORIZONTAL, weightx: 100))) {
|
||||||
label(text : "UDP Port", constraints : gbc(gridx :0, gridy: 6))
|
gridBagLayout()
|
||||||
i2pUDPPortField = textField(text : bind {model.i2pUDPPort}, columns : 4, constraints : gbc(gridx:1, gridy:6))
|
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 {
|
u = builder.panel {
|
||||||
gridBagLayout()
|
gridBagLayout()
|
||||||
label(text : "Changing these settings requires a restart", constraints : gbc(gridx : 0, gridy : 0, gridwidth: 2))
|
panel (border : titledBorder(title : "Theme Settings (changes require restart)", border : etchedBorder(), titlePosition: TitledBorder.TOP,
|
||||||
label(text : "Look And Feel", constraints : gbc(gridx: 0, gridy:1))
|
constraints : gbc(gridx : 0, gridy : 0, fill : GridBagConstraints.HORIZONTAL, weightx : 100))) {
|
||||||
lnfField = textField(text : bind {model.lnf}, columns : 4, constraints : gbc(gridx : 1, gridy : 1))
|
gridBagLayout()
|
||||||
label(text : "Font", constraints : gbc(gridx: 0, gridy : 2))
|
label(text : "Look And Feel", constraints : gbc(gridx: 0, gridy:0, anchor : GridBagConstraints.LINE_START, weightx: 100))
|
||||||
fontField = textField(text : bind {model.font}, columns : 4, constraints : gbc(gridx : 1, gridy:2))
|
lnfField = textField(text : bind {model.lnf}, columns : 4, constraints : gbc(gridx : 3, gridy : 0, anchor : GridBagConstraints.LINE_START))
|
||||||
// label(text : "Show Monitor", constraints : gbc(gridx :0, gridy: 3))
|
label(text : "Font", constraints : gbc(gridx: 0, gridy : 1, anchor : GridBagConstraints.LINE_START, weightx: 100))
|
||||||
// monitorCheckbox = checkBox(selected : bind {model.showMonitor}, constraints : gbc(gridx : 1, gridy: 3))
|
fontField = textField(text : bind {model.font}, columns : 4, constraints : gbc(gridx : 3, gridy:1, anchor : GridBagConstraints.LINE_START))
|
||||||
label(text : "Automatically Clear Cancelled Downloads", constraints: gbc(gridx: 0, gridy:4))
|
|
||||||
clearCancelledDownloadsCheckbox = checkBox(selected : bind {model.clearCancelledDownloads}, constraints : gbc(gridx : 1, gridy:4))
|
label(text : "Font size", constraints : gbc(gridx: 0, gridy : 2, anchor : GridBagConstraints.LINE_START, weightx : 100))
|
||||||
label(text : "Automatically Clear Finished Downloads", constraints: gbc(gridx: 0, gridy:5))
|
buttonGroup(id: "fontSizeGroup")
|
||||||
clearFinishedDownloadsCheckbox = checkBox(selected : bind {model.clearFinishedDownloads}, constraints : gbc(gridx : 1, gridy:5))
|
radioButton(text: "Automatic", selected : bind {model.automaticFontSize}, buttonGroup : fontSizeGroup,
|
||||||
label(text : "Exclude Local Files From Results", constraints: gbc(gridx:0, gridy:6))
|
constraints : gbc(gridx : 1, gridy: 2, anchor : GridBagConstraints.LINE_START), automaticFontAction)
|
||||||
excludeLocalResultCheckbox = checkBox(selected : bind {model.excludeLocalResult}, constraints : gbc(gridx: 1, gridy : 6))
|
radioButton(text: "Custom", selected : bind {!model.automaticFontSize}, buttonGroup : fontSizeGroup,
|
||||||
// label(text : "Show Hash Searches In Monitor", constraints: gbc(gridx:0, gridy:7))
|
constraints : gbc(gridx : 2, gridy: 2, anchor : GridBagConstraints.LINE_START), customFontAction)
|
||||||
// showSearchHashesCheckbox = checkBox(selected : bind {model.showSearchHashes}, constraints : gbc(gridx: 1, gridy: 7))
|
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 {
|
bandwidth = builder.panel {
|
||||||
gridBagLayout()
|
gridBagLayout()
|
||||||
label(text : "Changing these settings requires a restart", constraints : gbc(gridx : 0, gridy : 0, gridwidth: 2))
|
panel( border : titledBorder(title : "Changing bandwidth settings requires a restart", border : etchedBorder(), titlePosition : TitledBorder.TOP),
|
||||||
label(text : "Inbound bandwidth (KB)", constraints : gbc(gridx: 0, gridy : 1))
|
constraints : gbc(gridx : 0, gridy : 0, fill : GridBagConstraints.HORIZONTAL, weightx : 100)) {
|
||||||
inBwField = textField(text : bind {model.inBw}, columns : 3, constraints : gbc(gridx : 1, gridy : 1))
|
gridBagLayout()
|
||||||
label(text : "Outbound bandwidth (KB)", constraints : gbc(gridx: 0, gridy : 2))
|
label(text : "Inbound bandwidth (KB)", constraints : gbc(gridx: 0, gridy : 0, anchor : GridBagConstraints.LINE_START, weightx : 100))
|
||||||
outBwField = textField(text : bind {model.outBw}, columns : 3, constraints : gbc(gridx : 1, gridy : 2))
|
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 {
|
trust = builder.panel {
|
||||||
gridBagLayout()
|
gridBagLayout()
|
||||||
label(text : "Allow only trusted connections", constraints : gbc(gridx: 0, gridy : 0))
|
panel (border : titledBorder(title : "Trust Settings", border : etchedBorder(), titlePosition : TitledBorder.TOP),
|
||||||
allowUntrustedCheckbox = checkBox(selected : bind {model.onlyTrusted}, constraints : gbc(gridx: 1, gridy : 0))
|
constraints : gbc(gridx : 0, gridy : 0, fill : GridBagConstraints.HORIZONTAL, weightx: 100)) {
|
||||||
label(text : "Search extra hop", constraints : gbc(gridx:0, gridy:1))
|
gridBagLayout()
|
||||||
searchExtraHopCheckbox = checkBox(selected : bind {model.searchExtraHop}, constraints : gbc(gridx: 1, gridy : 1))
|
label(text : "Allow only trusted connections", constraints : gbc(gridx: 0, gridy : 0, anchor : GridBagConstraints.LINE_START, weightx: 100))
|
||||||
label(text : "Allow others to view my trust list", constraints : gbc(gridx: 0, gridy : 2))
|
allowUntrustedCheckbox = checkBox(selected : bind {model.onlyTrusted}, constraints : gbc(gridx: 1, gridy : 0, anchor : GridBagConstraints.LINE_END))
|
||||||
allowTrustListsCheckbox = checkBox(selected : bind {model.trustLists}, constraints : gbc(gridx: 1, gridy : 2))
|
label(text : "Search extra hop", constraints : gbc(gridx:0, gridy:1, anchor : GridBagConstraints.LINE_START, weightx : 100))
|
||||||
label(text : "Update trust lists every ", constraints : gbc(gridx:0, gridy:3))
|
searchExtraHopCheckbox = checkBox(selected : bind {model.searchExtraHop}, constraints : gbc(gridx: 1, gridy : 1, anchor : GridBagConstraints.LINE_END))
|
||||||
trustListIntervalField = textField(text : bind {model.trustListInterval}, constraints:gbc(gridx:1, gridy:3))
|
label(text : "Allow others to view my trust list", constraints : gbc(gridx: 0, gridy : 2, anchor : GridBagConstraints.LINE_START, weightx : 100))
|
||||||
label(text : "hours", constraints : gbc(gridx: 2, gridy:3))
|
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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -63,6 +63,7 @@ class SearchTabView {
|
|||||||
tableModel(list : model.senders) {
|
tableModel(list : model.senders) {
|
||||||
closureColumn(header : "Sender", preferredWidth : 500, type: String, read : {row -> row.getHumanReadableName()})
|
closureColumn(header : "Sender", preferredWidth : 500, type: String, read : {row -> row.getHumanReadableName()})
|
||||||
closureColumn(header : "Results", preferredWidth : 20, type: Integer, read : {row -> model.sendersBucket[row].size()})
|
closureColumn(header : "Results", preferredWidth : 20, type: Integer, read : {row -> model.sendersBucket[row].size()})
|
||||||
|
closureColumn(header : "Browse", preferredWidth : 20, type: Boolean, read : {row -> model.sendersBucket[row].first().browse})
|
||||||
closureColumn(header : "Trust", preferredWidth : 50, type: String, read : { row ->
|
closureColumn(header : "Trust", preferredWidth : 50, type: String, read : { row ->
|
||||||
model.core.trustService.getLevel(row.destination).toString()
|
model.core.trustService.getLevel(row.destination).toString()
|
||||||
})
|
})
|
||||||
@@ -70,11 +71,17 @@ class SearchTabView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
panel(constraints : BorderLayout.SOUTH) {
|
panel(constraints : BorderLayout.SOUTH) {
|
||||||
|
gridLayout(rows: 1, cols : 2)
|
||||||
|
panel (border : etchedBorder()){
|
||||||
|
button(text : "Browse Host", enabled : bind {model.browseActionEnabled}, browseAction)
|
||||||
|
}
|
||||||
|
panel (border : etchedBorder()){
|
||||||
button(text : "Trust", enabled: bind {model.trustButtonsEnabled }, trustAction)
|
button(text : "Trust", enabled: bind {model.trustButtonsEnabled }, trustAction)
|
||||||
button(text : "Neutral", enabled: bind {model.trustButtonsEnabled}, neutralAction)
|
button(text : "Neutral", enabled: bind {model.trustButtonsEnabled}, neutralAction)
|
||||||
button(text : "Distrust", enabled : bind {model.trustButtonsEnabled}, distrustAction)
|
button(text : "Distrust", enabled : bind {model.trustButtonsEnabled}, distrustAction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
panel {
|
panel {
|
||||||
borderLayout()
|
borderLayout()
|
||||||
scrollPane (constraints : BorderLayout.CENTER) {
|
scrollPane (constraints : BorderLayout.CENTER) {
|
||||||
@@ -93,6 +100,7 @@ class SearchTabView {
|
|||||||
panel()
|
panel()
|
||||||
panel {
|
panel {
|
||||||
button(text : "Download", enabled : bind {model.downloadActionEnabled}, downloadAction)
|
button(text : "Download", enabled : bind {model.downloadActionEnabled}, downloadAction)
|
||||||
|
button(text : "View Comment", enabled : bind {model.viewCommentActionEnabled}, showCommentAction)
|
||||||
}
|
}
|
||||||
panel {
|
panel {
|
||||||
gridBagLayout()
|
gridBagLayout()
|
||||||
@@ -183,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
|
// senders table
|
||||||
sendersTable.setDefaultRenderer(Integer.class, centerRenderer)
|
sendersTable.setDefaultRenderer(Integer.class, centerRenderer)
|
||||||
sendersTable.rowSorter.addRowSorterListener({evt -> lastSendersSortEvent = evt})
|
sendersTable.rowSorter.addRowSorterListener({evt -> lastSendersSortEvent = evt})
|
||||||
@@ -193,12 +211,14 @@ class SearchTabView {
|
|||||||
int row = selectedSenderRow()
|
int row = selectedSenderRow()
|
||||||
if (row < 0) {
|
if (row < 0) {
|
||||||
model.trustButtonsEnabled = false
|
model.trustButtonsEnabled = false
|
||||||
|
model.browseActionEnabled = false
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
|
Persona sender = model.senders[row]
|
||||||
|
model.browseActionEnabled = model.sendersBucket[sender].first().browse
|
||||||
model.trustButtonsEnabled = true
|
model.trustButtonsEnabled = true
|
||||||
model.results.clear()
|
model.results.clear()
|
||||||
Persona p = model.senders[row]
|
model.results.addAll(model.sendersBucket[sender])
|
||||||
model.results.addAll(model.sendersBucket[p])
|
|
||||||
resultsTable.model.fireTableDataChanged()
|
resultsTable.model.fireTableDataChanged()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -226,15 +246,15 @@ class SearchTabView {
|
|||||||
JMenuItem copyHashToClipboard = new JMenuItem("Copy hash to clipboard")
|
JMenuItem copyHashToClipboard = new JMenuItem("Copy hash to clipboard")
|
||||||
copyHashToClipboard.addActionListener({mvcGroup.view.copyHashToClipboard()})
|
copyHashToClipboard.addActionListener({mvcGroup.view.copyHashToClipboard()})
|
||||||
menu.add(copyHashToClipboard)
|
menu.add(copyHashToClipboard)
|
||||||
|
JMenuItem copyNameToClipboard = new JMenuItem("Copy name to clipboard")
|
||||||
|
copyNameToClipboard.addActionListener({mvcGroup.view.copyNameToClipboard()})
|
||||||
|
menu.add(copyNameToClipboard)
|
||||||
showMenu = true
|
showMenu = true
|
||||||
|
|
||||||
// show comment if any
|
// show comment if any
|
||||||
int selectedRow = resultsTable.getSelectedRow()
|
if (model.viewCommentActionEnabled) {
|
||||||
if (lastSortEvent != null)
|
JMenuItem showComment = new JMenuItem("View Comment")
|
||||||
selectedRow = resultsTable.rowSorter.convertRowIndexToModel(selectedRow)
|
showComment.addActionListener({mvcGroup.controller.showComment()})
|
||||||
if (model.results[selectedRow].comment != null) {
|
|
||||||
JMenuItem showComment = new JMenuItem("Show Comment")
|
|
||||||
showComment.addActionListener({mvcGroup.view.showComment()})
|
|
||||||
menu.add(showComment)
|
menu.add(showComment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -242,34 +262,33 @@ class SearchTabView {
|
|||||||
menu.show(e.getComponent(), e.getX(), e.getY())
|
menu.show(e.getComponent(), e.getX(), e.getY())
|
||||||
}
|
}
|
||||||
|
|
||||||
def copyHashToClipboard() {
|
private UIResultEvent getSelectedResult() {
|
||||||
int[] selectedRows = resultsTable.getSelectedRows()
|
int[] selectedRows = resultsTable.getSelectedRows()
|
||||||
if (selectedRows.length != 1)
|
if (selectedRows.length != 1)
|
||||||
return
|
return null
|
||||||
int selected = selectedRows[0]
|
int selected = selectedRows[0]
|
||||||
if (lastSortEvent != null)
|
if (lastSortEvent != null)
|
||||||
selected = resultsTable.rowSorter.convertRowIndexToModel(selected)
|
selected = resultsTable.rowSorter.convertRowIndexToModel(selected)
|
||||||
String hash = Base64.encode(model.results[selected].infohash.getRoot())
|
model.results[selected]
|
||||||
|
}
|
||||||
|
|
||||||
|
def copyHashToClipboard() {
|
||||||
|
def result = getSelectedResult()
|
||||||
|
if (result == null)
|
||||||
|
return
|
||||||
|
String hash = Base64.encode(result.infohash.getRoot())
|
||||||
StringSelection selection = new StringSelection(hash)
|
StringSelection selection = new StringSelection(hash)
|
||||||
def clipboard = Toolkit.getDefaultToolkit().getSystemClipboard()
|
def clipboard = Toolkit.getDefaultToolkit().getSystemClipboard()
|
||||||
clipboard.setContents(selection, null)
|
clipboard.setContents(selection, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
def showComment() {
|
def copyNameToClipboard() {
|
||||||
int selectedRow = resultsTable.getSelectedRow()
|
def result = getSelectedResult()
|
||||||
if (selectedRow < 0)
|
if (result == null)
|
||||||
return
|
return
|
||||||
if (lastSortEvent != null)
|
StringSelection selection = new StringSelection(result.getName())
|
||||||
selectedRow = resultsTable.rowSorter.convertRowIndexToModel(selectedRow)
|
def clipboard = Toolkit.getDefaultToolkit().getSystemClipboard()
|
||||||
UIResultEvent event = model.results[selectedRow]
|
clipboard.setContents(selection, null)
|
||||||
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 selectedSenderRow() {
|
||||||
|
@@ -5,6 +5,8 @@ class UISettings {
|
|||||||
String lnf
|
String lnf
|
||||||
boolean showMonitor
|
boolean showMonitor
|
||||||
String font
|
String font
|
||||||
|
boolean autoFontSize
|
||||||
|
int fontSize
|
||||||
boolean clearCancelledDownloads
|
boolean clearCancelledDownloads
|
||||||
boolean clearFinishedDownloads
|
boolean clearFinishedDownloads
|
||||||
boolean excludeLocalResult
|
boolean excludeLocalResult
|
||||||
@@ -18,6 +20,8 @@ class UISettings {
|
|||||||
clearFinishedDownloads = Boolean.parseBoolean(props.getProperty("clearFinishedDownloads","false"))
|
clearFinishedDownloads = Boolean.parseBoolean(props.getProperty("clearFinishedDownloads","false"))
|
||||||
excludeLocalResult = Boolean.parseBoolean(props.getProperty("excludeLocalResult","true"))
|
excludeLocalResult = Boolean.parseBoolean(props.getProperty("excludeLocalResult","true"))
|
||||||
showSearchHashes = Boolean.parseBoolean(props.getProperty("showSearchHashes","true"))
|
showSearchHashes = Boolean.parseBoolean(props.getProperty("showSearchHashes","true"))
|
||||||
|
autoFontSize = Boolean.parseBoolean(props.getProperty("autoFontSize","false"))
|
||||||
|
fontSize = Integer.parseInt(props.getProperty("fontSize","12"))
|
||||||
}
|
}
|
||||||
|
|
||||||
void write(OutputStream out) throws IOException {
|
void write(OutputStream out) throws IOException {
|
||||||
@@ -28,6 +32,8 @@ class UISettings {
|
|||||||
props.setProperty("clearFinishedDownloads", String.valueOf(clearFinishedDownloads))
|
props.setProperty("clearFinishedDownloads", String.valueOf(clearFinishedDownloads))
|
||||||
props.setProperty("excludeLocalResult", String.valueOf(excludeLocalResult))
|
props.setProperty("excludeLocalResult", String.valueOf(excludeLocalResult))
|
||||||
props.setProperty("showSearchHashes", String.valueOf(showSearchHashes))
|
props.setProperty("showSearchHashes", String.valueOf(showSearchHashes))
|
||||||
|
props.setProperty("autoFontSize", String.valueOf(autoFontSize))
|
||||||
|
props.setProperty("fontSize", String.valueOf(fontSize))
|
||||||
if (font != null)
|
if (font != null)
|
||||||
props.setProperty("font", font)
|
props.setProperty("font", font)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user