Compare commits
37 Commits
muwire-0.4
...
muwire-0.5
Author | SHA1 | Date | |
---|---|---|---|
![]() |
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 | ||
![]() |
1227cf9263 | ||
![]() |
a05575485f | ||
![]() |
f5bccd8126 | ||
![]() |
70fb789abf | ||
![]() |
feb712c253 | ||
![]() |
d22b403e2a | ||
![]() |
a24982e0df | ||
![]() |
6c26019164 | ||
![]() |
965fa79bbf | ||
![]() |
60ddb85461 |
@@ -35,7 +35,7 @@ class Cli {
|
||||
|
||||
Core core
|
||||
try {
|
||||
core = new Core(props, home, "0.4.16")
|
||||
core = new Core(props, home, "0.5.1")
|
||||
} catch (Exception bad) {
|
||||
bad.printStackTrace(System.out)
|
||||
println "Failed to initialize core, exiting"
|
||||
|
@@ -53,7 +53,7 @@ class CliDownloader {
|
||||
|
||||
Core core
|
||||
try {
|
||||
core = new Core(props, home, "0.4.16")
|
||||
core = new Core(props, home, "0.5.1")
|
||||
} catch (Exception bad) {
|
||||
bad.printStackTrace(System.out)
|
||||
println "Failed to initialize core, exiting"
|
||||
|
@@ -28,6 +28,8 @@ import com.muwire.core.files.FileSharedEvent
|
||||
import com.muwire.core.files.FileUnsharedEvent
|
||||
import com.muwire.core.files.HasherService
|
||||
import com.muwire.core.files.PersisterService
|
||||
import com.muwire.core.files.UICommentEvent
|
||||
import com.muwire.core.files.UIPersistFilesEvent
|
||||
import com.muwire.core.files.AllFilesLoadedEvent
|
||||
import com.muwire.core.files.DirectoryUnsharedEvent
|
||||
import com.muwire.core.files.DirectoryWatcher
|
||||
@@ -35,11 +37,13 @@ import com.muwire.core.hostcache.CacheClient
|
||||
import com.muwire.core.hostcache.HostCache
|
||||
import com.muwire.core.hostcache.HostDiscoveredEvent
|
||||
import com.muwire.core.mesh.MeshManager
|
||||
import com.muwire.core.search.BrowseManager
|
||||
import com.muwire.core.search.QueryEvent
|
||||
import com.muwire.core.search.ResultsEvent
|
||||
import com.muwire.core.search.ResultsSender
|
||||
import com.muwire.core.search.SearchEvent
|
||||
import com.muwire.core.search.SearchManager
|
||||
import com.muwire.core.search.UIBrowseEvent
|
||||
import com.muwire.core.search.UIResultBatchEvent
|
||||
import com.muwire.core.trust.TrustEvent
|
||||
import com.muwire.core.trust.TrustService
|
||||
@@ -215,6 +219,7 @@ public class Core {
|
||||
eventBus.register(FileUnsharedEvent.class, fileManager)
|
||||
eventBus.register(SearchEvent.class, fileManager)
|
||||
eventBus.register(DirectoryUnsharedEvent.class, fileManager)
|
||||
eventBus.register(UICommentEvent.class, fileManager)
|
||||
|
||||
log.info("initializing mesh manager")
|
||||
MeshManager meshManager = new MeshManager(fileManager, home, props)
|
||||
@@ -223,6 +228,7 @@ public class Core {
|
||||
log.info "initializing persistence service"
|
||||
persisterService = new PersisterService(new File(home, "files.json"), eventBus, 60000, fileManager)
|
||||
eventBus.register(UILoadedEvent.class, persisterService)
|
||||
eventBus.register(UIPersistFilesEvent.class, persisterService)
|
||||
|
||||
log.info("initializing host cache")
|
||||
File hostStorage = new File(home, "hosts.json")
|
||||
@@ -251,7 +257,7 @@ public class Core {
|
||||
I2PConnector i2pConnector = new I2PConnector(socketManager)
|
||||
|
||||
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"
|
||||
SearchManager searchManager = new SearchManager(eventBus, me, resultsSender)
|
||||
@@ -277,17 +283,19 @@ public class Core {
|
||||
log.info("initializing acceptor")
|
||||
I2PAcceptor i2pAcceptor = new I2PAcceptor(socketManager)
|
||||
connectionAcceptor = new ConnectionAcceptor(eventBus, connectionManager, props,
|
||||
i2pAcceptor, hostCache, trustService, searchManager, uploadManager, connectionEstablisher)
|
||||
i2pAcceptor, hostCache, trustService, searchManager, uploadManager, fileManager, connectionEstablisher)
|
||||
|
||||
log.info("initializing directory watcher")
|
||||
directoryWatcher = new DirectoryWatcher(eventBus, fileManager)
|
||||
directoryWatcher = new DirectoryWatcher(eventBus, fileManager, home, props)
|
||||
eventBus.register(FileSharedEvent.class, directoryWatcher)
|
||||
eventBus.register(AllFilesLoadedEvent.class, directoryWatcher)
|
||||
eventBus.register(DirectoryUnsharedEvent.class, directoryWatcher)
|
||||
|
||||
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(FileUnsharedEvent.class, hasherService)
|
||||
eventBus.register(DirectoryUnsharedEvent.class, hasherService)
|
||||
|
||||
log.info("initializing trust subscriber")
|
||||
trustSubscriber = new TrustSubscriber(eventBus, i2pConnector, props)
|
||||
@@ -298,6 +306,11 @@ public class Core {
|
||||
contentManager = new ContentManager()
|
||||
eventBus.register(ContentControlEvent.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() {
|
||||
@@ -362,7 +375,7 @@ public class Core {
|
||||
}
|
||||
}
|
||||
|
||||
Core core = new Core(props, home, "0.4.16")
|
||||
Core core = new Core(props, home, "0.5.1")
|
||||
core.startServices()
|
||||
|
||||
// ... at the end, sleep or execute script
|
||||
|
@@ -6,6 +6,7 @@ import com.muwire.core.hostcache.CrawlerResponse
|
||||
import com.muwire.core.util.DataUtil
|
||||
|
||||
import net.i2p.data.Base64
|
||||
import net.i2p.util.ConcurrentHashSet
|
||||
|
||||
class MuWireSettings {
|
||||
|
||||
@@ -23,6 +24,9 @@ class MuWireSettings {
|
||||
File downloadLocation
|
||||
CrawlerResponse crawlerResponse
|
||||
boolean shareDownloadedFiles
|
||||
boolean shareHiddenFiles
|
||||
boolean searchComments
|
||||
boolean browseFiles
|
||||
Set<String> watchedDirectories
|
||||
float downloadSequentialRatio
|
||||
int hostClearInterval, hostHopelessInterval, hostRejectInterval
|
||||
@@ -51,6 +55,7 @@ class MuWireSettings {
|
||||
autoDownloadUpdate = Boolean.parseBoolean(props.getProperty("autoDownloadUpdate","true"))
|
||||
updateType = props.getProperty("updateType","jar")
|
||||
shareDownloadedFiles = Boolean.parseBoolean(props.getProperty("shareDownloadedFiles","true"))
|
||||
shareHiddenFiles = Boolean.parseBoolean(props.getProperty("shareHiddenFiles","false"))
|
||||
downloadSequentialRatio = Float.valueOf(props.getProperty("downloadSequentialRatio","0.8"))
|
||||
hostClearInterval = Integer.valueOf(props.getProperty("hostClearInterval","15"))
|
||||
hostHopelessInterval = Integer.valueOf(props.getProperty("hostHopelessInterval", "1440"))
|
||||
@@ -59,6 +64,8 @@ class MuWireSettings {
|
||||
embeddedRouter = Boolean.valueOf(props.getProperty("embeddedRouter","false"))
|
||||
inBw = Integer.valueOf(props.getProperty("inBw","256"))
|
||||
outBw = Integer.valueOf(props.getProperty("outBw","128"))
|
||||
searchComments = Boolean.valueOf(props.getProperty("searchComments","true"))
|
||||
browseFiles = Boolean.valueOf(props.getProperty("browseFiles","true"))
|
||||
|
||||
watchedDirectories = readEncodedSet(props, "watchedDirectories")
|
||||
watchedKeywords = readEncodedSet(props, "watchedKeywords")
|
||||
@@ -89,6 +96,7 @@ class MuWireSettings {
|
||||
props.setProperty("autoDownloadUpdate", String.valueOf(autoDownloadUpdate))
|
||||
props.setProperty("updateType",String.valueOf(updateType))
|
||||
props.setProperty("shareDownloadedFiles", String.valueOf(shareDownloadedFiles))
|
||||
props.setProperty("shareHiddenFiles", String.valueOf(shareHiddenFiles))
|
||||
props.setProperty("downloadSequentialRatio", String.valueOf(downloadSequentialRatio))
|
||||
props.setProperty("hostClearInterval", String.valueOf(hostClearInterval))
|
||||
props.setProperty("hostHopelessInterval", String.valueOf(hostHopelessInterval))
|
||||
@@ -97,6 +105,8 @@ class MuWireSettings {
|
||||
props.setProperty("embeddedRouter", String.valueOf(embeddedRouter))
|
||||
props.setProperty("inBw", String.valueOf(inBw))
|
||||
props.setProperty("outBw", String.valueOf(outBw))
|
||||
props.setProperty("searchComments", String.valueOf(searchComments))
|
||||
props.setProperty("browseFiles", String.valueOf(browseFiles))
|
||||
|
||||
writeEncodedSet(watchedDirectories, "watchedDirectories", props)
|
||||
writeEncodedSet(watchedKeywords, "watchedKeywords", props)
|
||||
@@ -113,7 +123,7 @@ class MuWireSettings {
|
||||
}
|
||||
|
||||
private static Set<String> readEncodedSet(Properties props, String property) {
|
||||
Set<String> rv = new HashSet<>()
|
||||
Set<String> rv = new ConcurrentHashSet<>()
|
||||
if (props.containsKey(property)) {
|
||||
String[] encoded = props.getProperty(property).split(",")
|
||||
encoded.each { rv << DataUtil.readi18nString(Base64.decode(it)) }
|
||||
|
@@ -132,6 +132,7 @@ abstract class Connection implements Closeable {
|
||||
query.firstHop = e.firstHop
|
||||
query.keywords = e.searchEvent.getSearchTerms()
|
||||
query.oobInfohash = e.searchEvent.oobInfohash
|
||||
query.searchComments = e.searchEvent.searchComments
|
||||
if (e.searchEvent.searchHash != null)
|
||||
query.infohash = Base64.encode(e.searchEvent.searchHash)
|
||||
query.replyTo = e.replyTo.toBase64()
|
||||
@@ -209,11 +210,15 @@ abstract class Connection implements Closeable {
|
||||
boolean oob = false
|
||||
if (search.oobInfohash != null)
|
||||
oob = search.oobInfohash
|
||||
boolean searchComments = false
|
||||
if (search.searchComments != null)
|
||||
searchComments = search.searchComments
|
||||
|
||||
SearchEvent searchEvent = new SearchEvent(searchTerms : search.keywords,
|
||||
searchHash : infohash,
|
||||
uuid : uuid,
|
||||
oobInfohash : oob)
|
||||
oobInfohash : oob,
|
||||
searchComments : searchComments)
|
||||
QueryEvent event = new QueryEvent ( searchEvent : searchEvent,
|
||||
replyTo : replyTo,
|
||||
originator : originator,
|
||||
|
@@ -5,11 +5,13 @@ import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.logging.Level
|
||||
import java.util.zip.DeflaterOutputStream
|
||||
import java.util.zip.GZIPOutputStream
|
||||
import java.util.zip.InflaterInputStream
|
||||
|
||||
import com.muwire.core.EventBus
|
||||
import com.muwire.core.MuWireSettings
|
||||
import com.muwire.core.Persona
|
||||
import com.muwire.core.files.FileManager
|
||||
import com.muwire.core.hostcache.HostCache
|
||||
import com.muwire.core.trust.TrustLevel
|
||||
import com.muwire.core.trust.TrustService
|
||||
@@ -17,6 +19,7 @@ import com.muwire.core.upload.UploadManager
|
||||
import com.muwire.core.util.DataUtil
|
||||
import com.muwire.core.search.InvalidSearchResultException
|
||||
import com.muwire.core.search.ResultsParser
|
||||
import com.muwire.core.search.ResultsSender
|
||||
import com.muwire.core.search.SearchManager
|
||||
import com.muwire.core.search.UIResultBatchEvent
|
||||
import com.muwire.core.search.UIResultEvent
|
||||
@@ -37,6 +40,7 @@ class ConnectionAcceptor {
|
||||
final TrustService trustService
|
||||
final SearchManager searchManager
|
||||
final UploadManager uploadManager
|
||||
final FileManager fileManager
|
||||
final ConnectionEstablisher establisher
|
||||
|
||||
final ExecutorService acceptorThread
|
||||
@@ -47,7 +51,7 @@ class ConnectionAcceptor {
|
||||
ConnectionAcceptor(EventBus eventBus, UltrapeerConnectionManager manager,
|
||||
MuWireSettings settings, I2PAcceptor acceptor, HostCache hostCache,
|
||||
TrustService trustService, SearchManager searchManager, UploadManager uploadManager,
|
||||
ConnectionEstablisher establisher) {
|
||||
FileManager fileManager, ConnectionEstablisher establisher) {
|
||||
this.eventBus = eventBus
|
||||
this.manager = manager
|
||||
this.settings = settings
|
||||
@@ -55,6 +59,7 @@ class ConnectionAcceptor {
|
||||
this.hostCache = hostCache
|
||||
this.trustService = trustService
|
||||
this.searchManager = searchManager
|
||||
this.fileManager = fileManager
|
||||
this.uploadManager = uploadManager
|
||||
this.establisher = establisher
|
||||
|
||||
@@ -129,6 +134,9 @@ class ConnectionAcceptor {
|
||||
case (byte)'T':
|
||||
processTRUST(e)
|
||||
break
|
||||
case (byte)'B':
|
||||
processBROWSE(e)
|
||||
break
|
||||
default:
|
||||
throw new Exception("Invalid read $read")
|
||||
}
|
||||
@@ -246,44 +254,87 @@ class ConnectionAcceptor {
|
||||
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) {
|
||||
byte[] RUST = new byte[6]
|
||||
DataInputStream dis = new DataInputStream(e.getInputStream())
|
||||
dis.readFully(RUST)
|
||||
if (RUST != "RUST\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||
throw new IOException("Invalid TRUST connection")
|
||||
String header
|
||||
while ((header = DataUtil.readTillRN(dis)) != ""); // ignore headers for now
|
||||
try {
|
||||
byte[] RUST = new byte[6]
|
||||
DataInputStream dis = new DataInputStream(e.getInputStream())
|
||||
dis.readFully(RUST)
|
||||
if (RUST != "RUST\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||
throw new IOException("Invalid TRUST connection")
|
||||
String header
|
||||
while ((header = DataUtil.readTillRN(dis)) != ""); // ignore headers for now
|
||||
|
||||
OutputStream os = e.getOutputStream()
|
||||
if (!settings.allowTrustLists) {
|
||||
os.write("403 Not Allowed\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||
os.flush()
|
||||
OutputStream os = e.getOutputStream()
|
||||
if (!settings.allowTrustLists) {
|
||||
os.write("403 Not Allowed\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||
os.flush()
|
||||
e.close()
|
||||
return
|
||||
}
|
||||
|
||||
os.write("200 OK\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||
List<Persona> good = new ArrayList<>(trustService.good.values())
|
||||
int size = Math.min(Short.MAX_VALUE * 2, good.size())
|
||||
good = good.subList(0, size)
|
||||
DataOutputStream dos = new DataOutputStream(os)
|
||||
dos.writeShort(size)
|
||||
good.each {
|
||||
it.write(dos)
|
||||
}
|
||||
|
||||
List<Persona> bad = new ArrayList<>(trustService.bad.values())
|
||||
size = Math.min(Short.MAX_VALUE * 2, bad.size())
|
||||
bad = bad.subList(0, size)
|
||||
dos.writeShort(size)
|
||||
bad.each {
|
||||
it.write(dos)
|
||||
}
|
||||
|
||||
dos.flush()
|
||||
} finally {
|
||||
e.close()
|
||||
return
|
||||
}
|
||||
|
||||
os.write("200 OK\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||
List<Persona> good = new ArrayList<>(trustService.good.values())
|
||||
int size = Math.min(Short.MAX_VALUE * 2, good.size())
|
||||
good = good.subList(0, size)
|
||||
DataOutputStream dos = new DataOutputStream(os)
|
||||
dos.writeShort(size)
|
||||
good.each {
|
||||
it.write(dos)
|
||||
}
|
||||
|
||||
List<Persona> bad = new ArrayList<>(trustService.bad.values())
|
||||
size = Math.min(Short.MAX_VALUE * 2, bad.size())
|
||||
bad = bad.subList(0, size)
|
||||
dos.writeShort(size)
|
||||
bad.each {
|
||||
it.write(dos)
|
||||
}
|
||||
|
||||
dos.flush()
|
||||
e.close()
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -13,6 +13,7 @@ import java.nio.file.WatchService
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
import com.muwire.core.EventBus
|
||||
import com.muwire.core.MuWireSettings
|
||||
import com.muwire.core.SharedFile
|
||||
|
||||
import groovy.util.logging.Log
|
||||
@@ -31,6 +32,8 @@ class DirectoryWatcher {
|
||||
kinds = [ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE]
|
||||
}
|
||||
|
||||
private final File home
|
||||
private final MuWireSettings muOptions
|
||||
private final EventBus eventBus
|
||||
private final FileManager fileManager
|
||||
private final Thread watcherThread, publisherThread
|
||||
@@ -39,7 +42,9 @@ class DirectoryWatcher {
|
||||
private WatchService watchService
|
||||
private volatile boolean shutdown
|
||||
|
||||
DirectoryWatcher(EventBus eventBus, FileManager fileManager) {
|
||||
DirectoryWatcher(EventBus eventBus, FileManager fileManager, File home, MuWireSettings muOptions) {
|
||||
this.home = home
|
||||
this.muOptions = muOptions
|
||||
this.eventBus = eventBus
|
||||
this.fileManager = fileManager
|
||||
this.watcherThread = new Thread({watch() } as Runnable, "directory-watcher")
|
||||
@@ -64,15 +69,28 @@ class DirectoryWatcher {
|
||||
void onFileSharedEvent(FileSharedEvent e) {
|
||||
if (!e.file.isDirectory())
|
||||
return
|
||||
Path path = e.file.getCanonicalFile().toPath()
|
||||
File canonical = e.file.getCanonicalFile()
|
||||
Path path = canonical.toPath()
|
||||
WatchKey wk = path.register(watchService, kinds)
|
||||
watchedDirectories.put(e.file, wk)
|
||||
|
||||
watchedDirectories.put(canonical, wk)
|
||||
|
||||
if (muOptions.watchedDirectories.add(canonical.toString()))
|
||||
saveMuSettings()
|
||||
}
|
||||
|
||||
void onDirectoryUnsharedEvent(DirectoryUnsharedEvent e) {
|
||||
WatchKey wk = watchedDirectories.remove(e.directory)
|
||||
wk?.cancel()
|
||||
|
||||
if (muOptions.watchedDirectories.remove(e.directory.toString()))
|
||||
saveMuSettings()
|
||||
}
|
||||
|
||||
private void saveMuSettings() {
|
||||
File muSettingsFile = new File(home, "MuWire.properties")
|
||||
muSettingsFile.withOutputStream {
|
||||
muOptions.write(it)
|
||||
}
|
||||
}
|
||||
|
||||
private void watch() {
|
||||
|
@@ -8,8 +8,10 @@ import com.muwire.core.UILoadedEvent
|
||||
import com.muwire.core.search.ResultsEvent
|
||||
import com.muwire.core.search.SearchEvent
|
||||
import com.muwire.core.search.SearchIndex
|
||||
import com.muwire.core.util.DataUtil
|
||||
|
||||
import groovy.util.logging.Log
|
||||
import net.i2p.data.Base64
|
||||
|
||||
@Log
|
||||
class FileManager {
|
||||
@@ -20,6 +22,7 @@ class FileManager {
|
||||
final Map<InfoHash, Set<SharedFile>> rootToFiles = 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>> commentToFile = new HashMap<>()
|
||||
final SearchIndex index = new SearchIndex()
|
||||
|
||||
FileManager(EventBus eventBus, MuWireSettings settings) {
|
||||
@@ -62,6 +65,18 @@ class FileManager {
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -86,9 +101,45 @@ class FileManager {
|
||||
nameToFiles.remove(name)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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() {
|
||||
synchronized(fileToSharedFile) {
|
||||
@@ -112,10 +163,15 @@ class FileManager {
|
||||
} else {
|
||||
def names = index.search e.searchTerms
|
||||
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<>()
|
||||
files.each { sharedFiles.add fileToSharedFile[it] }
|
||||
files = filter(sharedFiles, e.oobInfohash)
|
||||
|
||||
if (!sharedFiles.isEmpty())
|
||||
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 com.muwire.core.EventBus
|
||||
import com.muwire.core.MuWireSettings
|
||||
import com.muwire.core.SharedFile
|
||||
|
||||
class HasherService {
|
||||
@@ -11,12 +12,15 @@ class HasherService {
|
||||
final FileHasher hasher
|
||||
final EventBus eventBus
|
||||
final FileManager fileManager
|
||||
final Set<File> hashed = new HashSet<>()
|
||||
final MuWireSettings settings
|
||||
Executor executor
|
||||
|
||||
HasherService(FileHasher hasher, EventBus eventBus, FileManager fileManager) {
|
||||
HasherService(FileHasher hasher, EventBus eventBus, FileManager fileManager, MuWireSettings settings) {
|
||||
this.hasher = hasher
|
||||
this.eventBus = eventBus
|
||||
this.fileManager = fileManager
|
||||
this.settings = settings
|
||||
}
|
||||
|
||||
void start() {
|
||||
@@ -24,13 +28,24 @@ class HasherService {
|
||||
}
|
||||
|
||||
void onFileSharedEvent(FileSharedEvent evt) {
|
||||
if (fileManager.fileToSharedFile.containsKey(evt.file.getCanonicalFile()))
|
||||
File canonical = evt.file.getCanonicalFile()
|
||||
if (!settings.shareHiddenFiles && canonical.isHidden())
|
||||
return
|
||||
executor.execute( { -> process(evt.file) } as Runnable)
|
||||
if (fileManager.fileToSharedFile.containsKey(canonical))
|
||||
return
|
||||
if (hashed.add(canonical))
|
||||
executor.execute( { -> process(canonical) } as Runnable)
|
||||
}
|
||||
|
||||
void onFileUnsharedEvent(FileUnsharedEvent evt) {
|
||||
hashed.remove(evt.unsharedFile.file)
|
||||
}
|
||||
|
||||
void onDirectoryUnsharedEvent(DirectoryUnsharedEvent evt) {
|
||||
hashed.remove(evt.directory)
|
||||
}
|
||||
|
||||
private void process(File f) {
|
||||
f = f.getCanonicalFile()
|
||||
if (f.isDirectory()) {
|
||||
f.listFiles().each {eventBus.publish new FileSharedEvent(file: it) }
|
||||
} else {
|
||||
|
@@ -3,6 +3,9 @@ package com.muwire.core.files
|
||||
import java.nio.file.CopyOption
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.StandardCopyOption
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.ThreadFactory
|
||||
import java.util.logging.Level
|
||||
import java.util.stream.Collectors
|
||||
|
||||
@@ -28,13 +31,16 @@ class PersisterService extends Service {
|
||||
final int interval
|
||||
final Timer timer
|
||||
final FileManager fileManager
|
||||
final ExecutorService persisterExecutor = Executors.newSingleThreadExecutor({ r ->
|
||||
new Thread(r, "file persister")
|
||||
} as ThreadFactory)
|
||||
|
||||
PersisterService(File location, EventBus listener, int interval, FileManager fileManager) {
|
||||
this.location = location
|
||||
this.listener = listener
|
||||
this.interval = interval
|
||||
this.fileManager = fileManager
|
||||
timer = new Timer("file persister", true)
|
||||
timer = new Timer("file persister timer", true)
|
||||
}
|
||||
|
||||
void stop() {
|
||||
@@ -44,6 +50,10 @@ class PersisterService extends Service {
|
||||
void onUILoadedEvent(UILoadedEvent e) {
|
||||
timer.schedule({load()} as TimerTask, 1)
|
||||
}
|
||||
|
||||
void onUIPersistFilesEvent(UIPersistFilesEvent e) {
|
||||
persistFiles()
|
||||
}
|
||||
|
||||
void load() {
|
||||
Thread.currentThread().setPriority(Thread.MIN_PRIORITY)
|
||||
@@ -127,19 +137,21 @@ class PersisterService extends Service {
|
||||
}
|
||||
|
||||
private void persistFiles() {
|
||||
def sharedFiles = fileManager.getSharedFiles()
|
||||
persisterExecutor.submit( {
|
||||
def sharedFiles = fileManager.getSharedFiles()
|
||||
|
||||
File tmp = File.createTempFile("muwire-files", "tmp")
|
||||
tmp.deleteOnExit()
|
||||
tmp.withPrintWriter { writer ->
|
||||
sharedFiles.each { k, v ->
|
||||
def json = toJson(k,v)
|
||||
json = JsonOutput.toJson(json)
|
||||
writer.println json
|
||||
File tmp = File.createTempFile("muwire-files", "tmp")
|
||||
tmp.deleteOnExit()
|
||||
tmp.withPrintWriter { writer ->
|
||||
sharedFiles.each { k, v ->
|
||||
def json = toJson(k,v)
|
||||
json = JsonOutput.toJson(json)
|
||||
writer.println json
|
||||
}
|
||||
}
|
||||
}
|
||||
Files.copy(tmp.toPath(), location.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
||||
tmp.delete()
|
||||
Files.copy(tmp.toPath(), location.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
||||
tmp.delete()
|
||||
} as Runnable)
|
||||
}
|
||||
|
||||
private def toJson(File f, SharedFile sf) {
|
||||
|
@@ -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
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
package com.muwire.core.files
|
||||
|
||||
import com.muwire.core.Event
|
||||
|
||||
class UIPersistFilesEvent extends Event {
|
||||
}
|
@@ -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")
|
||||
|
||||
// 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))
|
||||
|
||||
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,7 @@
|
||||
package com.muwire.core.search
|
||||
|
||||
import com.muwire.core.Event
|
||||
|
||||
class BrowseStatusEvent extends Event {
|
||||
BrowseStatus status
|
||||
}
|
@@ -94,6 +94,10 @@ class ResultsParser {
|
||||
String comment = null
|
||||
if (json.comment != null)
|
||||
comment = DataUtil.readi18nString(Base64.decode(json.comment))
|
||||
|
||||
boolean browse = false
|
||||
if (json.browse != null)
|
||||
browse = true
|
||||
|
||||
return new UIResultEvent( sender : p,
|
||||
name : name,
|
||||
@@ -102,6 +106,7 @@ class ResultsParser {
|
||||
pieceSize : pieceSize,
|
||||
sources : sources,
|
||||
comment : comment,
|
||||
browse : browse,
|
||||
uuid: uuid)
|
||||
} catch (Exception e) {
|
||||
throw new InvalidSearchResultException("parsing search result failed",e)
|
||||
|
@@ -4,6 +4,7 @@ import com.muwire.core.SharedFile
|
||||
import com.muwire.core.connection.Endpoint
|
||||
import com.muwire.core.connection.I2PConnector
|
||||
import com.muwire.core.files.FileHasher
|
||||
import com.muwire.core.util.DataUtil
|
||||
import com.muwire.core.Persona
|
||||
|
||||
import java.nio.charset.StandardCharsets
|
||||
@@ -17,6 +18,7 @@ import java.util.stream.Collectors
|
||||
import com.muwire.core.DownloadedFile
|
||||
import com.muwire.core.EventBus
|
||||
import com.muwire.core.InfoHash
|
||||
import com.muwire.core.MuWireSettings
|
||||
|
||||
import groovy.json.JsonOutput
|
||||
import groovy.util.logging.Log
|
||||
@@ -42,11 +44,13 @@ class ResultsSender {
|
||||
private final I2PConnector connector
|
||||
private final Persona me
|
||||
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.eventBus = eventBus
|
||||
this.me = me
|
||||
this.settings = settings
|
||||
}
|
||||
|
||||
void sendResults(UUID uuid, SharedFile[] results, Destination target, boolean oobInfohash) {
|
||||
@@ -60,13 +64,18 @@ class ResultsSender {
|
||||
Set<Destination> suggested = Collections.emptySet()
|
||||
if (it instanceof DownloadedFile)
|
||||
suggested = it.sources
|
||||
def comment = null
|
||||
if (it.getComment() != null) {
|
||||
comment = DataUtil.readi18nString(Base64.decode(it.getComment()))
|
||||
}
|
||||
def uiResultEvent = new UIResultEvent( sender : me,
|
||||
name : it.getFile().getName(),
|
||||
size : length,
|
||||
infohash : it.getInfoHash(),
|
||||
pieceSize : pieceSize,
|
||||
uuid : uuid,
|
||||
sources : suggested
|
||||
sources : suggested,
|
||||
comment : comment
|
||||
)
|
||||
eventBus.publish(uiResultEvent)
|
||||
}
|
||||
@@ -85,7 +94,6 @@ class ResultsSender {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
byte [] tmp = new byte[InfoHash.SIZE]
|
||||
JsonOutput jsonOutput = new JsonOutput()
|
||||
Endpoint endpoint = null;
|
||||
try {
|
||||
@@ -95,36 +103,7 @@ class ResultsSender {
|
||||
me.write(os)
|
||||
os.writeShort((short)results.length)
|
||||
results.each {
|
||||
byte [] name = it.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 = 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 obj = sharedFileToObj(it, settings.browseFiles)
|
||||
def json = jsonOutput.toJson(obj)
|
||||
os.writeShort((short)json.length())
|
||||
os.write(json.getBytes(StandardCharsets.US_ASCII))
|
||||
@@ -138,4 +117,30 @@ class ResultsSender {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,12 @@ class SearchEvent extends Event {
|
||||
byte [] searchHash
|
||||
UUID uuid
|
||||
boolean oobInfohash
|
||||
boolean searchComments
|
||||
|
||||
String toString() {
|
||||
def infoHash = null
|
||||
if (searchHash != null)
|
||||
infoHash = new InfoHash(searchHash)
|
||||
"searchTerms: $searchTerms searchHash:$infoHash, uuid:$uuid oobInfohash:$oobInfohash"
|
||||
"searchTerms: $searchTerms searchHash:$infoHash, uuid:$uuid oobInfohash:$oobInfohash searchComments:$searchComments"
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
int pieceSize
|
||||
String comment
|
||||
boolean browse
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
@@ -25,7 +25,8 @@ class HasherServiceTest {
|
||||
void before() {
|
||||
eventBus = new EventBus()
|
||||
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(FileSharedEvent.class, service)
|
||||
service.start()
|
||||
|
@@ -1,5 +1,5 @@
|
||||
group = com.muwire
|
||||
version = 0.4.16
|
||||
version = 0.5.1
|
||||
groovyVersion = 2.4.15
|
||||
slf4jVersion = 1.7.25
|
||||
spockVersion = 1.1-groovy-2.4
|
||||
|
@@ -58,7 +58,11 @@ dependencies {
|
||||
compile project(":core")
|
||||
compile "org.codehaus.griffon:griffon-guice:${griffon.version}"
|
||||
|
||||
runtime "org.slf4j:slf4j-simple:${slf4jVersion}"
|
||||
// runtime "org.slf4j:slf4j-simple:${slf4jVersion}"
|
||||
|
||||
runtime group: 'org.slf4j', name: 'slf4j-jdk14', version: "${slf4jVersion}"
|
||||
runtime group: 'org.slf4j', name: 'slf4j-api', version: "${slf4jVersion}"
|
||||
runtime group: 'org.slf4j', name: 'jul-to-slf4j', version: "${slf4jVersion}"
|
||||
runtime "javax.annotation:javax.annotation-api:1.3.2"
|
||||
|
||||
testCompile "org.codehaus.griffon:griffon-fest-test:${griffon.version}"
|
||||
|
@@ -56,4 +56,9 @@ mvcGroups {
|
||||
view = 'com.muwire.gui.AddCommentView'
|
||||
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 com.muwire.core.Core
|
||||
import com.muwire.core.files.UICommentEvent
|
||||
import com.muwire.core.util.DataUtil
|
||||
|
||||
@ArtifactProviderFor(GriffonController)
|
||||
@@ -17,6 +19,8 @@ class AddCommentController {
|
||||
@MVCMember @Nonnull
|
||||
AddCommentView view
|
||||
|
||||
Core core
|
||||
|
||||
@ControllerAction
|
||||
void save() {
|
||||
String comment = view.textarea.getText()
|
||||
@@ -25,9 +29,11 @@ class AddCommentController {
|
||||
else
|
||||
comment = Base64.encode(DataUtil.encodei18nString(comment))
|
||||
model.selectedFiles.each {
|
||||
def event = new UICommentEvent(sharedFile : it, oldComment : it.getComment())
|
||||
it.setComment(comment)
|
||||
core.eventBus.publish(event)
|
||||
}
|
||||
mvcGroup.parentGroup.view.builder.getVariable("shared-files-table").model.fireTableDataChanged()
|
||||
mvcGroup.parentGroup.view.refreshSharedFiles()
|
||||
cancel()
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,95 @@
|
||||
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.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
|
||||
}
|
||||
}
|
||||
|
||||
void onUIResultEvent(UIResultEvent e) {
|
||||
runInsideUIAsync {
|
||||
model.results << e
|
||||
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)
|
||||
}
|
||||
}
|
@@ -25,6 +25,7 @@ import com.muwire.core.download.UIDownloadPausedEvent
|
||||
import com.muwire.core.download.UIDownloadResumedEvent
|
||||
import com.muwire.core.files.DirectoryUnsharedEvent
|
||||
import com.muwire.core.files.FileUnsharedEvent
|
||||
import com.muwire.core.files.UIPersistFilesEvent
|
||||
import com.muwire.core.search.QueryEvent
|
||||
import com.muwire.core.search.SearchEvent
|
||||
import com.muwire.core.trust.RemoteTrustList
|
||||
@@ -84,7 +85,8 @@ class MainFrameController {
|
||||
def terms = replaced.split(" ")
|
||||
def nonEmpty = []
|
||||
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)
|
||||
}
|
||||
boolean firstHop = core.muOptions.allowUntrusted || core.muOptions.searchExtraHop
|
||||
core.eventBus.publish(new QueryEvent(searchEvent : searchEvent, firstHop : firstHop,
|
||||
@@ -276,6 +278,7 @@ class MainFrameController {
|
||||
sf.each {
|
||||
core.eventBus.publish(new FileUnsharedEvent(unsharedFile : it))
|
||||
}
|
||||
core.eventBus.publish(new UIPersistFilesEvent())
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
@@ -286,21 +289,10 @@ class MainFrameController {
|
||||
|
||||
Map<String, Object> params = new HashMap<>()
|
||||
params['selectedFiles'] = selectedFiles
|
||||
params['core'] = core
|
||||
mvcGroup.createMVCGroup("add-comment", "Add Comment", params)
|
||||
}
|
||||
|
||||
void stopWatchingDirectory() {
|
||||
String directory = mvcGroup.view.getSelectedWatchedDirectory()
|
||||
if (directory == null)
|
||||
return
|
||||
core.muOptions.watchedDirectories.remove(directory)
|
||||
saveMuWireSettings()
|
||||
core.eventBus.publish(new DirectoryUnsharedEvent(directory : new File(directory)))
|
||||
|
||||
model.watched.remove(directory)
|
||||
builder.getVariable("watched-directories-table").model.fireTableDataChanged()
|
||||
}
|
||||
|
||||
void saveMuWireSettings() {
|
||||
File f = new File(core.home, "MuWire.properties")
|
||||
f.withOutputStream {
|
||||
|
@@ -70,6 +70,10 @@ class OptionsController {
|
||||
model.updateCheckInterval = text
|
||||
settings.updateCheckInterval = Integer.valueOf(text)
|
||||
|
||||
boolean searchComments = view.searchCommentsCheckbox.model.isSelected()
|
||||
model.searchComments = searchComments
|
||||
settings.searchComments = searchComments
|
||||
|
||||
boolean autoDownloadUpdate = view.autoDownloadUpdateCheckbox.model.isSelected()
|
||||
model.autoDownloadUpdate = autoDownloadUpdate
|
||||
settings.autoDownloadUpdate = autoDownloadUpdate
|
||||
@@ -78,7 +82,15 @@ class OptionsController {
|
||||
boolean shareDownloaded = view.shareDownloadedCheckbox.model.isSelected()
|
||||
model.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
|
||||
|
||||
String downloadLocation = model.downloadLocation
|
||||
settings.downloadLocation = new File(downloadLocation)
|
||||
|
||||
@@ -123,10 +135,9 @@ class OptionsController {
|
||||
text = view.fontField.text
|
||||
model.font = text
|
||||
uiSettings.font = text
|
||||
|
||||
// boolean showMonitor = view.monitorCheckbox.model.isSelected()
|
||||
// model.showMonitor = showMonitor
|
||||
// uiSettings.showMonitor = showMonitor
|
||||
|
||||
uiSettings.autoFontSize = model.automaticFontSize
|
||||
uiSettings.fontSize = Integer.parseInt(view.fontSizeField.text)
|
||||
|
||||
boolean clearCancelledDownloads = view.clearCancelledDownloadsCheckbox.model.isSelected()
|
||||
model.clearCancelledDownloads = clearCancelledDownloads
|
||||
@@ -140,10 +151,6 @@ class OptionsController {
|
||||
model.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")
|
||||
uiSettingsFile.withOutputStream {
|
||||
uiSettings.write(it)
|
||||
@@ -167,5 +174,16 @@ class OptionsController {
|
||||
int rv = chooser.showOpenDialog(null)
|
||||
if (rv == JFileChooser.APPROVE_OPTION)
|
||||
model.downloadLocation = 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.inject.MVCMember
|
||||
import griffon.metadata.ArtifactProviderFor
|
||||
import net.i2p.data.Base64
|
||||
|
||||
import javax.annotation.Nonnull
|
||||
|
||||
import com.muwire.core.Core
|
||||
import com.muwire.core.Persona
|
||||
import com.muwire.core.download.UIDownloadEvent
|
||||
import com.muwire.core.search.UIResultEvent
|
||||
import com.muwire.core.trust.TrustEvent
|
||||
@@ -85,4 +88,37 @@ class SearchTabController {
|
||||
def sender = model.senders[row]
|
||||
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.inject.Inject
|
||||
import javax.swing.JLabel
|
||||
import javax.swing.JTable
|
||||
import javax.swing.LookAndFeel
|
||||
import javax.swing.UIManager
|
||||
import javax.swing.plaf.FontUIResource
|
||||
|
||||
import static griffon.util.GriffonApplicationUtils.isMacOSX
|
||||
import static groovy.swing.SwingBuilder.lookAndFeel
|
||||
|
||||
import java.awt.Font
|
||||
import java.awt.Toolkit
|
||||
import java.util.logging.Level
|
||||
import java.util.logging.LogManager
|
||||
|
||||
@Log
|
||||
class Initialize extends AbstractLifecycleHandler {
|
||||
@@ -29,6 +33,16 @@ class Initialize extends AbstractLifecycleHandler {
|
||||
|
||||
@Override
|
||||
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"
|
||||
def portableHome = System.getProperty("portable.home")
|
||||
def home = portableHome == null ?
|
||||
@@ -52,25 +66,43 @@ class Initialize extends AbstractLifecycleHandler {
|
||||
guiPropsFile.withInputStream { props.load(it) }
|
||||
uiSettings = new UISettings(props)
|
||||
|
||||
def lnf
|
||||
log.info("settting user-specified lnf $uiSettings.lnf")
|
||||
try {
|
||||
lookAndFeel(uiSettings.lnf)
|
||||
lnf = lookAndFeel(uiSettings.lnf)
|
||||
} catch (Throwable bad) {
|
||||
log.log(Level.WARNING,"couldn't set desired look and feeel, switching to defaults", bad)
|
||||
uiSettings.lnf = lookAndFeel("system","gtk","metal").getID()
|
||||
log.log(Level.WARNING,"couldn't set desired look and feel, switching to defaults", bad)
|
||||
lnf = lookAndFeel("system","gtk","metal")
|
||||
uiSettings.lnf = lnf.getID()
|
||||
}
|
||||
|
||||
if (uiSettings.font != null) {
|
||||
log.info("setting user-specified font $uiSettings.font")
|
||||
Font font = new Font(uiSettings.font, Font.PLAIN, 12)
|
||||
def defaults = UIManager.getDefaults()
|
||||
defaults.put("Button.font", font)
|
||||
defaults.put("RadioButton.font", font)
|
||||
defaults.put("Label.font", font)
|
||||
defaults.put("CheckBox.font", font)
|
||||
defaults.put("Table.font", font)
|
||||
defaults.put("TableHeader.font", font)
|
||||
// TODO: add others
|
||||
if (uiSettings.font != null || uiSettings.autoFontSize || uiSettings.fontSize > 0) {
|
||||
|
||||
FontUIResource defaultFont = lnf.getDefaults().getFont("Label.font")
|
||||
|
||||
String fontName
|
||||
if (uiSettings.font != null)
|
||||
fontName = uiSettings.font
|
||||
else
|
||||
fontName = defaultFont.getName()
|
||||
|
||||
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 {
|
||||
Properties props = new Properties()
|
||||
|
19
gui/griffon-app/models/com/muwire/gui/BrowseModel.groovy
Normal file
19
gui/griffon-app/models/com/muwire/gui/BrowseModel.groovy
Normal file
@@ -0,0 +1,19 @@
|
||||
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
|
||||
|
||||
def results = []
|
||||
}
|
@@ -1,6 +1,8 @@
|
||||
package com.muwire.gui
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.util.Calendar
|
||||
import java.util.UUID
|
||||
|
||||
@@ -8,12 +10,16 @@ import javax.annotation.Nonnull
|
||||
import javax.inject.Inject
|
||||
import javax.swing.JOptionPane
|
||||
import javax.swing.JTable
|
||||
import javax.swing.tree.DefaultMutableTreeNode
|
||||
import javax.swing.tree.DefaultTreeModel
|
||||
import javax.swing.tree.TreeNode
|
||||
|
||||
import com.muwire.core.Core
|
||||
import com.muwire.core.InfoHash
|
||||
import com.muwire.core.MuWireSettings
|
||||
import com.muwire.core.Persona
|
||||
import com.muwire.core.RouterDisconnectedEvent
|
||||
import com.muwire.core.SharedFile
|
||||
import com.muwire.core.connection.ConnectionAttemptStatus
|
||||
import com.muwire.core.connection.ConnectionEvent
|
||||
import com.muwire.core.connection.DisconnectionEvent
|
||||
@@ -21,6 +27,7 @@ import com.muwire.core.content.ContentControlEvent
|
||||
import com.muwire.core.download.DownloadStartedEvent
|
||||
import com.muwire.core.download.Downloader
|
||||
import com.muwire.core.files.AllFilesLoadedEvent
|
||||
import com.muwire.core.files.DirectoryUnsharedEvent
|
||||
import com.muwire.core.files.FileDownloadedEvent
|
||||
import com.muwire.core.files.FileHashedEvent
|
||||
import com.muwire.core.files.FileHashingEvent
|
||||
@@ -57,6 +64,8 @@ class MainFrameModel {
|
||||
FactoryBuilderSupport builder
|
||||
@MVCMember @Nonnull
|
||||
MainFrameController controller
|
||||
@MVCMember @Nonnull
|
||||
MainFrameView view
|
||||
@Inject @Nonnull GriffonApplication application
|
||||
@Observable boolean coreInitialized = false
|
||||
@Observable boolean routerPresent
|
||||
@@ -64,8 +73,11 @@ class MainFrameModel {
|
||||
def results = new ConcurrentHashMap<>()
|
||||
def downloads = []
|
||||
def uploads = []
|
||||
def shared = []
|
||||
def watched = []
|
||||
boolean treeVisible = true
|
||||
def shared
|
||||
def sharedTree
|
||||
def treeRoot
|
||||
final Map<SharedFile, TreeNode> fileToNode = new HashMap<>()
|
||||
def connectionList = []
|
||||
def searches = new LinkedList()
|
||||
def trusted = []
|
||||
@@ -122,6 +134,10 @@ class MainFrameModel {
|
||||
void mvcGroupInit(Map<String, Object> args) {
|
||||
|
||||
uiSettings = application.context.get("ui-settings")
|
||||
|
||||
shared = []
|
||||
treeRoot = new DefaultMutableTreeNode()
|
||||
sharedTree = new DefaultTreeModel(treeRoot)
|
||||
|
||||
Timer timer = new Timer("download-pumper", true)
|
||||
timer.schedule({
|
||||
@@ -165,7 +181,6 @@ class MainFrameModel {
|
||||
core = e.getNewValue()
|
||||
routerPresent = core.router != null
|
||||
me = core.me.getHumanReadableName()
|
||||
core.eventBus.register(UIResultEvent.class, this)
|
||||
core.eventBus.register(UIResultBatchEvent.class, this)
|
||||
core.eventBus.register(DownloadStartedEvent.class, this)
|
||||
core.eventBus.register(ConnectionEvent.class, this)
|
||||
@@ -233,9 +248,7 @@ class MainFrameModel {
|
||||
|
||||
void onAllFilesLoadedEvent(AllFilesLoadedEvent e) {
|
||||
runInsideUIAsync {
|
||||
watched.addAll(core.muOptions.watchedDirectories)
|
||||
builder.getVariable("watched-directories-table").model.fireTableDataChanged()
|
||||
watched.each { core.eventBus.publish(new FileSharedEvent(file : new File(it))) }
|
||||
core.muOptions.watchedDirectories.each { core.eventBus.publish(new FileSharedEvent(file : new File(it))) }
|
||||
|
||||
core.muOptions.trustSubscriptions.each {
|
||||
core.eventBus.publish(new TrustSubscriptionEvent(persona : it, subscribe : true))
|
||||
@@ -303,7 +316,6 @@ class MainFrameModel {
|
||||
|
||||
void onFileHashingEvent(FileHashingEvent e) {
|
||||
runInsideUIAsync {
|
||||
loadedFiles = shared.size()
|
||||
hashingFile = e.hashingFile
|
||||
}
|
||||
}
|
||||
@@ -319,6 +331,8 @@ class MainFrameModel {
|
||||
loadedFiles = shared.size()
|
||||
JTable table = builder.getVariable("shared-files-table")
|
||||
table.model.fireTableDataChanged()
|
||||
insertIntoTree(e.sharedFile)
|
||||
loadedFiles = fileToNode.size()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,6 +342,8 @@ class MainFrameModel {
|
||||
loadedFiles = shared.size()
|
||||
JTable table = builder.getVariable("shared-files-table")
|
||||
table.model.fireTableDataChanged()
|
||||
insertIntoTree(e.loadedFile)
|
||||
loadedFiles = fileToNode.size()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,8 +351,26 @@ class MainFrameModel {
|
||||
runInsideUIAsync {
|
||||
shared.remove(e.unsharedFile)
|
||||
loadedFiles = shared.size()
|
||||
JTable table = builder.getVariable("shared-files-table")
|
||||
table.model.fireTableDataChanged()
|
||||
|
||||
def dmtn = fileToNode.remove(e.unsharedFile)
|
||||
if (dmtn != null) {
|
||||
loadedFiles = fileToNode.size()
|
||||
while (true) {
|
||||
def parent = dmtn.getParent()
|
||||
parent.remove(dmtn)
|
||||
if (parent == treeRoot)
|
||||
break
|
||||
if (parent.getChildCount() == 0) {
|
||||
File file = parent.getUserObject().file
|
||||
if (core.muOptions.watchedDirectories.contains(file.toString()))
|
||||
core.eventBus.publish(new DirectoryUnsharedEvent(directory : parent.getUserObject().file))
|
||||
dmtn = parent
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
view.refreshSharedFiles()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,8 +513,44 @@ class MainFrameModel {
|
||||
shared << e.downloadedFile
|
||||
JTable table = builder.getVariable("shared-files-table")
|
||||
table.model.fireTableDataChanged()
|
||||
insertIntoTree(e.downloadedFile)
|
||||
loadedFiles = fileToNode.size()
|
||||
}
|
||||
}
|
||||
|
||||
private void insertIntoTree(SharedFile file) {
|
||||
List<File> parents = new ArrayList<>()
|
||||
File tmp = file.file.getParentFile()
|
||||
while(tmp.getParent() != null) {
|
||||
parents << tmp
|
||||
tmp = tmp.getParentFile()
|
||||
}
|
||||
Collections.reverse(parents)
|
||||
TreeNode node = treeRoot
|
||||
for(File path : parents) {
|
||||
boolean exists = false
|
||||
def children = node.children()
|
||||
def child = null
|
||||
while(children.hasMoreElements()) {
|
||||
child = children.nextElement()
|
||||
def userObject = child.getUserObject()
|
||||
if (userObject != null && userObject.file == path) {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!exists) {
|
||||
child = new DefaultMutableTreeNode(new InterimTreeNode(path))
|
||||
node.add(child)
|
||||
}
|
||||
node = child
|
||||
}
|
||||
|
||||
def dmtn = new DefaultMutableTreeNode(file)
|
||||
fileToNode.put(file, dmtn)
|
||||
node.add(dmtn)
|
||||
view.refreshSharedFiles()
|
||||
}
|
||||
|
||||
private static class UIConnection {
|
||||
Destination destination
|
||||
|
@@ -13,7 +13,10 @@ class OptionsModel {
|
||||
@Observable String updateCheckInterval
|
||||
@Observable boolean autoDownloadUpdate
|
||||
@Observable boolean shareDownloadedFiles
|
||||
@Observable boolean shareHiddenFiles
|
||||
@Observable String downloadLocation
|
||||
@Observable boolean searchComments
|
||||
@Observable boolean browseFiles
|
||||
|
||||
// i2p options
|
||||
@Observable String inboundLength
|
||||
@@ -27,6 +30,8 @@ class OptionsModel {
|
||||
@Observable boolean showMonitor
|
||||
@Observable String lnf
|
||||
@Observable String font
|
||||
@Observable boolean automaticFontSize
|
||||
@Observable int customFontSize
|
||||
@Observable boolean clearCancelledDownloads
|
||||
@Observable boolean clearFinishedDownloads
|
||||
@Observable boolean excludeLocalResult
|
||||
@@ -49,7 +54,10 @@ class OptionsModel {
|
||||
updateCheckInterval = settings.updateCheckInterval
|
||||
autoDownloadUpdate = settings.autoDownloadUpdate
|
||||
shareDownloadedFiles = settings.shareDownloadedFiles
|
||||
shareHiddenFiles = settings.shareHiddenFiles
|
||||
downloadLocation = settings.downloadLocation.getAbsolutePath()
|
||||
searchComments = settings.searchComments
|
||||
browseFiles = settings.browseFiles
|
||||
|
||||
Core core = application.context.get("core")
|
||||
inboundLength = core.i2pOptions["inbound.length"]
|
||||
@@ -63,6 +71,8 @@ class OptionsModel {
|
||||
showMonitor = uiSettings.showMonitor
|
||||
lnf = uiSettings.lnf
|
||||
font = uiSettings.font
|
||||
automaticFontSize = uiSettings.autoFontSize
|
||||
customFontSize = uiSettings.fontSize
|
||||
clearCancelledDownloads = uiSettings.clearCancelledDownloads
|
||||
clearFinishedDownloads = uiSettings.clearFinishedDownloads
|
||||
excludeLocalResult = uiSettings.excludeLocalResult
|
||||
|
@@ -21,6 +21,8 @@ class SearchTabModel {
|
||||
|
||||
@Observable boolean downloadActionEnabled
|
||||
@Observable boolean trustButtonsEnabled
|
||||
@Observable boolean browseActionEnabled
|
||||
@Observable boolean viewCommentActionEnabled
|
||||
|
||||
Core core
|
||||
UISettings uiSettings
|
||||
|
BIN
gui/griffon-app/resources/comment.png
Normal file
BIN
gui/griffon-app/resources/comment.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 598 B |
200
gui/griffon-app/views/com/muwire/gui/BrowseView.groovy
Normal file
200
gui/griffon-app/views/com/muwire/gui/BrowseView.groovy
Normal file
@@ -0,0 +1,200 @@
|
||||
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()})
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
@@ -16,11 +16,14 @@ import javax.swing.JMenuItem
|
||||
import javax.swing.JPopupMenu
|
||||
import javax.swing.JSplitPane
|
||||
import javax.swing.JTable
|
||||
import javax.swing.JTree
|
||||
import javax.swing.ListSelectionModel
|
||||
import javax.swing.SwingConstants
|
||||
import javax.swing.TransferHandler
|
||||
import javax.swing.border.Border
|
||||
import javax.swing.table.DefaultTableCellRenderer
|
||||
import javax.swing.tree.TreeNode
|
||||
import javax.swing.tree.TreePath
|
||||
|
||||
import com.muwire.core.Constants
|
||||
import com.muwire.core.MuWireSettings
|
||||
@@ -56,11 +59,12 @@ class MainFrameView {
|
||||
def downloadsTable
|
||||
def lastDownloadSortEvent
|
||||
def lastSharedSortEvent
|
||||
def lastWatchedSortEvent
|
||||
def trustTablesSortEvents = [:]
|
||||
|
||||
UISettings settings
|
||||
|
||||
void initUI() {
|
||||
UISettings settings = application.context.get("ui-settings")
|
||||
settings = application.context.get("ui-settings")
|
||||
builder.with {
|
||||
application(size : [1024,768], id: 'main-frame',
|
||||
locationRelativeTo : null,
|
||||
@@ -193,44 +197,46 @@ class MainFrameView {
|
||||
})
|
||||
}
|
||||
panel (border : etchedBorder(), constraints : BorderLayout.CENTER) {
|
||||
gridLayout(cols : 2, rows : 1)
|
||||
panel {
|
||||
borderLayout()
|
||||
scrollPane (constraints : BorderLayout.CENTER) {
|
||||
table(id : "watched-directories-table", autoCreateRowSorter: true) {
|
||||
tableModel(list : model.watched) {
|
||||
closureColumn(header: "Watched Directories", type : String, read : { it })
|
||||
borderLayout()
|
||||
panel (id : "shared-files-panel", constraints : BorderLayout.CENTER){
|
||||
cardLayout()
|
||||
panel (constraints : "shared files table") {
|
||||
borderLayout()
|
||||
scrollPane(constraints : BorderLayout.CENTER) {
|
||||
table(id : "shared-files-table", autoCreateRowSorter: true) {
|
||||
tableModel(list : model.shared) {
|
||||
closureColumn(header : "Name", preferredWidth : 500, type : String, read : {row -> row.getCachedPath()})
|
||||
closureColumn(header : "Size", preferredWidth : 100, type : Long, read : {row -> row.getCachedLength() })
|
||||
closureColumn(header : "Comments", preferredWidth : 100, type : Boolean, read : {it.getComment() != null})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
panel {
|
||||
borderLayout()
|
||||
scrollPane(constraints : BorderLayout.CENTER) {
|
||||
table(id : "shared-files-table", autoCreateRowSorter: true) {
|
||||
tableModel(list : model.shared) {
|
||||
closureColumn(header : "Name", preferredWidth : 500, type : String, read : {row -> row.getCachedPath()})
|
||||
closureColumn(header : "Size", preferredWidth : 100, type : Long, read : {row -> row.getCachedLength() })
|
||||
closureColumn(header : "Comments", preferredWidth : 100, type : Boolean, read : {it.getComment() != null})
|
||||
}
|
||||
panel (constraints : "shared files tree") {
|
||||
borderLayout()
|
||||
scrollPane(constraints : BorderLayout.CENTER) {
|
||||
def jtree = new JTree(model.sharedTree)
|
||||
jtree.setCellRenderer(new SharedTreeRenderer())
|
||||
tree(id : "shared-files-tree", rootVisible : false, expandsSelectedPaths: true, jtree)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
panel (constraints : BorderLayout.SOUTH) {
|
||||
gridLayout(rows:1, cols:2)
|
||||
gridLayout(rows:1, cols:3)
|
||||
panel {
|
||||
button(text : "Add directories to watch", actionPerformed : watchDirectories)
|
||||
button(text : "Share files", actionPerformed : shareFiles)
|
||||
buttonGroup(id : "sharedViewType")
|
||||
radioButton(text : "Tree", selected : true, buttonGroup : sharedViewType, actionPerformed : showSharedFilesTree)
|
||||
radioButton(text : "Table", selected : false, buttonGroup : sharedViewType, actionPerformed : showSharedFilesTable)
|
||||
}
|
||||
panel {
|
||||
button(text : "Share files", actionPerformed : shareFiles)
|
||||
button(text : "Add Comment", enabled : bind {model.addCommentButtonEnabled}, addCommentAction)
|
||||
}
|
||||
panel {
|
||||
gridLayout(rows : 1, cols : 2)
|
||||
panel {
|
||||
label("Shared:")
|
||||
label(text : bind {model.loadedFiles.toString()})
|
||||
}
|
||||
panel {
|
||||
button(text : "Add Comment", enabled : bind {model.addCommentButtonEnabled}, addCommentAction)
|
||||
label(text : bind {model.loadedFiles}, id : "shared-files-count")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -411,10 +417,7 @@ class MainFrameView {
|
||||
public boolean importData(TransferHandler.TransferSupport support) {
|
||||
def files = support.getTransferable().getTransferData(DataFlavor.javaFileListFlavor)
|
||||
files.each {
|
||||
if (it.isDirectory())
|
||||
watchDirectory(it)
|
||||
else
|
||||
model.core.eventBus.publish(new FileSharedEvent(file : it))
|
||||
model.core.eventBus.publish(new FileSharedEvent(file : it))
|
||||
}
|
||||
showUploadsWindow.call()
|
||||
true
|
||||
@@ -489,13 +492,7 @@ class MainFrameView {
|
||||
}
|
||||
})
|
||||
|
||||
// shared files table
|
||||
def sharedFilesTable = builder.getVariable("shared-files-table")
|
||||
sharedFilesTable.columnModel.getColumn(1).setCellRenderer(new SizeRenderer())
|
||||
|
||||
sharedFilesTable.rowSorter.addRowSorterListener({evt -> lastSharedSortEvent = evt})
|
||||
sharedFilesTable.rowSorter.setSortsOnUpdates(true)
|
||||
|
||||
// shared files menu
|
||||
JPopupMenu sharedFilesMenu = new JPopupMenu()
|
||||
JMenuItem copyHashToClipboard = new JMenuItem("Copy hash to clipboard")
|
||||
copyHashToClipboard.addActionListener({mvcGroup.view.copyHashToClipboard()})
|
||||
@@ -506,7 +503,8 @@ class MainFrameView {
|
||||
JMenuItem commentSelectedFiles = new JMenuItem("Comment selected files")
|
||||
commentSelectedFiles.addActionListener({mvcGroup.controller.addComment()})
|
||||
sharedFilesMenu.add(commentSelectedFiles)
|
||||
sharedFilesTable.addMouseListener(new MouseAdapter() {
|
||||
|
||||
def sharedFilesMouseListener = new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
if (e.isPopupTrigger())
|
||||
@@ -517,7 +515,16 @@ class MainFrameView {
|
||||
if (e.isPopupTrigger())
|
||||
showPopupMenu(sharedFilesMenu, e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// shared files table and tree
|
||||
def sharedFilesTable = builder.getVariable("shared-files-table")
|
||||
sharedFilesTable.columnModel.getColumn(1).setCellRenderer(new SizeRenderer())
|
||||
|
||||
sharedFilesTable.rowSorter.addRowSorterListener({evt -> lastSharedSortEvent = evt})
|
||||
sharedFilesTable.rowSorter.setSortsOnUpdates(true)
|
||||
|
||||
sharedFilesTable.addMouseListener(sharedFilesMouseListener)
|
||||
|
||||
selectionModel = sharedFilesTable.getSelectionModel()
|
||||
selectionModel.addListSelectionListener({
|
||||
@@ -526,6 +533,14 @@ class MainFrameView {
|
||||
return
|
||||
model.addCommentButtonEnabled = true
|
||||
})
|
||||
def sharedFilesTree = builder.getVariable("shared-files-tree")
|
||||
sharedFilesTree.addMouseListener(sharedFilesMouseListener)
|
||||
|
||||
sharedFilesTree.addTreeSelectionListener({
|
||||
def selectedNode = sharedFilesTree.getLastSelectedPathComponent()
|
||||
model.addCommentButtonEnabled = selectedNode != null
|
||||
|
||||
})
|
||||
|
||||
// searches table
|
||||
def searchesTable = builder.getVariable("searches-table")
|
||||
@@ -555,27 +570,6 @@ class MainFrameView {
|
||||
}
|
||||
})
|
||||
|
||||
// watched directories table
|
||||
def watchedTable = builder.getVariable("watched-directories-table")
|
||||
watchedTable.rowSorter.addRowSorterListener({evt -> lastWatchedSortEvent = evt})
|
||||
watchedTable.rowSorter.setSortsOnUpdates(true)
|
||||
JPopupMenu watchedMenu = new JPopupMenu()
|
||||
JMenuItem stopWatching = new JMenuItem("Stop sharing")
|
||||
stopWatching.addActionListener({mvcGroup.controller.stopWatchingDirectory()})
|
||||
watchedMenu.add(stopWatching)
|
||||
watchedTable.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
if (e.isPopupTrigger())
|
||||
showPopupMenu(watchedMenu, e)
|
||||
}
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
if (e.isPopupTrigger())
|
||||
showPopupMenu(watchedMenu, e)
|
||||
}
|
||||
})
|
||||
|
||||
// subscription table
|
||||
def subscriptionTable = builder.getVariable("subscription-table")
|
||||
subscriptionTable.setDefaultRenderer(Integer.class, centerRenderer)
|
||||
@@ -649,6 +643,9 @@ class MainFrameView {
|
||||
model.markNeutralFromDistrustedButtonEnabled = true
|
||||
}
|
||||
})
|
||||
|
||||
// show tree by default
|
||||
showSharedFilesTree.call()
|
||||
}
|
||||
|
||||
private static void showPopupMenu(JPopupMenu menu, MouseEvent event) {
|
||||
@@ -656,22 +653,42 @@ class MainFrameView {
|
||||
}
|
||||
|
||||
def selectedSharedFiles() {
|
||||
def sharedFilesTable = builder.getVariable("shared-files-table")
|
||||
int[] selected = sharedFilesTable.getSelectedRows()
|
||||
if (selected.length == 0)
|
||||
return null
|
||||
List<SharedFile> rv = new ArrayList<>()
|
||||
if (lastSharedSortEvent != null) {
|
||||
for (int i = 0; i < selected.length; i ++) {
|
||||
selected[i] = sharedFilesTable.rowSorter.convertRowIndexToModel(selected[i])
|
||||
if (!model.treeVisible) {
|
||||
def sharedFilesTable = builder.getVariable("shared-files-table")
|
||||
int[] selected = sharedFilesTable.getSelectedRows()
|
||||
if (selected.length == 0)
|
||||
return null
|
||||
List<SharedFile> rv = new ArrayList<>()
|
||||
if (lastSharedSortEvent != null) {
|
||||
for (int i = 0; i < selected.length; i ++) {
|
||||
selected[i] = sharedFilesTable.rowSorter.convertRowIndexToModel(selected[i])
|
||||
}
|
||||
}
|
||||
selected.each {
|
||||
rv.add(model.shared[it])
|
||||
}
|
||||
return rv
|
||||
} else {
|
||||
def sharedFilesTree = builder.getVariable("shared-files-tree")
|
||||
List<SharedFile> rv = new ArrayList<>()
|
||||
for (TreePath path : sharedFilesTree.getSelectionPaths()) {
|
||||
getLeafs(path.getLastPathComponent(), rv)
|
||||
}
|
||||
return rv
|
||||
}
|
||||
selected.each {
|
||||
rv.add(model.shared[it])
|
||||
}
|
||||
rv
|
||||
}
|
||||
|
||||
|
||||
private static void getLeafs(TreeNode node, List<SharedFile> dest) {
|
||||
if (node.isLeaf()) {
|
||||
dest.add(node.getUserObject())
|
||||
return
|
||||
}
|
||||
def children = node.children()
|
||||
while(children.hasMoreElements()) {
|
||||
getLeafs(children.nextElement(), dest)
|
||||
}
|
||||
}
|
||||
|
||||
def copyHashToClipboard() {
|
||||
def selectedFiles = selectedSharedFiles()
|
||||
if (selectedFiles == null)
|
||||
@@ -820,53 +837,34 @@ class MainFrameView {
|
||||
model.monitorPaneButtonEnabled = true
|
||||
model.trustPaneButtonEnabled = false
|
||||
}
|
||||
|
||||
def showSharedFilesTable = {
|
||||
model.treeVisible = false
|
||||
def cardsPanel = builder.getVariable("shared-files-panel")
|
||||
cardsPanel.getLayout().show(cardsPanel, "shared files table")
|
||||
}
|
||||
|
||||
def showSharedFilesTree = {
|
||||
model.treeVisible = true
|
||||
def cardsPanel = builder.getVariable("shared-files-panel")
|
||||
cardsPanel.getLayout().show(cardsPanel, "shared files tree")
|
||||
}
|
||||
|
||||
def shareFiles = {
|
||||
def chooser = new JFileChooser()
|
||||
chooser.setFileHidingEnabled(false)
|
||||
chooser.setFileHidingEnabled(!model.core.muOptions.shareHiddenFiles)
|
||||
chooser.setDialogTitle("Select file to share")
|
||||
chooser.setFileSelectionMode(JFileChooser.FILES_ONLY)
|
||||
chooser.setMultiSelectionEnabled(true)
|
||||
int rv = chooser.showOpenDialog(null)
|
||||
if (rv == JFileChooser.APPROVE_OPTION) {
|
||||
chooser.getSelectedFiles().each {
|
||||
model.core.eventBus.publish(new FileSharedEvent(file : it))
|
||||
File canonical = it.getCanonicalFile()
|
||||
model.core.eventBus.publish(new FileSharedEvent(file : canonical))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def watchDirectories = {
|
||||
def chooser = new JFileChooser()
|
||||
chooser.setFileHidingEnabled(false)
|
||||
chooser.setDialogTitle("Select directory to watch")
|
||||
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY)
|
||||
chooser.setMultiSelectionEnabled(true)
|
||||
int rv = chooser.showOpenDialog(null)
|
||||
if (rv == JFileChooser.APPROVE_OPTION) {
|
||||
chooser.getSelectedFiles().each { f ->
|
||||
watchDirectory(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void watchDirectory(File f) {
|
||||
model.watched << f.getAbsolutePath()
|
||||
application.context.get("muwire-settings").watchedDirectories << f.getAbsolutePath()
|
||||
mvcGroup.controller.saveMuWireSettings()
|
||||
builder.getVariable("watched-directories-table").model.fireTableDataChanged()
|
||||
model.core.eventBus.publish(new FileSharedEvent(file : f))
|
||||
}
|
||||
|
||||
String getSelectedWatchedDirectory() {
|
||||
def watchedTable = builder.getVariable("watched-directories-table")
|
||||
int selectedRow = watchedTable.getSelectedRow()
|
||||
if (selectedRow < 0)
|
||||
return null
|
||||
if (lastWatchedSortEvent != null)
|
||||
selectedRow = watchedTable.rowSorter.convertRowIndexToModel(selectedRow)
|
||||
model.watched[selectedRow]
|
||||
}
|
||||
|
||||
int getSelectedTrustTablesRow(String tableName) {
|
||||
def table = builder.getVariable(tableName)
|
||||
int selectedRow = table.getSelectedRow()
|
||||
@@ -876,4 +874,12 @@ class MainFrameView {
|
||||
selectedRow = table.rowSorter.convertRowIndexToModel(selectedRow)
|
||||
selectedRow
|
||||
}
|
||||
|
||||
public void refreshSharedFiles() {
|
||||
def tree = builder.getVariable("shared-files-tree")
|
||||
TreePath[] selectedPaths = tree.getSelectionPaths()
|
||||
model.sharedTree.nodeStructureChanged(model.treeRoot)
|
||||
tree.setSelectionPaths(selectedPaths)
|
||||
builder.getVariable("shared-files-table").model.fireTableDataChanged()
|
||||
}
|
||||
}
|
@@ -12,6 +12,7 @@ import javax.swing.SwingConstants
|
||||
import com.muwire.core.Core
|
||||
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.GridBagConstraints
|
||||
import java.awt.event.WindowAdapter
|
||||
import java.awt.event.WindowEvent
|
||||
|
||||
@@ -35,6 +36,9 @@ class OptionsView {
|
||||
def updateField
|
||||
def autoDownloadUpdateCheckbox
|
||||
def shareDownloadedCheckbox
|
||||
def shareHiddenCheckbox
|
||||
def searchCommentsCheckbox
|
||||
def browseFilesCheckbox
|
||||
|
||||
def inboundLengthField
|
||||
def inboundQuantityField
|
||||
@@ -46,6 +50,7 @@ class OptionsView {
|
||||
def lnfField
|
||||
def monitorCheckbox
|
||||
def fontField
|
||||
def fontSizeField
|
||||
def clearCancelledDownloadsCheckbox
|
||||
def clearFinishedDownloadsCheckbox
|
||||
def excludeLocalResultCheckbox
|
||||
@@ -69,23 +74,32 @@ class OptionsView {
|
||||
d.setResizable(false)
|
||||
p = builder.panel {
|
||||
gridBagLayout()
|
||||
label(text : "Retry failed downloads every", constraints : gbc(gridx: 0, gridy: 0))
|
||||
retryField = textField(text : bind { model.downloadRetryInterval }, columns : 2, constraints : gbc(gridx: 1, gridy: 0))
|
||||
label(text : "seconds", constraints : gbc(gridx : 2, gridy: 0))
|
||||
label(text : "Search in comments", constraints:gbc(gridx: 0, gridy:0))
|
||||
searchCommentsCheckbox = checkBox(selected : bind {model.searchComments}, constraints : gbc(gridx:1, gridy:0))
|
||||
|
||||
label(text : "Retry failed downloads every", constraints : gbc(gridx: 0, gridy: 1))
|
||||
retryField = textField(text : bind { model.downloadRetryInterval }, columns : 2, constraints : gbc(gridx: 1, gridy: 1))
|
||||
label(text : "seconds", constraints : gbc(gridx : 2, gridy: 1))
|
||||
|
||||
label(text : "Check for updates every", constraints : gbc(gridx : 0, gridy: 1))
|
||||
updateField = textField(text : bind {model.updateCheckInterval }, columns : 2, constraints : gbc(gridx : 1, gridy: 1))
|
||||
label(text : "hours", constraints : gbc(gridx: 2, gridy : 1))
|
||||
label(text : "Check for updates every", constraints : gbc(gridx : 0, gridy: 2))
|
||||
updateField = textField(text : bind {model.updateCheckInterval }, columns : 2, constraints : gbc(gridx : 1, gridy: 2))
|
||||
label(text : "hours", constraints : gbc(gridx: 2, gridy : 2))
|
||||
|
||||
label(text : "Download updates automatically", constraints: gbc(gridx :0, gridy : 2))
|
||||
autoDownloadUpdateCheckbox = checkBox(selected : bind {model.autoDownloadUpdate}, constraints : gbc(gridx:1, gridy : 2))
|
||||
label(text : "Download updates automatically", constraints: gbc(gridx :0, gridy : 3))
|
||||
autoDownloadUpdateCheckbox = checkBox(selected : bind {model.autoDownloadUpdate}, constraints : gbc(gridx:1, gridy : 3))
|
||||
|
||||
label(text : "Share downloaded files", constraints : gbc(gridx : 0, gridy:3))
|
||||
shareDownloadedCheckbox = checkBox(selected : bind {model.shareDownloadedFiles}, constraints : gbc(gridx :1, gridy:3))
|
||||
label(text : "Share downloaded files", constraints : gbc(gridx : 0, gridy:4))
|
||||
shareDownloadedCheckbox = checkBox(selected : bind {model.shareDownloadedFiles}, constraints : gbc(gridx :1, gridy:4))
|
||||
|
||||
label(text : "Share hidden files", constraints : gbc(gridx : 0, gridy:5))
|
||||
shareHiddenCheckbox = checkBox(selected : bind {model.shareHiddenFiles}, constraints : gbc(gridx :1, gridy:5))
|
||||
|
||||
label(text : "Allow browsing", constraints : gbc(gridx : 0, gridy : 6))
|
||||
browseFilesCheckbox = checkBox(selected : bind {model.browseFiles}, constraints : gbc(gridx : 1, gridy : 6))
|
||||
|
||||
label(text : "Save downloaded files to:", constraints: gbc(gridx:0, gridy:4))
|
||||
button(text : "Choose", constraints : gbc(gridx : 1, gridy:4), downloadLocationAction)
|
||||
label(text : bind {model.downloadLocation}, constraints: gbc(gridx:0, gridy:5, gridwidth:2))
|
||||
label(text : "Save downloaded files to:", constraints: gbc(gridx:0, gridy:7))
|
||||
button(text : "Choose", constraints : gbc(gridx : 1, gridy:7), downloadLocationAction)
|
||||
label(text : bind {model.downloadLocation}, constraints: gbc(gridx:0, gridy:8, gridwidth:2))
|
||||
|
||||
}
|
||||
i = builder.panel {
|
||||
@@ -113,19 +127,28 @@ class OptionsView {
|
||||
gridBagLayout()
|
||||
label(text : "Changing these settings requires a restart", constraints : gbc(gridx : 0, gridy : 0, gridwidth: 2))
|
||||
label(text : "Look And Feel", constraints : gbc(gridx: 0, gridy:1))
|
||||
lnfField = textField(text : bind {model.lnf}, columns : 4, constraints : gbc(gridx : 1, gridy : 1))
|
||||
lnfField = textField(text : bind {model.lnf}, columns : 4, constraints : gbc(gridx : 1, gridy : 1, anchor : GridBagConstraints.LINE_START))
|
||||
label(text : "Font", constraints : gbc(gridx: 0, gridy : 2))
|
||||
fontField = textField(text : bind {model.font}, columns : 4, constraints : gbc(gridx : 1, gridy:2))
|
||||
// label(text : "Show Monitor", constraints : gbc(gridx :0, gridy: 3))
|
||||
// monitorCheckbox = checkBox(selected : bind {model.showMonitor}, constraints : gbc(gridx : 1, gridy: 3))
|
||||
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 : "Automatically Clear Finished Downloads", constraints: gbc(gridx: 0, gridy:5))
|
||||
clearFinishedDownloadsCheckbox = checkBox(selected : bind {model.clearFinishedDownloads}, constraints : gbc(gridx : 1, gridy:5))
|
||||
label(text : "Exclude Local Files From Results", constraints: gbc(gridx:0, gridy:6))
|
||||
excludeLocalResultCheckbox = checkBox(selected : bind {model.excludeLocalResult}, constraints : gbc(gridx: 1, gridy : 6))
|
||||
// label(text : "Show Hash Searches In Monitor", constraints: gbc(gridx:0, gridy:7))
|
||||
// showSearchHashesCheckbox = checkBox(selected : bind {model.showSearchHashes}, constraints : gbc(gridx: 1, gridy: 7))
|
||||
fontField = textField(text : bind {model.font}, columns : 4, constraints : gbc(gridx : 1, gridy:2, anchor : GridBagConstraints.LINE_START))
|
||||
|
||||
label(text : "Font Size", constraints : gbc(gridx: 0, gridy : 3))
|
||||
buttonGroup(id: "fontSizeGroup")
|
||||
radioButton(text: "Automatic", selected : bind {model.automaticFontSize}, buttonGroup : fontSizeGroup,
|
||||
constraints : gbc(gridx : 1, gridy: 3, anchor : GridBagConstraints.LINE_START), automaticFontAction)
|
||||
radioButton(text: "Custom", selected : bind {!model.automaticFontSize}, buttonGroup : fontSizeGroup,
|
||||
constraints : gbc(gridx : 1, gridy: 4, anchor : GridBagConstraints.LINE_START), customFontAction)
|
||||
fontSizeField = textField(text : bind {model.customFontSize}, enabled : bind {!model.automaticFontSize}, constraints : gbc(gridx : 2, gridy : 4))
|
||||
|
||||
label(text : "Automatically Clear Cancelled Downloads", constraints: gbc(gridx: 0, gridy:5))
|
||||
clearCancelledDownloadsCheckbox = checkBox(selected : bind {model.clearCancelledDownloads},
|
||||
constraints : gbc(gridx : 1, gridy:5, anchor : GridBagConstraints.LINE_START))
|
||||
label(text : "Automatically Clear Finished Downloads", constraints: gbc(gridx: 0, gridy:6))
|
||||
clearFinishedDownloadsCheckbox = checkBox(selected : bind {model.clearFinishedDownloads},
|
||||
constraints : gbc(gridx : 1, gridy:6, anchor : GridBagConstraints.LINE_START))
|
||||
label(text : "Exclude Local Files From Results", constraints: gbc(gridx:0, gridy:7))
|
||||
excludeLocalResultCheckbox = checkBox(selected : bind {model.excludeLocalResult},
|
||||
constraints : gbc(gridx: 1, gridy : 7, anchor : GridBagConstraints.LINE_START))
|
||||
|
||||
}
|
||||
bandwidth = builder.panel {
|
||||
gridBagLayout()
|
||||
|
@@ -63,6 +63,7 @@ class SearchTabView {
|
||||
tableModel(list : model.senders) {
|
||||
closureColumn(header : "Sender", preferredWidth : 500, type: String, read : {row -> row.getHumanReadableName()})
|
||||
closureColumn(header : "Results", preferredWidth : 20, type: Integer, read : {row -> model.sendersBucket[row].size()})
|
||||
closureColumn(header : "Browse", preferredWidth : 20, type: Boolean, read : {row -> model.sendersBucket[row].first().browse})
|
||||
closureColumn(header : "Trust", preferredWidth : 50, type: String, read : { row ->
|
||||
model.core.trustService.getLevel(row.destination).toString()
|
||||
})
|
||||
@@ -70,9 +71,15 @@ class SearchTabView {
|
||||
}
|
||||
}
|
||||
panel(constraints : BorderLayout.SOUTH) {
|
||||
button(text : "Trust", enabled: bind {model.trustButtonsEnabled }, trustAction)
|
||||
button(text : "Neutral", enabled: bind {model.trustButtonsEnabled}, neutralAction)
|
||||
button(text : "Distrust", enabled : bind {model.trustButtonsEnabled}, distrustAction)
|
||||
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 : "Neutral", enabled: bind {model.trustButtonsEnabled}, neutralAction)
|
||||
button(text : "Distrust", enabled : bind {model.trustButtonsEnabled}, distrustAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
panel {
|
||||
@@ -93,6 +100,7 @@ class SearchTabView {
|
||||
panel()
|
||||
panel {
|
||||
button(text : "Download", enabled : bind {model.downloadActionEnabled}, downloadAction)
|
||||
button(text : "View Comment", enabled : bind {model.viewCommentActionEnabled}, showCommentAction)
|
||||
}
|
||||
panel {
|
||||
gridBagLayout()
|
||||
@@ -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
|
||||
sendersTable.setDefaultRenderer(Integer.class, centerRenderer)
|
||||
sendersTable.rowSorter.addRowSorterListener({evt -> lastSendersSortEvent = evt})
|
||||
@@ -193,12 +211,14 @@ class SearchTabView {
|
||||
int row = selectedSenderRow()
|
||||
if (row < 0) {
|
||||
model.trustButtonsEnabled = false
|
||||
model.browseActionEnabled = false
|
||||
return
|
||||
} else {
|
||||
Persona sender = model.senders[row]
|
||||
model.browseActionEnabled = model.sendersBucket[sender].first().browse
|
||||
model.trustButtonsEnabled = true
|
||||
model.results.clear()
|
||||
Persona p = model.senders[row]
|
||||
model.results.addAll(model.sendersBucket[p])
|
||||
model.results.addAll(model.sendersBucket[sender])
|
||||
resultsTable.model.fireTableDataChanged()
|
||||
}
|
||||
})
|
||||
@@ -226,50 +246,49 @@ class SearchTabView {
|
||||
JMenuItem copyHashToClipboard = new JMenuItem("Copy hash to clipboard")
|
||||
copyHashToClipboard.addActionListener({mvcGroup.view.copyHashToClipboard()})
|
||||
menu.add(copyHashToClipboard)
|
||||
JMenuItem copyNameToClipboard = new JMenuItem("Copy name to clipboard")
|
||||
copyNameToClipboard.addActionListener({mvcGroup.view.copyNameToClipboard()})
|
||||
menu.add(copyNameToClipboard)
|
||||
showMenu = true
|
||||
|
||||
// show comment if any
|
||||
int selectedRow = resultsTable.getSelectedRow()
|
||||
if (lastSortEvent != null)
|
||||
selectedRow = resultsTable.rowSorter.convertRowIndexToModel(selectedRow)
|
||||
if (model.results[selectedRow].comment != null) {
|
||||
JMenuItem showComment = new JMenuItem("Show Comment")
|
||||
showComment.addActionListener({mvcGroup.view.showComment()})
|
||||
if (model.viewCommentActionEnabled) {
|
||||
JMenuItem showComment = new JMenuItem("View Comment")
|
||||
showComment.addActionListener({mvcGroup.controller.showComment()})
|
||||
menu.add(showComment)
|
||||
}
|
||||
}
|
||||
if (showMenu)
|
||||
menu.show(e.getComponent(), e.getX(), e.getY())
|
||||
}
|
||||
|
||||
def copyHashToClipboard() {
|
||||
|
||||
private UIResultEvent getSelectedResult() {
|
||||
int[] selectedRows = resultsTable.getSelectedRows()
|
||||
if (selectedRows.length != 1)
|
||||
return
|
||||
return null
|
||||
int selected = selectedRows[0]
|
||||
if (lastSortEvent != null)
|
||||
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)
|
||||
def clipboard = Toolkit.getDefaultToolkit().getSystemClipboard()
|
||||
clipboard.setContents(selection, null)
|
||||
}
|
||||
|
||||
def showComment() {
|
||||
int selectedRow = resultsTable.getSelectedRow()
|
||||
if (selectedRow < 0)
|
||||
def copyNameToClipboard() {
|
||||
def result = getSelectedResult()
|
||||
if (result == null)
|
||||
return
|
||||
if (lastSortEvent != null)
|
||||
selectedRow = resultsTable.rowSorter.convertRowIndexToModel(selectedRow)
|
||||
UIResultEvent event = model.results[selectedRow]
|
||||
if (event.comment == null)
|
||||
return
|
||||
|
||||
String groupId = Base64.encode(event.infohash.getRoot())
|
||||
Map<String,Object> params = new HashMap<>()
|
||||
params['result'] = event
|
||||
|
||||
mvcGroup.createMVCGroup("show-comment", groupId, params)
|
||||
StringSelection selection = new StringSelection(result.getName())
|
||||
def clipboard = Toolkit.getDefaultToolkit().getSystemClipboard()
|
||||
clipboard.setContents(selection, null)
|
||||
}
|
||||
|
||||
int selectedSenderRow() {
|
||||
|
18
gui/src/main/groovy/com/muwire/gui/InterimTreeNode.groovy
Normal file
18
gui/src/main/groovy/com/muwire/gui/InterimTreeNode.groovy
Normal file
@@ -0,0 +1,18 @@
|
||||
package com.muwire.gui
|
||||
|
||||
class InterimTreeNode {
|
||||
private final File file
|
||||
InterimTreeNode(File file) {
|
||||
this.file = file
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof InterimTreeNode))
|
||||
return false
|
||||
file == o.file
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
file.getName()
|
||||
}
|
||||
}
|
44
gui/src/main/groovy/com/muwire/gui/SharedTreeRenderer.groovy
Normal file
44
gui/src/main/groovy/com/muwire/gui/SharedTreeRenderer.groovy
Normal file
@@ -0,0 +1,44 @@
|
||||
package com.muwire.gui
|
||||
|
||||
import java.awt.Component
|
||||
|
||||
import javax.swing.ImageIcon
|
||||
import javax.swing.JTree
|
||||
import javax.swing.tree.DefaultTreeCellRenderer
|
||||
|
||||
import com.muwire.core.SharedFile
|
||||
|
||||
import net.i2p.data.DataHelper
|
||||
|
||||
class SharedTreeRenderer extends DefaultTreeCellRenderer {
|
||||
|
||||
private final ImageIcon commentIcon
|
||||
|
||||
SharedTreeRenderer() {
|
||||
commentIcon = new ImageIcon((URL) SharedTreeRenderer.class.getResource("/comment.png"))
|
||||
}
|
||||
|
||||
public Component getTreeCellRendererComponent(JTree tree, Object value,
|
||||
boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
|
||||
|
||||
def userObject = value.getUserObject()
|
||||
def defaultRenderer = super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus)
|
||||
if (userObject instanceof InterimTreeNode || userObject == null)
|
||||
return defaultRenderer
|
||||
|
||||
|
||||
SharedFile sf = (SharedFile) userObject
|
||||
String name = sf.getFile().getName()
|
||||
long length = sf.getCachedLength()
|
||||
String formatted = DataHelper.formatSize2Decimal(length, false)+"B"
|
||||
|
||||
|
||||
setText("$name ($formatted)")
|
||||
setEnabled(true)
|
||||
if (sf.comment != null) {
|
||||
setIcon(commentIcon)
|
||||
}
|
||||
|
||||
this
|
||||
}
|
||||
}
|
@@ -5,11 +5,13 @@ class UISettings {
|
||||
String lnf
|
||||
boolean showMonitor
|
||||
String font
|
||||
boolean autoFontSize
|
||||
int fontSize
|
||||
boolean clearCancelledDownloads
|
||||
boolean clearFinishedDownloads
|
||||
boolean excludeLocalResult
|
||||
boolean showSearchHashes
|
||||
|
||||
|
||||
UISettings(Properties props) {
|
||||
lnf = props.getProperty("lnf", "system")
|
||||
showMonitor = Boolean.parseBoolean(props.getProperty("showMonitor", "false"))
|
||||
@@ -18,6 +20,8 @@ class UISettings {
|
||||
clearFinishedDownloads = Boolean.parseBoolean(props.getProperty("clearFinishedDownloads","false"))
|
||||
excludeLocalResult = Boolean.parseBoolean(props.getProperty("excludeLocalResult","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 {
|
||||
@@ -28,6 +32,8 @@ class UISettings {
|
||||
props.setProperty("clearFinishedDownloads", String.valueOf(clearFinishedDownloads))
|
||||
props.setProperty("excludeLocalResult", String.valueOf(excludeLocalResult))
|
||||
props.setProperty("showSearchHashes", String.valueOf(showSearchHashes))
|
||||
props.setProperty("autoFontSize", String.valueOf(autoFontSize))
|
||||
props.setProperty("fontSize", String.valueOf(fontSize))
|
||||
if (font != null)
|
||||
props.setProperty("font", font)
|
||||
|
||||
|
Reference in New Issue
Block a user