Compare commits
1 Commits
source-tra
...
fix-possib
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7718dc0821 |
@@ -4,7 +4,7 @@ MuWire is an easy to use file-sharing program which offers anonymity using [I2P
|
|||||||
|
|
||||||
It is inspired by the LimeWire Gnutella client and developped by a former LimeWire developer.
|
It is inspired by the LimeWire Gnutella client and developped by a former LimeWire developer.
|
||||||
|
|
||||||
The current stable release - 0.2.5 is avaiable for download at http://muwire.com. You can find technical documentation in the "doc" folder.
|
The current stable release - 0.1.5 is avaiable for download at http://muwire.com. You can find technical documentation in the "doc" folder.
|
||||||
|
|
||||||
### Building
|
### Building
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ Some of the UI tests will fail because they haven't been written yet :-/
|
|||||||
|
|
||||||
### Running
|
### Running
|
||||||
|
|
||||||
You need to have an I2P router up and running on the same machine. After you build the application, look inside "gui/build/distributions". Untar/unzip one of the "shadow" files and then run the jar contained inside by typing "java -jar MuWire-x.y.z.jar" in a terminal or command prompt. If you use a custom I2CP host and port, create a file $HOME/.MuWire/i2p.properties and put "i2cp.tcp.host=<host>" and "i2cp.tcp.port=<port>" in there.
|
You need to have an I2P router up and running on the same machine. After you build the application, look inside "gui/build/distributions". Untar/unzip one of the "shadow" files and then run the jar contained inside by typing "java -jar MuWire-x.y.z.jar" in a terminal or command prompt. If you use a custom I2CP host and port, create a file $HOME/.MuWire/i2p.properties and put "i2cp.tcp.host=<host>" and "i2cp.tcp.post=<port>" in there.
|
||||||
|
|
||||||
The first time you run MuWire it will ask you to select a nickname. This nickname will be displayed with search results, so that others can verify the file was shared by you. It is best to leave MuWire running all the time, just like I2P.
|
The first time you run MuWire it will ask you to select a nickname. This nickname will be displayed with search results, so that others can verify the file was shared by you. It is best to leave MuWire running all the time, just like I2P.
|
||||||
|
|
||||||
|
5
TODO.md
5
TODO.md
@@ -32,10 +32,6 @@ For ease of deployment for new users, and so that users do not need to run a sep
|
|||||||
|
|
||||||
Basically any non-gui non-cli user interface
|
Basically any non-gui non-cli user interface
|
||||||
|
|
||||||
##### Metadata editing and search
|
|
||||||
|
|
||||||
To enable parsing of metadata from known file types and the user editing it or adding manual metadata
|
|
||||||
|
|
||||||
### Small Items
|
### Small Items
|
||||||
|
|
||||||
* Detect if router is dead and show warning or exit
|
* Detect if router is dead and show warning or exit
|
||||||
@@ -43,3 +39,4 @@ To enable parsing of metadata from known file types and the user editing it or a
|
|||||||
* Download file sequentially
|
* Download file sequentially
|
||||||
* Unsharing of files
|
* Unsharing of files
|
||||||
* Multiple-selection download, Ctrl-A
|
* Multiple-selection download, Ctrl-A
|
||||||
|
* Automatic sharing of new files in shared directories (more like medium item)
|
||||||
|
@@ -4,7 +4,6 @@ import java.util.concurrent.CountDownLatch
|
|||||||
|
|
||||||
import com.muwire.core.Core
|
import com.muwire.core.Core
|
||||||
import com.muwire.core.MuWireSettings
|
import com.muwire.core.MuWireSettings
|
||||||
import com.muwire.core.UILoadedEvent
|
|
||||||
import com.muwire.core.connection.ConnectionAttemptStatus
|
import com.muwire.core.connection.ConnectionAttemptStatus
|
||||||
import com.muwire.core.connection.ConnectionEvent
|
import com.muwire.core.connection.ConnectionEvent
|
||||||
import com.muwire.core.connection.DisconnectionEvent
|
import com.muwire.core.connection.DisconnectionEvent
|
||||||
@@ -35,7 +34,7 @@ class Cli {
|
|||||||
|
|
||||||
Core core
|
Core core
|
||||||
try {
|
try {
|
||||||
core = new Core(props, home, "0.2.6")
|
core = new Core(props, home, "0.2.1")
|
||||||
} catch (Exception bad) {
|
} catch (Exception bad) {
|
||||||
bad.printStackTrace(System.out)
|
bad.printStackTrace(System.out)
|
||||||
println "Failed to initialize core, exiting"
|
println "Failed to initialize core, exiting"
|
||||||
@@ -84,8 +83,7 @@ class Cli {
|
|||||||
}
|
}
|
||||||
core.eventBus.register(AllFilesLoadedEvent.class, fileLoader)
|
core.eventBus.register(AllFilesLoadedEvent.class, fileLoader)
|
||||||
core.startServices()
|
core.startServices()
|
||||||
|
|
||||||
core.eventBus.publish(new UILoadedEvent())
|
|
||||||
println "waiting for files to load"
|
println "waiting for files to load"
|
||||||
latch.await()
|
latch.await()
|
||||||
// now we begin
|
// now we begin
|
||||||
|
@@ -53,7 +53,7 @@ class CliDownloader {
|
|||||||
|
|
||||||
Core core
|
Core core
|
||||||
try {
|
try {
|
||||||
core = new Core(props, home, "0.2.6")
|
core = new Core(props, home, "0.2.1")
|
||||||
} catch (Exception bad) {
|
} catch (Exception bad) {
|
||||||
bad.printStackTrace(System.out)
|
bad.printStackTrace(System.out)
|
||||||
println "Failed to initialize core, exiting"
|
println "Failed to initialize core, exiting"
|
||||||
|
@@ -23,7 +23,6 @@ import com.muwire.core.files.FileSharedEvent
|
|||||||
import com.muwire.core.files.FileUnsharedEvent
|
import com.muwire.core.files.FileUnsharedEvent
|
||||||
import com.muwire.core.files.HasherService
|
import com.muwire.core.files.HasherService
|
||||||
import com.muwire.core.files.PersisterService
|
import com.muwire.core.files.PersisterService
|
||||||
import com.muwire.core.files.DirectoryWatcher
|
|
||||||
import com.muwire.core.hostcache.CacheClient
|
import com.muwire.core.hostcache.CacheClient
|
||||||
import com.muwire.core.hostcache.HostCache
|
import com.muwire.core.hostcache.HostCache
|
||||||
import com.muwire.core.hostcache.HostDiscoveredEvent
|
import com.muwire.core.hostcache.HostDiscoveredEvent
|
||||||
@@ -71,7 +70,6 @@ public class Core {
|
|||||||
private final ConnectionEstablisher connectionEstablisher
|
private final ConnectionEstablisher connectionEstablisher
|
||||||
private final HasherService hasherService
|
private final HasherService hasherService
|
||||||
private final DownloadManager downloadManager
|
private final DownloadManager downloadManager
|
||||||
private final DirectoryWatcher directoryWatcher
|
|
||||||
|
|
||||||
public Core(MuWireSettings props, File home, String myVersion) {
|
public Core(MuWireSettings props, File home, String myVersion) {
|
||||||
this.home = home
|
this.home = home
|
||||||
@@ -164,7 +162,6 @@ public class Core {
|
|||||||
|
|
||||||
log.info "initializing persistence service"
|
log.info "initializing persistence service"
|
||||||
persisterService = new PersisterService(new File(home, "files.json"), eventBus, 15000, fileManager)
|
persisterService = new PersisterService(new File(home, "files.json"), eventBus, 15000, fileManager)
|
||||||
eventBus.register(UILoadedEvent.class, persisterService)
|
|
||||||
|
|
||||||
log.info("initializing host cache")
|
log.info("initializing host cache")
|
||||||
File hostStorage = new File(home, "hosts.json")
|
File hostStorage = new File(home, "hosts.json")
|
||||||
@@ -216,9 +213,6 @@ public class Core {
|
|||||||
connectionAcceptor = new ConnectionAcceptor(eventBus, connectionManager, props,
|
connectionAcceptor = new ConnectionAcceptor(eventBus, connectionManager, props,
|
||||||
i2pAcceptor, hostCache, trustService, searchManager, uploadManager, connectionEstablisher)
|
i2pAcceptor, hostCache, trustService, searchManager, uploadManager, connectionEstablisher)
|
||||||
|
|
||||||
log.info("initializing directory watcher")
|
|
||||||
directoryWatcher = new DirectoryWatcher(eventBus, fileManager)
|
|
||||||
eventBus.register(FileSharedEvent.class, directoryWatcher)
|
|
||||||
|
|
||||||
log.info("initializing hasher service")
|
log.info("initializing hasher service")
|
||||||
hasherService = new HasherService(new FileHasher(), eventBus, fileManager)
|
hasherService = new HasherService(new FileHasher(), eventBus, fileManager)
|
||||||
@@ -227,9 +221,9 @@ public class Core {
|
|||||||
|
|
||||||
public void startServices() {
|
public void startServices() {
|
||||||
hasherService.start()
|
hasherService.start()
|
||||||
directoryWatcher.start()
|
|
||||||
trustService.start()
|
trustService.start()
|
||||||
trustService.waitForLoad()
|
trustService.waitForLoad()
|
||||||
|
persisterService.start()
|
||||||
hostCache.start()
|
hostCache.start()
|
||||||
connectionManager.start()
|
connectionManager.start()
|
||||||
cacheClient.start()
|
cacheClient.start()
|
||||||
@@ -246,8 +240,6 @@ public class Core {
|
|||||||
connectionAcceptor.stop()
|
connectionAcceptor.stop()
|
||||||
log.info("shutting down connection establisher")
|
log.info("shutting down connection establisher")
|
||||||
connectionEstablisher.stop()
|
connectionEstablisher.stop()
|
||||||
log.info("shutting down directory watcher")
|
|
||||||
directoryWatcher.stop()
|
|
||||||
log.info("shutting down connection manager")
|
log.info("shutting down connection manager")
|
||||||
connectionManager.shutdown()
|
connectionManager.shutdown()
|
||||||
}
|
}
|
||||||
@@ -276,7 +268,7 @@ public class Core {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Core core = new Core(props, home, "0.2.6")
|
Core core = new Core(props, home, "0.2.1")
|
||||||
core.startServices()
|
core.startServices()
|
||||||
|
|
||||||
// ... at the end, sleep or execute script
|
// ... at the end, sleep or execute script
|
||||||
|
@@ -1,11 +1,6 @@
|
|||||||
package com.muwire.core
|
package com.muwire.core
|
||||||
|
|
||||||
import java.util.stream.Collectors
|
|
||||||
|
|
||||||
import com.muwire.core.hostcache.CrawlerResponse
|
import com.muwire.core.hostcache.CrawlerResponse
|
||||||
import com.muwire.core.util.DataUtil
|
|
||||||
|
|
||||||
import net.i2p.data.Base64
|
|
||||||
|
|
||||||
class MuWireSettings {
|
class MuWireSettings {
|
||||||
|
|
||||||
@@ -15,9 +10,10 @@ class MuWireSettings {
|
|||||||
int updateCheckInterval
|
int updateCheckInterval
|
||||||
String nickname
|
String nickname
|
||||||
File downloadLocation
|
File downloadLocation
|
||||||
|
String sharedFiles
|
||||||
CrawlerResponse crawlerResponse
|
CrawlerResponse crawlerResponse
|
||||||
boolean shareDownloadedFiles
|
boolean shareDownloadedFiles
|
||||||
Set<String> watchedDirectories
|
boolean watchSharedDirectories
|
||||||
|
|
||||||
MuWireSettings() {
|
MuWireSettings() {
|
||||||
this(new Properties())
|
this(new Properties())
|
||||||
@@ -30,16 +26,11 @@ class MuWireSettings {
|
|||||||
nickname = props.getProperty("nickname","MuWireUser")
|
nickname = props.getProperty("nickname","MuWireUser")
|
||||||
downloadLocation = new File((String)props.getProperty("downloadLocation",
|
downloadLocation = new File((String)props.getProperty("downloadLocation",
|
||||||
System.getProperty("user.home")))
|
System.getProperty("user.home")))
|
||||||
|
sharedFiles = props.getProperty("sharedFiles")
|
||||||
downloadRetryInterval = Integer.parseInt(props.getProperty("downloadRetryInterval","15"))
|
downloadRetryInterval = Integer.parseInt(props.getProperty("downloadRetryInterval","15"))
|
||||||
updateCheckInterval = Integer.parseInt(props.getProperty("updateCheckInterval","36"))
|
updateCheckInterval = Integer.parseInt(props.getProperty("updateCheckInterval","36"))
|
||||||
shareDownloadedFiles = Boolean.parseBoolean(props.getProperty("shareDownloadedFiles","true"))
|
shareDownloadedFiles = Boolean.parseBoolean(props.getProperty("shareDownloadedFiles","true"))
|
||||||
|
watchSharedDirectories = Boolean.parseBoolean(props.getProperty("watchSharedDirectories","true"))
|
||||||
watchedDirectories = new HashSet<>()
|
|
||||||
if (props.containsKey("watchedDirectories")) {
|
|
||||||
String[] encoded = props.getProperty("watchedDirectories").split(",")
|
|
||||||
encoded.each { watchedDirectories << DataUtil.readi18nString(Base64.decode(it)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void write(OutputStream out) throws IOException {
|
void write(OutputStream out) throws IOException {
|
||||||
@@ -52,14 +43,9 @@ class MuWireSettings {
|
|||||||
props.setProperty("downloadRetryInterval", String.valueOf(downloadRetryInterval))
|
props.setProperty("downloadRetryInterval", String.valueOf(downloadRetryInterval))
|
||||||
props.setProperty("updateCheckInterval", String.valueOf(updateCheckInterval))
|
props.setProperty("updateCheckInterval", String.valueOf(updateCheckInterval))
|
||||||
props.setProperty("shareDownloadedFiles", String.valueOf(shareDownloadedFiles))
|
props.setProperty("shareDownloadedFiles", String.valueOf(shareDownloadedFiles))
|
||||||
|
props.setProperty("watchSharedDirectories", String.valueOf(watchSharedDirectories))
|
||||||
if (!watchedDirectories.isEmpty()) {
|
if (sharedFiles != null)
|
||||||
String encoded = watchedDirectories.stream().
|
props.setProperty("sharedFiles", sharedFiles)
|
||||||
map({Base64.encode(DataUtil.encodei18nString(it))}).
|
|
||||||
collect(Collectors.joining(","))
|
|
||||||
props.setProperty("watchedDirectories", encoded)
|
|
||||||
}
|
|
||||||
|
|
||||||
props.store(out, "")
|
props.store(out, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -17,8 +17,6 @@ import com.muwire.core.upload.UploadManager
|
|||||||
import com.muwire.core.search.InvalidSearchResultException
|
import com.muwire.core.search.InvalidSearchResultException
|
||||||
import com.muwire.core.search.ResultsParser
|
import com.muwire.core.search.ResultsParser
|
||||||
import com.muwire.core.search.SearchManager
|
import com.muwire.core.search.SearchManager
|
||||||
import com.muwire.core.search.UIResultBatchEvent
|
|
||||||
import com.muwire.core.search.UIResultEvent
|
|
||||||
import com.muwire.core.search.UnexpectedResultsException
|
import com.muwire.core.search.UnexpectedResultsException
|
||||||
|
|
||||||
import groovy.json.JsonOutput
|
import groovy.json.JsonOutput
|
||||||
@@ -227,15 +225,13 @@ class ConnectionAcceptor {
|
|||||||
if (sender.destination != e.getDestination())
|
if (sender.destination != e.getDestination())
|
||||||
throw new IOException("Sender destination mismatch expected $e.getDestination(), got $sender.destination")
|
throw new IOException("Sender destination mismatch expected $e.getDestination(), got $sender.destination")
|
||||||
int nResults = dis.readUnsignedShort()
|
int nResults = dis.readUnsignedShort()
|
||||||
UIResultEvent[] results = new UIResultEvent[nResults]
|
|
||||||
for (int i = 0; i < nResults; i++) {
|
for (int i = 0; i < nResults; i++) {
|
||||||
int jsonSize = dis.readUnsignedShort()
|
int jsonSize = dis.readUnsignedShort()
|
||||||
byte [] payload = new byte[jsonSize]
|
byte [] payload = new byte[jsonSize]
|
||||||
dis.readFully(payload)
|
dis.readFully(payload)
|
||||||
def json = slurper.parse(payload)
|
def json = slurper.parse(payload)
|
||||||
results[i] = ResultsParser.parse(sender, resultsUUID, json)
|
eventBus.publish(ResultsParser.parse(sender, resultsUUID, json))
|
||||||
}
|
}
|
||||||
eventBus.publish(new UIResultBatchEvent(uuid: resultsUUID, results: results))
|
|
||||||
} catch (IOException | UnexpectedResultsException | InvalidSearchResultException bad) {
|
} catch (IOException | UnexpectedResultsException | InvalidSearchResultException bad) {
|
||||||
log.log(Level.WARNING, "failed to process POST", bad)
|
log.log(Level.WARNING, "failed to process POST", bad)
|
||||||
} finally {
|
} finally {
|
||||||
|
@@ -2,6 +2,7 @@ package com.muwire.core.connection
|
|||||||
|
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
|
||||||
import com.muwire.core.EventBus
|
import com.muwire.core.EventBus
|
||||||
import com.muwire.core.MuWireSettings
|
import com.muwire.core.MuWireSettings
|
||||||
@@ -66,7 +67,7 @@ class PeerConnection extends Connection {
|
|||||||
protected void write(Object message) {
|
protected void write(Object message) {
|
||||||
byte[] payload
|
byte[] payload
|
||||||
if (message instanceof Map) {
|
if (message instanceof Map) {
|
||||||
payload = JsonOutput.toJson(message).bytes
|
payload = JsonOutput.toJson(message).getBytes(StandardCharsets.UTF_8)
|
||||||
DataUtil.packHeader(payload.length, writeHeader)
|
DataUtil.packHeader(payload.length, writeHeader)
|
||||||
log.fine "$name writing message type ${message.type} length $payload.length"
|
log.fine "$name writing message type ${message.type} length $payload.length"
|
||||||
writeHeader[0] &= (byte)0x7F
|
writeHeader[0] &= (byte)0x7F
|
||||||
|
@@ -58,8 +58,6 @@ public class DownloadManager {
|
|||||||
e.result.each {
|
e.result.each {
|
||||||
destinations.add(it.sender.destination)
|
destinations.add(it.sender.destination)
|
||||||
}
|
}
|
||||||
destinations.addAll(e.sources)
|
|
||||||
destinations.remove(me.destination)
|
|
||||||
|
|
||||||
def downloader = new Downloader(eventBus, this, me, e.target, size,
|
def downloader = new Downloader(eventBus, this, me, e.target, size,
|
||||||
infohash, pieceSize, connector, destinations,
|
infohash, pieceSize, connector, destinations,
|
||||||
|
@@ -16,7 +16,6 @@ import java.nio.file.Files
|
|||||||
import java.nio.file.StandardOpenOption
|
import java.nio.file.StandardOpenOption
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.security.NoSuchAlgorithmException
|
import java.security.NoSuchAlgorithmException
|
||||||
import java.util.logging.Level
|
|
||||||
|
|
||||||
@Log
|
@Log
|
||||||
class DownloadSession {
|
class DownloadSession {
|
||||||
@@ -24,7 +23,7 @@ class DownloadSession {
|
|||||||
private static int SAMPLES = 10
|
private static int SAMPLES = 10
|
||||||
|
|
||||||
private final String meB64
|
private final String meB64
|
||||||
private final Pieces pieces
|
private final Pieces downloaded, claimed
|
||||||
private final InfoHash infoHash
|
private final InfoHash infoHash
|
||||||
private final Endpoint endpoint
|
private final Endpoint endpoint
|
||||||
private final File file
|
private final File file
|
||||||
@@ -37,10 +36,11 @@ class DownloadSession {
|
|||||||
|
|
||||||
private ByteBuffer mapped
|
private ByteBuffer mapped
|
||||||
|
|
||||||
DownloadSession(String meB64, Pieces pieces, InfoHash infoHash, Endpoint endpoint, File file,
|
DownloadSession(String meB64, Pieces downloaded, Pieces claimed, InfoHash infoHash, Endpoint endpoint, File file,
|
||||||
int pieceSize, long fileLength) {
|
int pieceSize, long fileLength) {
|
||||||
this.meB64 = meB64
|
this.meB64 = meB64
|
||||||
this.pieces = pieces
|
this.downloaded = downloaded
|
||||||
|
this.claimed = claimed
|
||||||
this.endpoint = endpoint
|
this.endpoint = endpoint
|
||||||
this.infoHash = infoHash
|
this.infoHash = infoHash
|
||||||
this.file = file
|
this.file = file
|
||||||
@@ -63,11 +63,20 @@ class DownloadSession {
|
|||||||
OutputStream os = endpoint.getOutputStream()
|
OutputStream os = endpoint.getOutputStream()
|
||||||
InputStream is = endpoint.getInputStream()
|
InputStream is = endpoint.getInputStream()
|
||||||
|
|
||||||
int piece = pieces.claim()
|
int piece
|
||||||
if (piece == -1)
|
while(true) {
|
||||||
return false
|
piece = downloaded.getRandomPiece()
|
||||||
boolean unclaim = true
|
if (claimed.isMarked(piece)) {
|
||||||
|
if (downloaded.donePieces() + claimed.donePieces() == downloaded.nPieces) {
|
||||||
|
log.info("all pieces claimed")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
claimed.markDownloaded(piece)
|
||||||
|
|
||||||
log.info("will download piece $piece")
|
log.info("will download piece $piece")
|
||||||
|
|
||||||
long start = piece * pieceSize
|
long start = piece * pieceSize
|
||||||
@@ -76,6 +85,7 @@ class DownloadSession {
|
|||||||
|
|
||||||
String root = Base64.encode(infoHash.getRoot())
|
String root = Base64.encode(infoHash.getRoot())
|
||||||
|
|
||||||
|
FileChannel channel
|
||||||
try {
|
try {
|
||||||
os.write("GET $root\r\n".getBytes(StandardCharsets.US_ASCII))
|
os.write("GET $root\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
os.write("Range: $start-$end\r\n".getBytes(StandardCharsets.US_ASCII))
|
os.write("Range: $start-$end\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
@@ -85,7 +95,7 @@ class DownloadSession {
|
|||||||
if (code.startsWith("404 ")) {
|
if (code.startsWith("404 ")) {
|
||||||
log.warning("file not found")
|
log.warning("file not found")
|
||||||
endpoint.close()
|
endpoint.close()
|
||||||
return false
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (code.startsWith("416 ")) {
|
if (code.startsWith("416 ")) {
|
||||||
@@ -125,46 +135,41 @@ class DownloadSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// start the download
|
// start the download
|
||||||
FileChannel channel
|
channel = Files.newByteChannel(file.toPath(), EnumSet.of(StandardOpenOption.READ, StandardOpenOption.WRITE,
|
||||||
try {
|
StandardOpenOption.SPARSE, StandardOpenOption.CREATE)) // TODO: double-check, maybe CREATE_NEW
|
||||||
channel = Files.newByteChannel(file.toPath(), EnumSet.of(StandardOpenOption.READ, StandardOpenOption.WRITE,
|
mapped = channel.map(FileChannel.MapMode.READ_WRITE, start, end - start + 1)
|
||||||
StandardOpenOption.SPARSE, StandardOpenOption.CREATE)) // TODO: double-check, maybe CREATE_NEW
|
|
||||||
mapped = channel.map(FileChannel.MapMode.READ_WRITE, start, end - start + 1)
|
byte[] tmp = new byte[0x1 << 13]
|
||||||
|
while(mapped.hasRemaining()) {
|
||||||
byte[] tmp = new byte[0x1 << 13]
|
if (mapped.remaining() < tmp.length)
|
||||||
while(mapped.hasRemaining()) {
|
tmp = new byte[mapped.remaining()]
|
||||||
if (mapped.remaining() < tmp.length)
|
int read = is.read(tmp)
|
||||||
tmp = new byte[mapped.remaining()]
|
if (read == -1)
|
||||||
int read = is.read(tmp)
|
throw new IOException()
|
||||||
if (read == -1)
|
synchronized(this) {
|
||||||
throw new IOException()
|
mapped.put(tmp, 0, read)
|
||||||
synchronized(this) {
|
|
||||||
mapped.put(tmp, 0, read)
|
if (timestamps.size() == SAMPLES) {
|
||||||
|
timestamps.removeFirst()
|
||||||
if (timestamps.size() == SAMPLES) {
|
reads.removeFirst()
|
||||||
timestamps.removeFirst()
|
|
||||||
reads.removeFirst()
|
|
||||||
}
|
|
||||||
timestamps.addLast(System.currentTimeMillis())
|
|
||||||
reads.addLast(read)
|
|
||||||
}
|
}
|
||||||
|
timestamps.addLast(System.currentTimeMillis())
|
||||||
|
reads.addLast(read)
|
||||||
}
|
}
|
||||||
|
|
||||||
mapped.clear()
|
|
||||||
digest.update(mapped)
|
|
||||||
byte [] hash = digest.digest()
|
|
||||||
byte [] expected = new byte[32]
|
|
||||||
System.arraycopy(infoHash.getHashList(), piece * 32, expected, 0, 32)
|
|
||||||
if (hash != expected)
|
|
||||||
throw new BadHashException()
|
|
||||||
} finally {
|
|
||||||
try { channel?.close() } catch (IOException ignore) {}
|
|
||||||
}
|
}
|
||||||
pieces.markDownloaded(piece)
|
|
||||||
unclaim = false
|
mapped.clear()
|
||||||
|
digest.update(mapped)
|
||||||
|
byte [] hash = digest.digest()
|
||||||
|
byte [] expected = new byte[32]
|
||||||
|
System.arraycopy(infoHash.getHashList(), piece * 32, expected, 0, 32)
|
||||||
|
if (hash != expected)
|
||||||
|
throw new BadHashException()
|
||||||
|
|
||||||
|
downloaded.markDownloaded(piece)
|
||||||
} finally {
|
} finally {
|
||||||
if (unclaim)
|
claimed.clear(piece)
|
||||||
pieces.unclaim(piece)
|
try { channel?.close() } catch (IOException ignore) {}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@@ -4,13 +4,9 @@ import com.muwire.core.InfoHash
|
|||||||
import com.muwire.core.Persona
|
import com.muwire.core.Persona
|
||||||
import com.muwire.core.connection.Endpoint
|
import com.muwire.core.connection.Endpoint
|
||||||
|
|
||||||
import java.nio.file.AtomicMoveNotSupportedException
|
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.StandardCopyOption
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.ExecutorService
|
import java.util.concurrent.ExecutorService
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
|
||||||
import java.util.logging.Level
|
import java.util.logging.Level
|
||||||
|
|
||||||
import com.muwire.core.Constants
|
import com.muwire.core.Constants
|
||||||
@@ -21,7 +17,6 @@ import com.muwire.core.files.FileDownloadedEvent
|
|||||||
|
|
||||||
import groovy.util.logging.Log
|
import groovy.util.logging.Log
|
||||||
import net.i2p.data.Destination
|
import net.i2p.data.Destination
|
||||||
import net.i2p.util.ConcurrentHashSet
|
|
||||||
|
|
||||||
@Log
|
@Log
|
||||||
public class Downloader {
|
public class Downloader {
|
||||||
@@ -39,7 +34,7 @@ public class Downloader {
|
|||||||
private final DownloadManager downloadManager
|
private final DownloadManager downloadManager
|
||||||
private final Persona me
|
private final Persona me
|
||||||
private final File file
|
private final File file
|
||||||
private final Pieces pieces
|
private final Pieces downloaded, claimed
|
||||||
private final long length
|
private final long length
|
||||||
private InfoHash infoHash
|
private InfoHash infoHash
|
||||||
private final int pieceSize
|
private final int pieceSize
|
||||||
@@ -47,15 +42,12 @@ public class Downloader {
|
|||||||
private final Set<Destination> destinations
|
private final Set<Destination> destinations
|
||||||
private final int nPieces
|
private final int nPieces
|
||||||
private final File piecesFile
|
private final File piecesFile
|
||||||
private final File incompleteFile
|
|
||||||
final int pieceSizePow2
|
final int pieceSizePow2
|
||||||
private final Map<Destination, DownloadWorker> activeWorkers = new ConcurrentHashMap<>()
|
private final Map<Destination, DownloadWorker> activeWorkers = new ConcurrentHashMap<>()
|
||||||
private final Set<Destination> successfulDestinations = new ConcurrentHashSet<>()
|
|
||||||
|
|
||||||
|
|
||||||
private volatile boolean cancelled
|
private volatile boolean cancelled
|
||||||
private final AtomicBoolean eventFired = new AtomicBoolean()
|
private volatile boolean eventFired
|
||||||
private boolean piecesFileClosed
|
|
||||||
|
|
||||||
public Downloader(EventBus eventBus, DownloadManager downloadManager,
|
public Downloader(EventBus eventBus, DownloadManager downloadManager,
|
||||||
Persona me, File file, long length, InfoHash infoHash,
|
Persona me, File file, long length, InfoHash infoHash,
|
||||||
@@ -70,7 +62,6 @@ public class Downloader {
|
|||||||
this.connector = connector
|
this.connector = connector
|
||||||
this.destinations = destinations
|
this.destinations = destinations
|
||||||
this.piecesFile = new File(incompletes, file.getName()+".pieces")
|
this.piecesFile = new File(incompletes, file.getName()+".pieces")
|
||||||
this.incompleteFile = new File(incompletes, file.getName()+".part")
|
|
||||||
this.pieceSizePow2 = pieceSizePow2
|
this.pieceSizePow2 = pieceSizePow2
|
||||||
this.pieceSize = 1 << pieceSizePow2
|
this.pieceSize = 1 << pieceSizePow2
|
||||||
|
|
||||||
@@ -81,7 +72,8 @@ public class Downloader {
|
|||||||
nPieces = length / pieceSize + 1
|
nPieces = length / pieceSize + 1
|
||||||
this.nPieces = nPieces
|
this.nPieces = nPieces
|
||||||
|
|
||||||
pieces = new Pieces(nPieces, Constants.DOWNLOAD_SEQUENTIAL_RATIO)
|
downloaded = new Pieces(nPieces, Constants.DOWNLOAD_SEQUENTIAL_RATIO)
|
||||||
|
claimed = new Pieces(nPieces)
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized InfoHash getInfoHash() {
|
public synchronized InfoHash getInfoHash() {
|
||||||
@@ -108,24 +100,20 @@ public class Downloader {
|
|||||||
return
|
return
|
||||||
piecesFile.eachLine {
|
piecesFile.eachLine {
|
||||||
int piece = Integer.parseInt(it)
|
int piece = Integer.parseInt(it)
|
||||||
pieces.markDownloaded(piece)
|
downloaded.markDownloaded(piece)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void writePieces() {
|
void writePieces() {
|
||||||
synchronized(piecesFile) {
|
piecesFile.withPrintWriter { writer ->
|
||||||
if (piecesFileClosed)
|
downloaded.getDownloaded().each { piece ->
|
||||||
return
|
writer.println(piece)
|
||||||
piecesFile.withPrintWriter { writer ->
|
|
||||||
pieces.getDownloaded().each { piece ->
|
|
||||||
writer.println(piece)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public long donePieces() {
|
public long donePieces() {
|
||||||
pieces.donePieces()
|
downloaded.donePieces()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -148,7 +136,7 @@ public class Downloader {
|
|||||||
allFinished &= it.currentState == WorkerState.FINISHED
|
allFinished &= it.currentState == WorkerState.FINISHED
|
||||||
}
|
}
|
||||||
if (allFinished) {
|
if (allFinished) {
|
||||||
if (pieces.isComplete())
|
if (downloaded.isComplete())
|
||||||
return DownloadState.FINISHED
|
return DownloadState.FINISHED
|
||||||
return DownloadState.FAILED
|
return DownloadState.FAILED
|
||||||
}
|
}
|
||||||
@@ -182,11 +170,8 @@ public class Downloader {
|
|||||||
public void cancel() {
|
public void cancel() {
|
||||||
cancelled = true
|
cancelled = true
|
||||||
stop()
|
stop()
|
||||||
synchronized(piecesFile) {
|
file.delete()
|
||||||
piecesFileClosed = true
|
piecesFile.delete()
|
||||||
piecesFile.delete()
|
|
||||||
}
|
|
||||||
incompleteFile.delete()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
@@ -246,32 +231,23 @@ public class Downloader {
|
|||||||
}
|
}
|
||||||
currentState = WorkerState.DOWNLOADING
|
currentState = WorkerState.DOWNLOADING
|
||||||
boolean requestPerformed
|
boolean requestPerformed
|
||||||
while(!pieces.isComplete()) {
|
while(!downloaded.isComplete()) {
|
||||||
currentSession = new DownloadSession(me.toBase64(), pieces, getInfoHash(), endpoint, incompleteFile, pieceSize, length)
|
currentSession = new DownloadSession(me.toBase64(), downloaded, claimed, getInfoHash(), endpoint, file, pieceSize, length)
|
||||||
requestPerformed = currentSession.request()
|
requestPerformed = currentSession.request()
|
||||||
if (!requestPerformed)
|
if (!requestPerformed)
|
||||||
break
|
break
|
||||||
successfulDestinations.add(endpoint.destination)
|
|
||||||
writePieces()
|
writePieces()
|
||||||
}
|
}
|
||||||
} catch (Exception bad) {
|
} catch (Exception bad) {
|
||||||
log.log(Level.WARNING,"Exception while downloading",bad)
|
log.log(Level.WARNING,"Exception while downloading",bad)
|
||||||
} finally {
|
} finally {
|
||||||
currentState = WorkerState.FINISHED
|
currentState = WorkerState.FINISHED
|
||||||
if (pieces.isComplete() && eventFired.compareAndSet(false, true)) {
|
if (downloaded.isComplete() && !eventFired) {
|
||||||
synchronized(piecesFile) {
|
piecesFile.delete()
|
||||||
piecesFileClosed = true
|
eventFired = true
|
||||||
piecesFile.delete()
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
Files.move(incompleteFile.toPath(), file.toPath(), StandardCopyOption.ATOMIC_MOVE)
|
|
||||||
} catch (AtomicMoveNotSupportedException e) {
|
|
||||||
Files.copy(incompleteFile.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
|
||||||
incompleteFile.delete()
|
|
||||||
}
|
|
||||||
eventBus.publish(
|
eventBus.publish(
|
||||||
new FileDownloadedEvent(
|
new FileDownloadedEvent(
|
||||||
downloadedFile : new DownloadedFile(file, getInfoHash(), pieceSizePow2, successfulDestinations),
|
downloadedFile : new DownloadedFile(file, getInfoHash(), pieceSizePow2, Collections.emptySet()),
|
||||||
downloader : Downloader.this))
|
downloader : Downloader.this))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
package com.muwire.core.download
|
package com.muwire.core.download
|
||||||
|
|
||||||
class Pieces {
|
class Pieces {
|
||||||
private final BitSet done, claimed
|
private final BitSet bitSet
|
||||||
private final int nPieces
|
private final int nPieces
|
||||||
private final float ratio
|
private final float ratio
|
||||||
private final Random random = new Random()
|
private final Random random = new Random()
|
||||||
@@ -13,53 +13,52 @@ class Pieces {
|
|||||||
Pieces(int nPieces, float ratio) {
|
Pieces(int nPieces, float ratio) {
|
||||||
this.nPieces = nPieces
|
this.nPieces = nPieces
|
||||||
this.ratio = ratio
|
this.ratio = ratio
|
||||||
done = new BitSet(nPieces)
|
bitSet = new BitSet(nPieces)
|
||||||
claimed = new BitSet(nPieces)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized int claim() {
|
synchronized int getRandomPiece() {
|
||||||
int claimedCardinality = claimed.cardinality()
|
int cardinality = bitSet.cardinality()
|
||||||
if (claimedCardinality == nPieces)
|
if (cardinality == nPieces)
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
// if fuller than ratio just do sequential
|
// if fuller than ratio just do sequential
|
||||||
if ( (1.0f * claimedCardinality) / nPieces > ratio) {
|
if ( (1.0f * cardinality) / nPieces > ratio) {
|
||||||
int rv = claimed.nextClearBit(0)
|
return bitSet.nextClearBit(0)
|
||||||
claimed.set(rv)
|
|
||||||
return rv
|
|
||||||
}
|
}
|
||||||
|
|
||||||
while(true) {
|
while(true) {
|
||||||
int start = random.nextInt(nPieces)
|
int start = random.nextInt(nPieces)
|
||||||
if (claimed.get(start))
|
if (bitSet.get(start))
|
||||||
continue
|
continue
|
||||||
claimed.set(start)
|
|
||||||
return start
|
return start
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized def getDownloaded() {
|
def getDownloaded() {
|
||||||
def rv = []
|
def rv = []
|
||||||
for (int i = done.nextSetBit(0); i >= 0; i = done.nextSetBit(i+1)) {
|
for (int i = bitSet.nextSetBit(0); i >= 0; i = bitSet.nextSetBit(i+1)) {
|
||||||
rv << i
|
rv << i
|
||||||
}
|
}
|
||||||
rv
|
rv
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void markDownloaded(int piece) {
|
synchronized void markDownloaded(int piece) {
|
||||||
done.set(piece)
|
bitSet.set(piece)
|
||||||
claimed.set(piece)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void unclaim(int piece) {
|
synchronized void clear(int piece) {
|
||||||
claimed.clear(piece)
|
bitSet.clear(piece)
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized boolean isComplete() {
|
synchronized boolean isComplete() {
|
||||||
done.cardinality() == nPieces
|
bitSet.cardinality() == nPieces
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized boolean isMarked(int piece) {
|
||||||
|
bitSet.get(piece)
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized int donePieces() {
|
synchronized int donePieces() {
|
||||||
done.cardinality()
|
bitSet.cardinality()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,11 +3,8 @@ package com.muwire.core.download
|
|||||||
import com.muwire.core.Event
|
import com.muwire.core.Event
|
||||||
import com.muwire.core.search.UIResultEvent
|
import com.muwire.core.search.UIResultEvent
|
||||||
|
|
||||||
import net.i2p.data.Destination
|
|
||||||
|
|
||||||
class UIDownloadEvent extends Event {
|
class UIDownloadEvent extends Event {
|
||||||
|
|
||||||
UIResultEvent[] result
|
UIResultEvent[] result
|
||||||
Set<Destination> sources
|
|
||||||
File target
|
File target
|
||||||
}
|
}
|
||||||
|
@@ -1,137 +1,4 @@
|
|||||||
package com.muwire.core.files
|
package com.muwire.core.files
|
||||||
|
|
||||||
import java.nio.file.FileSystem
|
|
||||||
import java.nio.file.FileSystems
|
|
||||||
import java.nio.file.Path
|
|
||||||
import java.nio.file.Paths
|
|
||||||
import static java.nio.file.StandardWatchEventKinds.*
|
|
||||||
import java.nio.file.WatchEvent
|
|
||||||
import java.nio.file.WatchKey
|
|
||||||
import java.nio.file.WatchService
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
|
|
||||||
import com.muwire.core.EventBus
|
|
||||||
import com.muwire.core.SharedFile
|
|
||||||
|
|
||||||
import groovy.util.logging.Log
|
|
||||||
import net.i2p.util.SystemVersion
|
|
||||||
|
|
||||||
@Log
|
|
||||||
class DirectoryWatcher {
|
class DirectoryWatcher {
|
||||||
|
|
||||||
private static final long WAIT_TIME = 1000
|
|
||||||
|
|
||||||
private static final WatchEvent.Kind[] kinds
|
|
||||||
static {
|
|
||||||
if (SystemVersion.isMac())
|
|
||||||
kinds = [ENTRY_MODIFY, ENTRY_DELETE]
|
|
||||||
else
|
|
||||||
kinds = [ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE]
|
|
||||||
}
|
|
||||||
|
|
||||||
private final EventBus eventBus
|
|
||||||
private final FileManager fileManager
|
|
||||||
private final Thread watcherThread, publisherThread
|
|
||||||
private final Map<File, Long> waitingFiles = new ConcurrentHashMap<>()
|
|
||||||
private WatchService watchService
|
|
||||||
private volatile boolean shutdown
|
|
||||||
|
|
||||||
DirectoryWatcher(EventBus eventBus, FileManager fileManager) {
|
|
||||||
this.eventBus = eventBus
|
|
||||||
this.fileManager = fileManager
|
|
||||||
this.watcherThread = new Thread({watch() } as Runnable, "directory-watcher")
|
|
||||||
watcherThread.setDaemon(true)
|
|
||||||
this.publisherThread = new Thread({publish()} as Runnable, "watched-files-publisher")
|
|
||||||
publisherThread.setDaemon(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
void start() {
|
|
||||||
watchService = FileSystems.getDefault().newWatchService()
|
|
||||||
watcherThread.start()
|
|
||||||
publisherThread.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
void stop() {
|
|
||||||
shutdown = true
|
|
||||||
watcherThread.interrupt()
|
|
||||||
publisherThread.interrupt()
|
|
||||||
watchService.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
void onFileSharedEvent(FileSharedEvent e) {
|
|
||||||
if (!e.file.isDirectory())
|
|
||||||
return
|
|
||||||
Path path = e.file.getCanonicalFile().toPath()
|
|
||||||
path.register(watchService, kinds)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void watch() {
|
|
||||||
try {
|
|
||||||
while(!shutdown) {
|
|
||||||
WatchKey key = watchService.take()
|
|
||||||
key.pollEvents().each {
|
|
||||||
switch(it.kind()) {
|
|
||||||
case ENTRY_CREATE: processCreated(key.watchable(), it.context()); break
|
|
||||||
case ENTRY_MODIFY: processModified(key.watchable(), it.context()); break
|
|
||||||
case ENTRY_DELETE: processDeleted(key.watchable(), it.context()); break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
key.reset()
|
|
||||||
}
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
if (!shutdown)
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void processCreated(Path parent, Path path) {
|
|
||||||
File f= join(parent, path)
|
|
||||||
log.fine("created entry $f")
|
|
||||||
if (f.isDirectory())
|
|
||||||
f.toPath().register(watchService, kinds)
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processModified(Path parent, Path path) {
|
|
||||||
File f = join(parent, path)
|
|
||||||
log.fine("modified entry $f")
|
|
||||||
waitingFiles.put(f, System.currentTimeMillis())
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processDeleted(Path parent, Path path) {
|
|
||||||
File f = join(parent, path)
|
|
||||||
log.fine("deleted entry $f")
|
|
||||||
SharedFile sf = fileManager.fileToSharedFile.get(f)
|
|
||||||
if (sf != null)
|
|
||||||
eventBus.publish(new FileUnsharedEvent(unsharedFile : sf))
|
|
||||||
}
|
|
||||||
|
|
||||||
private static File join(Path parent, Path path) {
|
|
||||||
File parentFile = parent.toFile().getCanonicalFile()
|
|
||||||
new File(parentFile, path.toFile().getName())
|
|
||||||
}
|
|
||||||
|
|
||||||
private void publish() {
|
|
||||||
try {
|
|
||||||
while(!shutdown) {
|
|
||||||
Thread.sleep(WAIT_TIME)
|
|
||||||
long now = System.currentTimeMillis()
|
|
||||||
def published = []
|
|
||||||
waitingFiles.each { file, timestamp ->
|
|
||||||
if (now - timestamp > WAIT_TIME) {
|
|
||||||
log.fine("publishing file $file")
|
|
||||||
eventBus.publish new FileSharedEvent(file : file)
|
|
||||||
published << file
|
|
||||||
}
|
|
||||||
}
|
|
||||||
published.each {
|
|
||||||
waitingFiles.remove(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
if (!shutdown)
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -32,7 +32,7 @@ class HasherService {
|
|||||||
private void process(File f) {
|
private void process(File f) {
|
||||||
f = f.getCanonicalFile()
|
f = f.getCanonicalFile()
|
||||||
if (f.isDirectory()) {
|
if (f.isDirectory()) {
|
||||||
f.listFiles().each {eventBus.publish new FileSharedEvent(file: it) }
|
f.listFiles().each {onFileSharedEvent new FileSharedEvent(file: it) }
|
||||||
} else {
|
} else {
|
||||||
if (f.length() == 0) {
|
if (f.length() == 0) {
|
||||||
eventBus.publish new FileHashedEvent(error: "Not sharing empty file $f")
|
eventBus.publish new FileHashedEvent(error: "Not sharing empty file $f")
|
||||||
|
@@ -11,7 +11,6 @@ import com.muwire.core.EventBus
|
|||||||
import com.muwire.core.InfoHash
|
import com.muwire.core.InfoHash
|
||||||
import com.muwire.core.Service
|
import com.muwire.core.Service
|
||||||
import com.muwire.core.SharedFile
|
import com.muwire.core.SharedFile
|
||||||
import com.muwire.core.UILoadedEvent
|
|
||||||
import com.muwire.core.util.DataUtil
|
import com.muwire.core.util.DataUtil
|
||||||
|
|
||||||
import groovy.json.JsonOutput
|
import groovy.json.JsonOutput
|
||||||
@@ -37,14 +36,14 @@ class PersisterService extends Service {
|
|||||||
timer = new Timer("file persister", true)
|
timer = new Timer("file persister", true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void start() {
|
||||||
|
timer.schedule({load()} as TimerTask, 1)
|
||||||
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
timer.cancel()
|
timer.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
void onUILoadedEvent(UILoadedEvent e) {
|
|
||||||
timer.schedule({load()} as TimerTask, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
void load() {
|
void load() {
|
||||||
if (location.exists() && location.isFile()) {
|
if (location.exists() && location.isFile()) {
|
||||||
def slurper = new JsonSlurper()
|
def slurper = new JsonSlurper()
|
||||||
|
@@ -1,7 +1,5 @@
|
|||||||
package com.muwire.core.search
|
package com.muwire.core.search
|
||||||
|
|
||||||
import java.util.stream.Collectors
|
|
||||||
|
|
||||||
import javax.naming.directory.InvalidSearchControlsException
|
import javax.naming.directory.InvalidSearchControlsException
|
||||||
|
|
||||||
import com.muwire.core.InfoHash
|
import com.muwire.core.InfoHash
|
||||||
@@ -9,7 +7,6 @@ import com.muwire.core.Persona
|
|||||||
import com.muwire.core.util.DataUtil
|
import com.muwire.core.util.DataUtil
|
||||||
|
|
||||||
import net.i2p.data.Base64
|
import net.i2p.data.Base64
|
||||||
import net.i2p.data.Destination
|
|
||||||
|
|
||||||
class ResultsParser {
|
class ResultsParser {
|
||||||
public static UIResultEvent parse(Persona p, UUID uuid, def json) throws InvalidSearchResultException {
|
public static UIResultEvent parse(Persona p, UUID uuid, def json) throws InvalidSearchResultException {
|
||||||
@@ -61,7 +58,6 @@ class ResultsParser {
|
|||||||
size : size,
|
size : size,
|
||||||
infohash : parsedIH,
|
infohash : parsedIH,
|
||||||
pieceSize : pieceSize,
|
pieceSize : pieceSize,
|
||||||
sources : Collections.emptySet(),
|
|
||||||
uuid : uuid)
|
uuid : uuid)
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new InvalidSearchResultException("parsing search result failed",e)
|
throw new InvalidSearchResultException("parsing search result failed",e)
|
||||||
@@ -86,17 +82,11 @@ class ResultsParser {
|
|||||||
if (infoHash.length != InfoHash.SIZE)
|
if (infoHash.length != InfoHash.SIZE)
|
||||||
throw new InvalidSearchResultException("invalid infohash size $infoHash.length")
|
throw new InvalidSearchResultException("invalid infohash size $infoHash.length")
|
||||||
int pieceSize = json.pieceSize
|
int pieceSize = json.pieceSize
|
||||||
|
|
||||||
Set<Destination> sources = Collections.emptySet()
|
|
||||||
if (json.sources != null)
|
|
||||||
sources = json.sources.stream().map({new Destination(it)}).collect(Collectors.toSet())
|
|
||||||
|
|
||||||
return new UIResultEvent( sender : p,
|
return new UIResultEvent( sender : p,
|
||||||
name : name,
|
name : name,
|
||||||
size : size,
|
size : size,
|
||||||
infohash : new InfoHash(infoHash),
|
infohash : new InfoHash(infoHash),
|
||||||
pieceSize : pieceSize,
|
pieceSize : pieceSize,
|
||||||
sources : sources,
|
|
||||||
uuid: uuid)
|
uuid: uuid)
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new InvalidSearchResultException("parsing search result failed",e)
|
throw new InvalidSearchResultException("parsing search result failed",e)
|
||||||
|
@@ -11,9 +11,7 @@ import java.util.concurrent.Executor
|
|||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.ThreadFactory
|
import java.util.concurrent.ThreadFactory
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import java.util.stream.Collectors
|
|
||||||
|
|
||||||
import com.muwire.core.DownloadedFile
|
|
||||||
import com.muwire.core.EventBus
|
import com.muwire.core.EventBus
|
||||||
import com.muwire.core.InfoHash
|
import com.muwire.core.InfoHash
|
||||||
|
|
||||||
@@ -56,16 +54,12 @@ class ResultsSender {
|
|||||||
int pieceSize = it.getPieceSize()
|
int pieceSize = it.getPieceSize()
|
||||||
if (pieceSize == 0)
|
if (pieceSize == 0)
|
||||||
pieceSize = FileHasher.getPieceSize(length)
|
pieceSize = FileHasher.getPieceSize(length)
|
||||||
Set<Destination> suggested = Collections.emptySet()
|
|
||||||
if (it instanceof DownloadedFile)
|
|
||||||
suggested = it.sources
|
|
||||||
def uiResultEvent = new UIResultEvent( sender : me,
|
def uiResultEvent = new UIResultEvent( sender : me,
|
||||||
name : it.getFile().getName(),
|
name : it.getFile().getName(),
|
||||||
size : length,
|
size : length,
|
||||||
infohash : it.getInfoHash(),
|
infohash : it.getInfoHash(),
|
||||||
pieceSize : pieceSize,
|
pieceSize : pieceSize,
|
||||||
uuid : uuid,
|
uuid : uuid
|
||||||
sources : suggested
|
|
||||||
)
|
)
|
||||||
eventBus.publish(uiResultEvent)
|
eventBus.publish(uiResultEvent)
|
||||||
}
|
}
|
||||||
@@ -116,10 +110,6 @@ class ResultsSender {
|
|||||||
}
|
}
|
||||||
obj.hashList = hashListB64
|
obj.hashList = hashListB64
|
||||||
}
|
}
|
||||||
|
|
||||||
if (it instanceof DownloadedFile)
|
|
||||||
obj.sources = it.sources.stream().map({dest -> dest.toBase64()}).collect(Collectors.toSet())
|
|
||||||
|
|
||||||
def json = jsonOutput.toJson(obj)
|
def json = jsonOutput.toJson(obj)
|
||||||
os.writeShort((short)json.length())
|
os.writeShort((short)json.length())
|
||||||
os.write(json.getBytes(StandardCharsets.US_ASCII))
|
os.write(json.getBytes(StandardCharsets.US_ASCII))
|
||||||
|
@@ -1,8 +0,0 @@
|
|||||||
package com.muwire.core.search
|
|
||||||
|
|
||||||
import com.muwire.core.Event
|
|
||||||
|
|
||||||
class UIResultBatchEvent extends Event {
|
|
||||||
UUID uuid
|
|
||||||
UIResultEvent[] results
|
|
||||||
}
|
|
@@ -4,11 +4,8 @@ import com.muwire.core.Event
|
|||||||
import com.muwire.core.InfoHash
|
import com.muwire.core.InfoHash
|
||||||
import com.muwire.core.Persona
|
import com.muwire.core.Persona
|
||||||
|
|
||||||
import net.i2p.data.Destination
|
|
||||||
|
|
||||||
class UIResultEvent extends Event {
|
class UIResultEvent extends Event {
|
||||||
Persona sender
|
Persona sender
|
||||||
Set<Destination> sources
|
|
||||||
UUID uuid
|
UUID uuid
|
||||||
String name
|
String name
|
||||||
long size
|
long size
|
||||||
|
@@ -51,23 +51,4 @@ class ContentUploader extends Uploader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return file.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized int getProgress() {
|
|
||||||
if (mapped == null)
|
|
||||||
return 0
|
|
||||||
int position = mapped.position()
|
|
||||||
int total = request.getRange().end - request.getRange().start
|
|
||||||
(int)(position * 100.0 / total)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getDownloader() {
|
|
||||||
request.downloader.getHumanReadableName()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -6,8 +6,6 @@ import java.nio.charset.StandardCharsets
|
|||||||
import com.muwire.core.InfoHash
|
import com.muwire.core.InfoHash
|
||||||
import com.muwire.core.connection.Endpoint
|
import com.muwire.core.connection.Endpoint
|
||||||
|
|
||||||
import net.i2p.data.Base64
|
|
||||||
|
|
||||||
class HashListUploader extends Uploader {
|
class HashListUploader extends Uploader {
|
||||||
private final InfoHash infoHash
|
private final InfoHash infoHash
|
||||||
private final HashListRequest request
|
private final HashListRequest request
|
||||||
@@ -16,7 +14,6 @@ class HashListUploader extends Uploader {
|
|||||||
super(endpoint)
|
super(endpoint)
|
||||||
this.infoHash = infoHash
|
this.infoHash = infoHash
|
||||||
mapped = ByteBuffer.wrap(infoHash.getHashList())
|
mapped = ByteBuffer.wrap(infoHash.getHashList())
|
||||||
this.request = request
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void respond() {
|
void respond() {
|
||||||
@@ -35,21 +32,4 @@ class HashListUploader extends Uploader {
|
|||||||
}
|
}
|
||||||
endpoint.getOutputStream().flush()
|
endpoint.getOutputStream().flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return "Hash list for " + Base64.encode(infoHash.getRoot());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized int getProgress() {
|
|
||||||
(int)(mapped.position() * 100.0 / mapped.capacity())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getDownloader() {
|
|
||||||
request.downloader.getHumanReadableName()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -23,13 +23,4 @@ abstract class Uploader {
|
|||||||
return -1
|
return -1
|
||||||
mapped.position()
|
mapped.position()
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract String getName();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return an integer between 0 and 100
|
|
||||||
*/
|
|
||||||
abstract int getProgress();
|
|
||||||
|
|
||||||
abstract String getDownloader();
|
|
||||||
}
|
}
|
||||||
|
@@ -25,17 +25,4 @@ public class SharedFile {
|
|||||||
public int getPieceSize() {
|
public int getPieceSize() {
|
||||||
return pieceSize;
|
return pieceSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return file.hashCode() ^ infoHash.hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (!(o instanceof SharedFile))
|
|
||||||
return false;
|
|
||||||
SharedFile other = (SharedFile)o;
|
|
||||||
return file.equals(other.file) && infoHash.equals(other.infoHash);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
group = com.muwire
|
group = com.muwire
|
||||||
version = 0.2.6
|
version = 0.2.1
|
||||||
groovyVersion = 2.4.15
|
groovyVersion = 2.4.15
|
||||||
slf4jVersion = 1.7.25
|
slf4jVersion = 1.7.25
|
||||||
spockVersion = 1.1-groovy-2.4
|
spockVersion = 1.1-groovy-2.4
|
||||||
|
@@ -62,7 +62,7 @@ class MainFrameController {
|
|||||||
|
|
||||||
def searchEvent
|
def searchEvent
|
||||||
if (hashSearch) {
|
if (hashSearch) {
|
||||||
searchEvent = new SearchEvent(searchHash : root, uuid : uuid, oobInfohash: true)
|
searchEvent = new SearchEvent(searchHash : root, uuid : uuid)
|
||||||
} else {
|
} else {
|
||||||
// this can be improved a lot
|
// this can be improved a lot
|
||||||
def replaced = search.toLowerCase().trim().replaceAll(Constants.SPLIT_PATTERN, " ")
|
def replaced = search.toLowerCase().trim().replaceAll(Constants.SPLIT_PATTERN, " ")
|
||||||
@@ -118,20 +118,16 @@ class MainFrameController {
|
|||||||
void download() {
|
void download() {
|
||||||
def result = selectedResult()
|
def result = selectedResult()
|
||||||
if (result == null)
|
if (result == null)
|
||||||
return
|
return // TODO disable button
|
||||||
|
|
||||||
if (!model.canDownload(result.infohash))
|
|
||||||
return
|
|
||||||
|
|
||||||
def file = new File(application.context.get("muwire-settings").downloadLocation, result.name)
|
def file = new File(application.context.get("muwire-settings").downloadLocation, result.name)
|
||||||
|
|
||||||
def selected = builder.getVariable("result-tabs").getSelectedComponent()
|
def selected = builder.getVariable("result-tabs").getSelectedComponent()
|
||||||
def group = selected.getClientProperty("mvc-group")
|
def group = selected.getClientProperty("mvc-group")
|
||||||
|
|
||||||
def resultsBucket = group.model.hashBucket[result.infohash]
|
def resultsBucket = group.model.hashBucket[result.infohash]
|
||||||
def sources = group.model.sourcesBucket[result.infohash]
|
|
||||||
|
|
||||||
core.eventBus.publish(new UIDownloadEvent(result : resultsBucket, sources: sources, target : file))
|
core.eventBus.publish(new UIDownloadEvent(result : resultsBucket, target : file))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ControllerAction
|
@ControllerAction
|
||||||
@@ -154,7 +150,6 @@ class MainFrameController {
|
|||||||
void cancel() {
|
void cancel() {
|
||||||
def downloader = model.downloads[selectedDownload()].downloader
|
def downloader = model.downloads[selectedDownload()].downloader
|
||||||
downloader.cancel()
|
downloader.cancel()
|
||||||
model.downloadInfoHashes.remove(downloader.getInfoHash())
|
|
||||||
core.eventBus.publish(new UIDownloadCancelledEvent(downloader : downloader))
|
core.eventBus.publish(new UIDownloadCancelledEvent(downloader : downloader))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,13 +190,6 @@ class MainFrameController {
|
|||||||
println "unsharing selected files"
|
println "unsharing selected files"
|
||||||
}
|
}
|
||||||
|
|
||||||
void saveMuWireSettings() {
|
|
||||||
File f = new File(core.home, "MuWire.properties")
|
|
||||||
f.withOutputStream {
|
|
||||||
core.muOptions.write(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void mvcGroupInit(Map<String, String> args) {
|
void mvcGroupInit(Map<String, String> args) {
|
||||||
application.addPropertyChangeListener("core", {e->
|
application.addPropertyChangeListener("core", {e->
|
||||||
core = e.getNewValue()
|
core = e.getNewValue()
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import griffon.core.GriffonApplication
|
import griffon.core.GriffonApplication
|
||||||
import griffon.core.env.Metadata
|
import griffon.core.env.Metadata
|
||||||
import groovy.util.logging.Log
|
import groovy.util.logging.Log
|
||||||
@@ -105,6 +104,12 @@ class Ready extends AbstractLifecycleHandler {
|
|||||||
it.propertyChange(new PropertyChangeEvent(this, "core", null, core))
|
it.propertyChange(new PropertyChangeEvent(this, "core", null, core))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (props.sharedFiles != null) {
|
||||||
|
props.sharedFiles.split(",").each {
|
||||||
|
core.eventBus.publish(new FileSharedEvent(file : new File(it)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
core.eventBus.publish(new UILoadedEvent())
|
core.eventBus.publish(new UILoadedEvent())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,7 +9,6 @@ import javax.swing.JTable
|
|||||||
|
|
||||||
import com.muwire.core.Core
|
import com.muwire.core.Core
|
||||||
import com.muwire.core.InfoHash
|
import com.muwire.core.InfoHash
|
||||||
import com.muwire.core.MuWireSettings
|
|
||||||
import com.muwire.core.Persona
|
import com.muwire.core.Persona
|
||||||
import com.muwire.core.connection.ConnectionAttemptStatus
|
import com.muwire.core.connection.ConnectionAttemptStatus
|
||||||
import com.muwire.core.connection.ConnectionEvent
|
import com.muwire.core.connection.ConnectionEvent
|
||||||
@@ -20,9 +19,7 @@ import com.muwire.core.files.FileDownloadedEvent
|
|||||||
import com.muwire.core.files.FileHashedEvent
|
import com.muwire.core.files.FileHashedEvent
|
||||||
import com.muwire.core.files.FileLoadedEvent
|
import com.muwire.core.files.FileLoadedEvent
|
||||||
import com.muwire.core.files.FileSharedEvent
|
import com.muwire.core.files.FileSharedEvent
|
||||||
import com.muwire.core.files.FileUnsharedEvent
|
|
||||||
import com.muwire.core.search.QueryEvent
|
import com.muwire.core.search.QueryEvent
|
||||||
import com.muwire.core.search.UIResultBatchEvent
|
|
||||||
import com.muwire.core.search.UIResultEvent
|
import com.muwire.core.search.UIResultEvent
|
||||||
import com.muwire.core.trust.TrustEvent
|
import com.muwire.core.trust.TrustEvent
|
||||||
import com.muwire.core.trust.TrustService
|
import com.muwire.core.trust.TrustService
|
||||||
@@ -55,7 +52,6 @@ class MainFrameModel {
|
|||||||
def downloads = []
|
def downloads = []
|
||||||
def uploads = []
|
def uploads = []
|
||||||
def shared = []
|
def shared = []
|
||||||
def watched = []
|
|
||||||
def connectionList = []
|
def connectionList = []
|
||||||
def searches = new LinkedList()
|
def searches = new LinkedList()
|
||||||
def trusted = []
|
def trusted = []
|
||||||
@@ -63,14 +59,11 @@ class MainFrameModel {
|
|||||||
|
|
||||||
@Observable int connections
|
@Observable int connections
|
||||||
@Observable String me
|
@Observable String me
|
||||||
@Observable boolean downloadActionEnabled
|
@Observable boolean searchButtonsEnabled
|
||||||
@Observable boolean trustButtonsEnabled
|
|
||||||
@Observable boolean cancelButtonEnabled
|
@Observable boolean cancelButtonEnabled
|
||||||
@Observable boolean retryButtonEnabled
|
@Observable boolean retryButtonEnabled
|
||||||
|
|
||||||
private final Set<InfoHash> infoHashes = new HashSet<>()
|
private final Set<InfoHash> infoHashes = new HashSet<>()
|
||||||
|
|
||||||
private final Set<InfoHash> downloadInfoHashes = new HashSet<>()
|
|
||||||
|
|
||||||
volatile Core core
|
volatile Core core
|
||||||
|
|
||||||
@@ -122,7 +115,6 @@ class MainFrameModel {
|
|||||||
core = e.getNewValue()
|
core = e.getNewValue()
|
||||||
me = core.me.getHumanReadableName()
|
me = core.me.getHumanReadableName()
|
||||||
core.eventBus.register(UIResultEvent.class, this)
|
core.eventBus.register(UIResultEvent.class, this)
|
||||||
core.eventBus.register(UIResultBatchEvent.class, this)
|
|
||||||
core.eventBus.register(DownloadStartedEvent.class, this)
|
core.eventBus.register(DownloadStartedEvent.class, this)
|
||||||
core.eventBus.register(ConnectionEvent.class, this)
|
core.eventBus.register(ConnectionEvent.class, this)
|
||||||
core.eventBus.register(DisconnectionEvent.class, this)
|
core.eventBus.register(DisconnectionEvent.class, this)
|
||||||
@@ -134,10 +126,9 @@ class MainFrameModel {
|
|||||||
core.eventBus.register(QueryEvent.class, this)
|
core.eventBus.register(QueryEvent.class, this)
|
||||||
core.eventBus.register(UpdateAvailableEvent.class, this)
|
core.eventBus.register(UpdateAvailableEvent.class, this)
|
||||||
core.eventBus.register(FileDownloadedEvent.class, this)
|
core.eventBus.register(FileDownloadedEvent.class, this)
|
||||||
core.eventBus.register(FileUnsharedEvent.class, this)
|
|
||||||
|
|
||||||
timer.schedule({
|
timer.schedule({
|
||||||
int retryInterval = core.muOptions.downloadRetryInterval
|
int retryInterval = application.context.get("muwire-settings").downloadRetryInterval
|
||||||
if (retryInterval > 0) {
|
if (retryInterval > 0) {
|
||||||
retryInterval *= 60000
|
retryInterval *= 60000
|
||||||
long now = System.currentTimeMillis()
|
long now = System.currentTimeMillis()
|
||||||
@@ -160,10 +151,6 @@ class MainFrameModel {
|
|||||||
runInsideUIAsync {
|
runInsideUIAsync {
|
||||||
trusted.addAll(core.trustService.good.values())
|
trusted.addAll(core.trustService.good.values())
|
||||||
distrusted.addAll(core.trustService.bad.values())
|
distrusted.addAll(core.trustService.bad.values())
|
||||||
|
|
||||||
watched.addAll(core.muOptions.watchedDirectories)
|
|
||||||
builder.getVariable("watched-directories-table").model.fireTableDataChanged()
|
|
||||||
watched.each { core.eventBus.publish(new FileSharedEvent(file : new File(it))) }
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -174,15 +161,9 @@ class MainFrameModel {
|
|||||||
resultsGroup?.model.handleResult(e)
|
resultsGroup?.model.handleResult(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
void onUIResultBatchEvent(UIResultBatchEvent e) {
|
|
||||||
MVCGroup resultsGroup = results.get(e.uuid)
|
|
||||||
resultsGroup?.model.handleResultBatch(e.results)
|
|
||||||
}
|
|
||||||
|
|
||||||
void onDownloadStartedEvent(DownloadStartedEvent e) {
|
void onDownloadStartedEvent(DownloadStartedEvent e) {
|
||||||
runInsideUIAsync {
|
runInsideUIAsync {
|
||||||
downloads << e
|
downloads << e
|
||||||
downloadInfoHashes.add(e.downloader.infoHash)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,17 +225,6 @@ class MainFrameModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFileUnsharedEvent(FileUnsharedEvent e) {
|
|
||||||
InfoHash infohash = e.unsharedFile.infoHash
|
|
||||||
if (!infoHashes.remove(infohash))
|
|
||||||
return
|
|
||||||
runInsideUIAsync {
|
|
||||||
shared.remove(e.unsharedFile)
|
|
||||||
JTable table = builder.getVariable("shared-files-table")
|
|
||||||
table.model.fireTableDataChanged()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void onUploadEvent(UploadEvent e) {
|
void onUploadEvent(UploadEvent e) {
|
||||||
runInsideUIAsync {
|
runInsideUIAsync {
|
||||||
uploads << e.uploader
|
uploads << e.uploader
|
||||||
@@ -363,8 +333,4 @@ class MainFrameModel {
|
|||||||
return destination == other.destination
|
return destination == other.destination
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean canDownload(InfoHash hash) {
|
|
||||||
!downloadInfoHashes.contains(hash)
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -23,7 +23,6 @@ class SearchTabModel {
|
|||||||
String uuid
|
String uuid
|
||||||
def results = []
|
def results = []
|
||||||
def hashBucket = [:]
|
def hashBucket = [:]
|
||||||
def sourcesBucket = [:]
|
|
||||||
|
|
||||||
|
|
||||||
void mvcGroupInit(Map<String, String> args) {
|
void mvcGroupInit(Map<String, String> args) {
|
||||||
@@ -47,43 +46,10 @@ class SearchTabModel {
|
|||||||
hashBucket[e.infohash] = bucket
|
hashBucket[e.infohash] = bucket
|
||||||
}
|
}
|
||||||
bucket << e
|
bucket << e
|
||||||
|
|
||||||
Set sourceBucket = sourcesBucket.get(e.infohash)
|
|
||||||
if (sourceBucket == null) {
|
|
||||||
sourceBucket = new HashSet()
|
|
||||||
sourcesBucket.put(e.infohash, sourceBucket)
|
|
||||||
}
|
|
||||||
sourceBucket.addAll(e.sources)
|
|
||||||
|
|
||||||
results << e
|
results << e
|
||||||
JTable table = builder.getVariable("results-table")
|
JTable table = builder.getVariable("results-table")
|
||||||
table.model.fireTableDataChanged()
|
table.model.fireTableDataChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleResultBatch(UIResultEvent[] batch) {
|
|
||||||
runInsideUIAsync {
|
|
||||||
batch.each {
|
|
||||||
if (uiSettings.excludeLocalResult && it.sender == core.me)
|
|
||||||
return
|
|
||||||
def bucket = hashBucket.get(it.infohash)
|
|
||||||
if (bucket == null) {
|
|
||||||
bucket = []
|
|
||||||
hashBucket[it.infohash] = bucket
|
|
||||||
}
|
|
||||||
|
|
||||||
Set sourceBucket = sourcesBucket.get(it.infohash)
|
|
||||||
if (sourceBucket == null) {
|
|
||||||
sourceBucket = new HashSet()
|
|
||||||
sourcesBucket.put(it.infohash, sourceBucket)
|
|
||||||
}
|
|
||||||
sourceBucket.addAll(it.sources)
|
|
||||||
|
|
||||||
bucket << it
|
|
||||||
results << it
|
|
||||||
}
|
|
||||||
JTable table = builder.getVariable("results-table")
|
|
||||||
table.model.fireTableDataChanged()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -105,9 +105,9 @@ class MainFrameView {
|
|||||||
borderLayout()
|
borderLayout()
|
||||||
tabbedPane(id : "result-tabs", constraints: BorderLayout.CENTER)
|
tabbedPane(id : "result-tabs", constraints: BorderLayout.CENTER)
|
||||||
panel(constraints : BorderLayout.SOUTH) {
|
panel(constraints : BorderLayout.SOUTH) {
|
||||||
button(text : "Download", enabled : bind {model.downloadActionEnabled}, downloadAction)
|
button(text : "Download", enabled : bind {model.searchButtonsEnabled}, downloadAction)
|
||||||
button(text : "Trust", enabled: bind {model.trustButtonsEnabled }, trustAction)
|
button(text : "Trust", enabled: bind {model.searchButtonsEnabled }, trustAction)
|
||||||
button(text : "Distrust", enabled : bind {model.trustButtonsEnabled}, distrustAction)
|
button(text : "Distrust", enabled : bind {model.searchButtonsEnabled}, distrustAction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
panel (constraints : JSplitPane.BOTTOM) {
|
panel (constraints : JSplitPane.BOTTOM) {
|
||||||
@@ -120,7 +120,7 @@ class MainFrameView {
|
|||||||
closureColumn(header: "Progress", preferredWidth: 20, type: String, read: { row ->
|
closureColumn(header: "Progress", preferredWidth: 20, type: String, read: { row ->
|
||||||
int pieces = row.downloader.nPieces
|
int pieces = row.downloader.nPieces
|
||||||
int done = row.downloader.donePieces()
|
int done = row.downloader.donePieces()
|
||||||
"$done/$pieces pieces".toString()
|
"$done/$pieces pieces"
|
||||||
})
|
})
|
||||||
closureColumn(header: "Sources", preferredWidth : 10, type: Integer, read : {row -> row.downloader.activeWorkers()})
|
closureColumn(header: "Sources", preferredWidth : 10, type: Integer, read : {row -> row.downloader.activeWorkers()})
|
||||||
closureColumn(header: "Speed", preferredWidth: 50, type:String, read :{row ->
|
closureColumn(header: "Speed", preferredWidth: 50, type:String, read :{row ->
|
||||||
@@ -139,32 +139,16 @@ class MainFrameView {
|
|||||||
panel (constraints: "uploads window"){
|
panel (constraints: "uploads window"){
|
||||||
gridLayout(cols : 1, rows : 2)
|
gridLayout(cols : 1, rows : 2)
|
||||||
panel {
|
panel {
|
||||||
gridLayout(cols : 2, rows : 1)
|
borderLayout()
|
||||||
panel {
|
panel (constraints : BorderLayout.NORTH) {
|
||||||
borderLayout()
|
button(text : "Click here to share files", actionPerformed : shareFiles)
|
||||||
panel (constraints : BorderLayout.NORTH) {
|
|
||||||
button(text : "Add directories to watch", actionPerformed : watchDirectories)
|
|
||||||
}
|
|
||||||
scrollPane (constraints : BorderLayout.CENTER) {
|
|
||||||
table(id : "watched-directories-table", autoCreateRowSorter: true) {
|
|
||||||
tableModel(list : model.watched) {
|
|
||||||
closureColumn(header: "Watched Directories", type : String, read : { it })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
panel {
|
scrollPane ( constraints : BorderLayout.CENTER) {
|
||||||
borderLayout()
|
table(id : "shared-files-table", autoCreateRowSorter: true) {
|
||||||
panel (constraints : BorderLayout.NORTH) {
|
tableModel(list : model.shared) {
|
||||||
button(text : "Share files", actionPerformed : shareFiles)
|
closureColumn(header : "Name", preferredWidth : 550, type : String, read : {row -> row.file.getAbsolutePath()})
|
||||||
}
|
closureColumn(header : "Size", preferredWidth : 50, type : Long, read : {row -> row.file.length() })
|
||||||
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.file.getAbsolutePath()})
|
|
||||||
closureColumn(header : "Size", preferredWidth : 100, type : Long, read : {row -> row.file.length() })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -176,13 +160,16 @@ class MainFrameView {
|
|||||||
scrollPane (constraints : BorderLayout.CENTER) {
|
scrollPane (constraints : BorderLayout.CENTER) {
|
||||||
table(id : "uploads-table") {
|
table(id : "uploads-table") {
|
||||||
tableModel(list : model.uploads) {
|
tableModel(list : model.uploads) {
|
||||||
closureColumn(header : "Name", type : String, read : {row -> row.getName() })
|
closureColumn(header : "Name", type : String, read : {row -> row.file.getName() })
|
||||||
closureColumn(header : "Progress", type : String, read : { row ->
|
closureColumn(header : "Progress", type : String, read : { row ->
|
||||||
int percent = row.getProgress()
|
int position = row.getPosition()
|
||||||
|
def range = row.request.getRange()
|
||||||
|
int total = range.end - range.start
|
||||||
|
int percent = (int)((position * 100.0) / total)
|
||||||
"$percent%"
|
"$percent%"
|
||||||
})
|
})
|
||||||
closureColumn(header : "Downloader", type : String, read : { row ->
|
closureColumn(header : "Downloader", type : String, read : { row ->
|
||||||
row.getDownloader()
|
row.request.downloader?.getHumanReadableName()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -482,26 +469,11 @@ class MainFrameView {
|
|||||||
|
|
||||||
def shareFiles = {
|
def shareFiles = {
|
||||||
def chooser = new JFileChooser()
|
def chooser = new JFileChooser()
|
||||||
chooser.setDialogTitle("Select file to share")
|
chooser.setDialogTitle("Select file or directory to share")
|
||||||
chooser.setFileSelectionMode(JFileChooser.FILES_ONLY)
|
chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES)
|
||||||
int rv = chooser.showOpenDialog(null)
|
int rv = chooser.showOpenDialog(null)
|
||||||
if (rv == JFileChooser.APPROVE_OPTION) {
|
if (rv == JFileChooser.APPROVE_OPTION) {
|
||||||
model.core.eventBus.publish(new FileSharedEvent(file : chooser.getSelectedFile()))
|
model.core.eventBus.publish(new FileSharedEvent(file : chooser.getSelectedFile()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def watchDirectories = {
|
|
||||||
def chooser = new JFileChooser()
|
|
||||||
chooser.setDialogTitle("Select directory to watch")
|
|
||||||
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY)
|
|
||||||
int rv = chooser.showOpenDialog(null)
|
|
||||||
if (rv == JFileChooser.APPROVE_OPTION) {
|
|
||||||
File f = chooser.getSelectedFile()
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -47,9 +47,8 @@ class SearchTabView {
|
|||||||
resultsTable = table(id : "results-table", autoCreateRowSorter : true) {
|
resultsTable = table(id : "results-table", autoCreateRowSorter : true) {
|
||||||
tableModel(list: model.results) {
|
tableModel(list: model.results) {
|
||||||
closureColumn(header: "Name", preferredWidth: 350, type: String, read : {row -> row.name.replace('<','_')})
|
closureColumn(header: "Name", preferredWidth: 350, type: String, read : {row -> row.name.replace('<','_')})
|
||||||
closureColumn(header: "Size", preferredWidth: 20, type: Long, read : {row -> row.size})
|
closureColumn(header: "Size", preferredWidth: 50, type: Long, read : {row -> row.size})
|
||||||
closureColumn(header: "Direct Sources", preferredWidth: 50, type : Integer, read : { row -> model.hashBucket[row.infohash].size()})
|
closureColumn(header: "Sources", preferredWidth: 10, type : Integer, read : { row -> model.hashBucket[row.infohash].size()})
|
||||||
closureColumn(header: "Possible Sources", preferredWidth : 50, type : Integer, read : {row -> model.sourcesBucket[row.infohash].size()})
|
|
||||||
closureColumn(header: "Sender", preferredWidth: 170, type: String, read : {row -> row.sender.getHumanReadableName()})
|
closureColumn(header: "Sender", preferredWidth: 170, type: String, read : {row -> row.sender.getHumanReadableName()})
|
||||||
closureColumn(header: "Trust", preferredWidth: 50, type: String, read : {row ->
|
closureColumn(header: "Trust", preferredWidth: 50, type: String, read : {row ->
|
||||||
model.core.trustService.getLevel(row.sender.destination).toString()
|
model.core.trustService.getLevel(row.sender.destination).toString()
|
||||||
@@ -67,13 +66,7 @@ class SearchTabView {
|
|||||||
def selectionModel = resultsTable.getSelectionModel()
|
def selectionModel = resultsTable.getSelectionModel()
|
||||||
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
|
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
|
||||||
selectionModel.addListSelectionListener( {
|
selectionModel.addListSelectionListener( {
|
||||||
int row = resultsTable.getSelectedRow()
|
mvcGroup.parentGroup.model.searchButtonsEnabled = true
|
||||||
if (row < 0)
|
|
||||||
return
|
|
||||||
if (lastSortEvent != null)
|
|
||||||
row = resultsTable.rowSorter.convertRowIndexToModel(row)
|
|
||||||
mvcGroup.parentGroup.model.trustButtonsEnabled = true
|
|
||||||
mvcGroup.parentGroup.model.downloadActionEnabled = mvcGroup.parentGroup.model.canDownload(model.results[row].infohash)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,18 +105,25 @@ class SearchTabView {
|
|||||||
resultsTable.rowSorter.setSortsOnUpdates(true)
|
resultsTable.rowSorter.setSortsOnUpdates(true)
|
||||||
|
|
||||||
|
|
||||||
|
JPopupMenu menu = new JPopupMenu()
|
||||||
|
JMenuItem download = new JMenuItem("Download")
|
||||||
|
download.addActionListener({mvcGroup.parentGroup.controller.download()})
|
||||||
|
menu.add(download)
|
||||||
|
JMenuItem copyHashToClipboard = new JMenuItem("Copy hash to clipboard")
|
||||||
|
copyHashToClipboard.addActionListener({mvcGroup.view.copyHashToClipboard()})
|
||||||
|
menu.add(copyHashToClipboard)
|
||||||
resultsTable.addMouseListener(new MouseAdapter() {
|
resultsTable.addMouseListener(new MouseAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void mouseClicked(MouseEvent e) {
|
public void mouseClicked(MouseEvent e) {
|
||||||
if (e.button == MouseEvent.BUTTON3)
|
if (e.button == MouseEvent.BUTTON3)
|
||||||
showPopupMenu(e)
|
showPopupMenu(menu, e)
|
||||||
else if (e.button == MouseEvent.BUTTON1 && e.clickCount == 2)
|
else if (e.button == MouseEvent.BUTTON1 && e.clickCount == 2)
|
||||||
mvcGroup.parentGroup.controller.download()
|
mvcGroup.parentGroup.controller.download()
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void mouseReleased(MouseEvent e) {
|
public void mouseReleased(MouseEvent e) {
|
||||||
if (e.button == MouseEvent.BUTTON3)
|
if (e.button == MouseEvent.BUTTON3)
|
||||||
showPopupMenu(e)
|
showPopupMenu(menu, e)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -131,21 +131,12 @@ class SearchTabView {
|
|||||||
def closeTab = {
|
def closeTab = {
|
||||||
int index = parent.indexOfTab(searchTerms)
|
int index = parent.indexOfTab(searchTerms)
|
||||||
parent.removeTabAt(index)
|
parent.removeTabAt(index)
|
||||||
mvcGroup.parentGroup.model.trustButtonsEnabled = false
|
mvcGroup.parentGroup.model.searchButtonsEnabled = false
|
||||||
mvcGroup.parentGroup.model.downloadActionEnabled = false
|
|
||||||
mvcGroup.destroy()
|
mvcGroup.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
def showPopupMenu(MouseEvent e) {
|
def showPopupMenu(JPopupMenu menu, MouseEvent e) {
|
||||||
JPopupMenu menu = new JPopupMenu()
|
println "showing popup menu"
|
||||||
if (mvcGroup.parentGroup.model.downloadActionEnabled) {
|
|
||||||
JMenuItem download = new JMenuItem("Download")
|
|
||||||
download.addActionListener({mvcGroup.parentGroup.controller.download()})
|
|
||||||
menu.add(download)
|
|
||||||
}
|
|
||||||
JMenuItem copyHashToClipboard = new JMenuItem("Copy hash to clipboard")
|
|
||||||
copyHashToClipboard.addActionListener({mvcGroup.view.copyHashToClipboard()})
|
|
||||||
menu.add(copyHashToClipboard)
|
|
||||||
menu.show(e.getComponent(), e.getX(), e.getY())
|
menu.show(e.getComponent(), e.getX(), e.getY())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user