Compare commits

..

9 Commits

Author SHA1 Message Date
Zlatin Balevsky
59c84d8a5e Release 0.2.8 2019-06-18 17:48:07 +01:00
Zlatin Balevsky
8b55021a4b fix 2019-06-18 17:23:18 +01:00
Zlatin Balevsky
8bd3ebfaf5 timestamp entries 2019-06-18 17:17:03 +01:00
Zlatin Balevsky
526ec45da3 Release 0.2.7 2019-06-18 15:53:54 +01:00
Zlatin Balevsky
deb7c0b4b0 exclude files present locally from search results 2019-06-18 15:45:27 +01:00
Zlatin Balevsky
e85a0c7b2c Merge branch 'source-tracking' 2019-06-18 12:22:46 +01:00
Zlatin Balevsky
7b021a47eb fix detection of moving files into a watched dir on Linux 2019-06-18 12:20:10 +01:00
Zlatin Balevsky
0c21d4d6c1 implement source tracking 2019-06-18 11:34:19 +01:00
Zlatin Balevsky
8e9f79d404 update TODO 2019-06-18 09:43:22 +01:00
16 changed files with 73 additions and 17 deletions

View File

@@ -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)

View File

@@ -35,7 +35,7 @@ class Cli {
Core core Core core
try { try {
core = new Core(props, home, "0.2.6") core = new Core(props, home, "0.2.8")
} 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"
@@ -73,7 +73,7 @@ class Cli {
Timer timer = new Timer("status-printer", true) Timer timer = new Timer("status-printer", true)
timer.schedule({ timer.schedule({
println "Connections $connectionsListener.connections Uploads $uploadsListener.uploads Shared $sharedListener.shared" println String.valueOf(new Date()) + " Connections $connectionsListener.connections Uploads $uploadsListener.uploads Shared $sharedListener.shared"
} as TimerTask, 60000, 60000) } as TimerTask, 60000, 60000)
def latch = new CountDownLatch(1) def latch = new CountDownLatch(1)
@@ -119,11 +119,11 @@ class Cli {
volatile int uploads volatile int uploads
public void onUploadEvent(UploadEvent e) { public void onUploadEvent(UploadEvent e) {
uploads++ uploads++
println "Starting upload of ${e.uploader.file.getName()} to ${e.uploader.request.downloader.getHumanReadableName()}" println String.valueOf(new Date()) + " Starting upload of ${e.uploader.file.getName()} to ${e.uploader.request.downloader.getHumanReadableName()}"
} }
public void onUploadFinishedEvent(UploadFinishedEvent e) { public void onUploadFinishedEvent(UploadFinishedEvent e) {
uploads-- uploads--
println "Finished upload of ${e.uploader.file.getName()} to ${e.uploader.request.downloader.getHumanReadableName()}" println String.valueOf(new Date()) + " Finished upload of ${e.uploader.file.getName()} to ${e.uploader.request.downloader.getHumanReadableName()}"
} }
} }

View File

@@ -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.8")
} 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"

View File

@@ -72,6 +72,7 @@ public class Core {
private final HasherService hasherService private final HasherService hasherService
private final DownloadManager downloadManager private final DownloadManager downloadManager
private final DirectoryWatcher directoryWatcher private final DirectoryWatcher directoryWatcher
final FileManager fileManager
public Core(MuWireSettings props, File home, String myVersion) { public Core(MuWireSettings props, File home, String myVersion) {
this.home = home this.home = home
@@ -155,7 +156,7 @@ public class Core {
log.info "initializing file manager" log.info "initializing file manager"
FileManager fileManager = new FileManager(eventBus, props) fileManager = new FileManager(eventBus, props)
eventBus.register(FileHashedEvent.class, fileManager) eventBus.register(FileHashedEvent.class, fileManager)
eventBus.register(FileLoadedEvent.class, fileManager) eventBus.register(FileLoadedEvent.class, fileManager)
eventBus.register(FileDownloadedEvent.class, fileManager) eventBus.register(FileDownloadedEvent.class, fileManager)
@@ -276,7 +277,7 @@ public class Core {
} }
} }
Core core = new Core(props, home, "0.2.6") Core core = new Core(props, home, "0.2.8")
core.startServices() core.startServices()
// ... at the end, sleep or execute script // ... at the end, sleep or execute script

View File

@@ -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,

View File

@@ -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 ")) {

View File

@@ -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))
} }

View File

@@ -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
} }

View File

@@ -91,6 +91,8 @@ class DirectoryWatcher {
log.fine("created entry $f") log.fine("created entry $f")
if (f.isDirectory()) if (f.isDirectory())
f.toPath().register(watchService, kinds) f.toPath().register(watchService, kinds)
else
waitingFiles.put(f, System.currentTimeMillis())
} }
private void processModified(Path parent, Path path) { private void processModified(Path parent, Path path) {

View File

@@ -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)

View File

@@ -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))

View File

@@ -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

View File

@@ -1,5 +1,5 @@
group = com.muwire group = com.muwire
version = 0.2.6 version = 0.2.8
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

View File

@@ -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

View File

@@ -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) {
@@ -37,7 +38,7 @@ class SearchTabModel {
void handleResult(UIResultEvent e) { void handleResult(UIResultEvent e) {
if (uiSettings.excludeLocalResult && if (uiSettings.excludeLocalResult &&
e.sender == core.me) core.fileManager.rootToFiles.containsKey(e.infohash))
return return
runInsideUIAsync { runInsideUIAsync {
def bucket = hashBucket.get(e.infohash) def bucket = hashBucket.get(e.infohash)
@@ -47,6 +48,13 @@ class SearchTabModel {
} }
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()
@@ -56,13 +64,22 @@ class SearchTabModel {
void handleResultBatch(UIResultEvent[] batch) { void handleResultBatch(UIResultEvent[] batch) {
runInsideUIAsync { runInsideUIAsync {
batch.each { batch.each {
if (uiSettings.excludeLocalResult && it.sender == core.me) if (uiSettings.excludeLocalResult &&
core.fileManager.rootToFiles.containsKey(it.infohash))
return return
def bucket = hashBucket.get(it.infohash) def bucket = hashBucket.get(it.infohash)
if (bucket == null) { if (bucket == null) {
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
} }

View File

@@ -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()