Compare commits
5 Commits
file-monit
...
source-tra
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c21d4d6c1 | ||
|
|
8e9f79d404 | ||
|
|
bf33a6ff61 | ||
|
|
19c8d84afd | ||
|
|
0c1008d6b3 |
@@ -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.1.5 is avaiable for download at http://muwire.com. You can find technical documentation in the "doc" folder.
|
The current stable release - 0.2.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.post=<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.port=<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,6 +32,10 @@ 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
|
||||||
@@ -39,4 +43,3 @@ Basically any non-gui non-cli user interface
|
|||||||
* 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)
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class Cli {
|
|||||||
|
|
||||||
Core core
|
Core core
|
||||||
try {
|
try {
|
||||||
core = new Core(props, home, "0.2.5")
|
core = new Core(props, home, "0.2.6")
|
||||||
} catch (Exception bad) {
|
} catch (Exception bad) {
|
||||||
bad.printStackTrace(System.out)
|
bad.printStackTrace(System.out)
|
||||||
println "Failed to initialize core, exiting"
|
println "Failed to initialize core, exiting"
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class CliDownloader {
|
|||||||
|
|
||||||
Core core
|
Core core
|
||||||
try {
|
try {
|
||||||
core = new Core(props, home, "0.2.5")
|
core = new Core(props, home, "0.2.6")
|
||||||
} 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"
|
||||||
|
|||||||
@@ -276,7 +276,7 @@ public class Core {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Core core = new Core(props, home, "0.2.5")
|
Core core = new Core(props, home, "0.2.6")
|
||||||
core.startServices()
|
core.startServices()
|
||||||
|
|
||||||
// ... at the end, sleep or execute script
|
// ... at the end, sleep or execute script
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ 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,
|
||||||
|
|||||||
@@ -85,7 +85,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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (code.startsWith("416 ")) {
|
if (code.startsWith("416 ")) {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ 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 {
|
||||||
@@ -49,6 +50,7 @@ public class Downloader {
|
|||||||
private final File incompleteFile
|
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
|
||||||
@@ -249,6 +251,7 @@ public class Downloader {
|
|||||||
requestPerformed = currentSession.request()
|
requestPerformed = currentSession.request()
|
||||||
if (!requestPerformed)
|
if (!requestPerformed)
|
||||||
break
|
break
|
||||||
|
successfulDestinations.add(endpoint.destination)
|
||||||
writePieces()
|
writePieces()
|
||||||
}
|
}
|
||||||
} catch (Exception bad) {
|
} catch (Exception bad) {
|
||||||
@@ -268,7 +271,7 @@ public class Downloader {
|
|||||||
}
|
}
|
||||||
eventBus.publish(
|
eventBus.publish(
|
||||||
new FileDownloadedEvent(
|
new FileDownloadedEvent(
|
||||||
downloadedFile : new DownloadedFile(file, getInfoHash(), pieceSizePow2, Collections.emptySet()),
|
downloadedFile : new DownloadedFile(file, getInfoHash(), pieceSizePow2, successfulDestinations),
|
||||||
downloader : Downloader.this))
|
downloader : Downloader.this))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,11 @@ 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,5 +1,7 @@
|
|||||||
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
|
||||||
@@ -7,6 +9,7 @@ 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 {
|
||||||
@@ -58,6 +61,7 @@ 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)
|
||||||
@@ -82,11 +86,17 @@ 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,7 +11,9 @@ 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
|
||||||
|
|
||||||
@@ -54,12 +56,16 @@ 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)
|
||||||
}
|
}
|
||||||
@@ -110,6 +116,10 @@ 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))
|
||||||
|
|||||||
@@ -4,8 +4,11 @@ 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
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
group = com.muwire
|
group = com.muwire
|
||||||
version = 0.2.5
|
version = 0.2.6
|
||||||
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
|
||||||
|
|||||||
@@ -129,8 +129,9 @@ class MainFrameController {
|
|||||||
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, target : file))
|
core.eventBus.publish(new UIDownloadEvent(result : resultsBucket, sources: sources, target : file))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ControllerAction
|
@ControllerAction
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ 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) {
|
||||||
@@ -46,6 +47,13 @@ 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")
|
||||||
@@ -63,6 +71,14 @@ class SearchTabModel {
|
|||||||
bucket = []
|
bucket = []
|
||||||
hashBucket[it.infohash] = 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
|
bucket << it
|
||||||
results << it
|
results << it
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,8 +47,9 @@ 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: 50, type: Long, read : {row -> row.size})
|
closureColumn(header: "Size", preferredWidth: 20, type: Long, read : {row -> row.size})
|
||||||
closureColumn(header: "Sources", preferredWidth: 10, type : Integer, read : { row -> model.hashBucket[row.infohash].size()})
|
closureColumn(header: "Direct Sources", preferredWidth: 50, 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()
|
||||||
|
|||||||
Reference in New Issue
Block a user