Compare commits
9 Commits
muwire-0.6
...
muwire-0.6
Author | SHA1 | Date | |
---|---|---|---|
![]() |
761bf0a177 | ||
![]() |
bd873211c0 | ||
![]() |
036971cfe5 | ||
![]() |
a2637570b1 | ||
![]() |
6012adbeab | ||
![]() |
8f6b6b0caa | ||
![]() |
8f3b5aea8d | ||
![]() |
ee098ace8e | ||
![]() |
5d8401e4bf |
@@ -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.5.9 is avaiable for download at https://muwire.com. You can find technical documentation in the "doc" folder.
|
The current stable release - 0.6.0 is avaiable for download at https://muwire.com. You can find technical documentation in the "doc" folder.
|
||||||
|
|
||||||
### Building
|
### Building
|
||||||
|
|
||||||
|
@@ -32,7 +32,7 @@ import com.muwire.core.UILoadedEvent
|
|||||||
import com.muwire.core.files.AllFilesLoadedEvent
|
import com.muwire.core.files.AllFilesLoadedEvent
|
||||||
|
|
||||||
class CliLanterna {
|
class CliLanterna {
|
||||||
private static final String MW_VERSION = "0.6.0"
|
private static final String MW_VERSION = "0.6.2"
|
||||||
|
|
||||||
private static volatile Core core
|
private static volatile Core core
|
||||||
|
|
||||||
|
@@ -406,7 +406,7 @@ public class Core {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Core core = new Core(props, home, "0.6.0")
|
Core core = new Core(props, home, "0.6.2")
|
||||||
core.startServices()
|
core.startServices()
|
||||||
|
|
||||||
// ... at the end, sleep or execute script
|
// ... at the end, sleep or execute script
|
||||||
|
@@ -277,6 +277,57 @@ public class Downloader {
|
|||||||
executorService.submit(newWorker)
|
executorService.submit(newWorker)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean isSequential() {
|
||||||
|
pieces.ratio == 0f
|
||||||
|
}
|
||||||
|
|
||||||
|
File generatePreview() {
|
||||||
|
int lastCompletePiece = pieces.firstIncomplete() - 1
|
||||||
|
if (lastCompletePiece == -1)
|
||||||
|
return null
|
||||||
|
if (lastCompletePiece < -1)
|
||||||
|
return file
|
||||||
|
long previewableLength = (lastCompletePiece + 1) * ((long)pieceSize)
|
||||||
|
|
||||||
|
// generate name
|
||||||
|
long now = System.currentTimeMillis()
|
||||||
|
File previewFile
|
||||||
|
File parentFile = file.getParentFile()
|
||||||
|
int lastDot = file.getName().lastIndexOf('.')
|
||||||
|
if (lastDot < 0)
|
||||||
|
previewFile = new File(parentFile, file.getName() + "." + String.valueOf(now) + ".mwpreview")
|
||||||
|
else {
|
||||||
|
String name = file.getName().substring(0, lastDot)
|
||||||
|
String extension = file.getName().substring(lastDot + 1)
|
||||||
|
String previewName = name + "." + String.valueOf(now) + ".mwpreview."+extension
|
||||||
|
previewFile = new File(parentFile, previewName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy
|
||||||
|
InputStream is = null
|
||||||
|
OutputStream os = null
|
||||||
|
try {
|
||||||
|
is = new BufferedInputStream(new FileInputStream(incompleteFile))
|
||||||
|
os = new BufferedOutputStream(new FileOutputStream(previewFile))
|
||||||
|
byte [] tmp = new byte[0x1 << 13]
|
||||||
|
long totalCopied = 0
|
||||||
|
while(totalCopied < previewableLength) {
|
||||||
|
int read = is.read(tmp, 0, (int)Math.min(tmp.length, previewableLength - totalCopied))
|
||||||
|
if (read < 0)
|
||||||
|
throw new IOException("EOF?")
|
||||||
|
os.write(tmp, 0, read)
|
||||||
|
totalCopied += read
|
||||||
|
}
|
||||||
|
return previewFile
|
||||||
|
} catch (IOException bad) {
|
||||||
|
log.log(Level.WARNING,"Preview failed",bad)
|
||||||
|
return null
|
||||||
|
} finally {
|
||||||
|
try {is?.close() } catch (IOException ignore) {}
|
||||||
|
try {os?.close() } catch (IOException ignore) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class DownloadWorker implements Runnable {
|
class DownloadWorker implements Runnable {
|
||||||
private final Destination destination
|
private final Destination destination
|
||||||
private volatile WorkerState currentState
|
private volatile WorkerState currentState
|
||||||
|
@@ -108,6 +108,10 @@ class Pieces {
|
|||||||
partials.clear()
|
partials.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
synchronized int firstIncomplete() {
|
||||||
|
done.nextClearBit(0)
|
||||||
|
}
|
||||||
|
|
||||||
synchronized void write(PrintWriter writer) {
|
synchronized void write(PrintWriter writer) {
|
||||||
for (int i = done.nextSetBit(0); i >= 0; i = done.nextSetBit(i+1)) {
|
for (int i = done.nextSetBit(0); i >= 0; i = done.nextSetBit(i+1)) {
|
||||||
writer.println(i)
|
writer.println(i)
|
||||||
|
@@ -143,6 +143,7 @@ class FileManager {
|
|||||||
|
|
||||||
String comment = sf.getComment()
|
String comment = sf.getComment()
|
||||||
if (comment != null) {
|
if (comment != null) {
|
||||||
|
comment = DataUtil.readi18nString(Base64.decode(comment))
|
||||||
Set<File> existingComment = commentToFile.get(comment)
|
Set<File> existingComment = commentToFile.get(comment)
|
||||||
if (existingComment != null) {
|
if (existingComment != null) {
|
||||||
existingComment.remove(sf.getFile())
|
existingComment.remove(sf.getFile())
|
||||||
@@ -229,7 +230,7 @@ class FileManager {
|
|||||||
return files
|
return files
|
||||||
Set<SharedFile> rv = new HashSet<>()
|
Set<SharedFile> rv = new HashSet<>()
|
||||||
files.each {
|
files.each {
|
||||||
if (it.getPieceSize() != 0)
|
if (it != null && it.getPieceSize() != 0)
|
||||||
rv.add(it)
|
rv.add(it)
|
||||||
}
|
}
|
||||||
rv
|
rv
|
||||||
|
@@ -39,10 +39,11 @@ class SearchIndex {
|
|||||||
split.each { if (it.length() > 0) rv << it }
|
split.each { if (it.length() > 0) rv << it }
|
||||||
|
|
||||||
// then just by ' '
|
// then just by ' '
|
||||||
source.split(' ').each { if (it.length() > 0) rv << it }
|
source.toLowerCase().split(' ').each { if (it.length() > 0) rv << it }
|
||||||
|
|
||||||
// and add original string
|
// and add original string
|
||||||
rv << source
|
rv << source
|
||||||
|
rv << source.toLowerCase()
|
||||||
rv.toArray(new String[0])
|
rv.toArray(new String[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
package com.muwire.core.files
|
package com.muwire.core.files
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertAll
|
||||||
|
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
@@ -9,6 +11,9 @@ import com.muwire.core.MuWireSettings
|
|||||||
import com.muwire.core.SharedFile
|
import com.muwire.core.SharedFile
|
||||||
import com.muwire.core.search.ResultsEvent
|
import com.muwire.core.search.ResultsEvent
|
||||||
import com.muwire.core.search.SearchEvent
|
import com.muwire.core.search.SearchEvent
|
||||||
|
import com.muwire.core.util.DataUtil
|
||||||
|
|
||||||
|
import net.i2p.data.Base64
|
||||||
|
|
||||||
class FileManagerTest {
|
class FileManagerTest {
|
||||||
|
|
||||||
@@ -185,4 +190,39 @@ class FileManagerTest {
|
|||||||
assert results == null
|
assert results == null
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testComplicatedScenario() {
|
||||||
|
// this tries to reproduce an NPE when un-sharing then sharing again and searching
|
||||||
|
String comment = "same comment"
|
||||||
|
comment = Base64.encode(DataUtil.encodei18nString(comment))
|
||||||
|
File f1 = new File("MuWire-0.5.10.AppImage")
|
||||||
|
InfoHash ih1 = InfoHash.fromHashList(new byte[32])
|
||||||
|
SharedFile sf1 = new SharedFile(f1, ih1, 0)
|
||||||
|
sf1.setComment(comment)
|
||||||
|
|
||||||
|
manager.onFileLoadedEvent(new FileLoadedEvent(loadedFile : sf1))
|
||||||
|
manager.onFileUnsharedEvent(new FileUnsharedEvent(unsharedFile : sf1, deleted : true))
|
||||||
|
|
||||||
|
File f2 = new File("MuWire-0.6.0.AppImage")
|
||||||
|
InfoHash ih2 = InfoHash.fromHashList(new byte[64])
|
||||||
|
SharedFile sf2 = new SharedFile(f2, ih2, 0)
|
||||||
|
sf2.setComment(comment)
|
||||||
|
|
||||||
|
manager.onFileLoadedEvent(new FileLoadedEvent(loadedFile : sf2))
|
||||||
|
|
||||||
|
manager.onSearchEvent(new SearchEvent(searchTerms : ["muwire"]))
|
||||||
|
Thread.sleep(20)
|
||||||
|
|
||||||
|
assert results != null
|
||||||
|
assert results.results.size() == 1
|
||||||
|
assert results.results.contains(sf2)
|
||||||
|
|
||||||
|
results = null
|
||||||
|
manager.onSearchEvent(new SearchEvent(searchTerms : ['comment'], searchComments : true, oobInfohash : true))
|
||||||
|
Thread.sleep(20)
|
||||||
|
assert results != null
|
||||||
|
assert results.results.size() == 1
|
||||||
|
assert results.results.contains(sf2)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,6 +8,7 @@ import com.muwire.core.Destinations
|
|||||||
import com.muwire.core.Persona
|
import com.muwire.core.Persona
|
||||||
import com.muwire.core.Personas
|
import com.muwire.core.Personas
|
||||||
|
|
||||||
|
import groovy.json.JsonSlurper
|
||||||
import net.i2p.data.Base64
|
import net.i2p.data.Base64
|
||||||
import net.i2p.data.Destination
|
import net.i2p.data.Destination
|
||||||
|
|
||||||
@@ -55,13 +56,16 @@ class TrustServiceTest {
|
|||||||
service.onTrustEvent new TrustEvent(level: TrustLevel.DISTRUSTED, persona: personas.persona2)
|
service.onTrustEvent new TrustEvent(level: TrustLevel.DISTRUSTED, persona: personas.persona2)
|
||||||
|
|
||||||
Thread.sleep(250)
|
Thread.sleep(250)
|
||||||
|
JsonSlurper slurper = new JsonSlurper()
|
||||||
def trusted = new HashSet<>()
|
def trusted = new HashSet<>()
|
||||||
persistGood.eachLine {
|
persistGood.eachLine {
|
||||||
trusted.add(new Persona(new ByteArrayInputStream(Base64.decode(it))))
|
def json = slurper.parseText(it)
|
||||||
|
trusted.add(new Persona(new ByteArrayInputStream(Base64.decode(json.persona))))
|
||||||
}
|
}
|
||||||
def distrusted = new HashSet<>()
|
def distrusted = new HashSet<>()
|
||||||
persistBad.eachLine {
|
persistBad.eachLine {
|
||||||
distrusted.add(new Persona(new ByteArrayInputStream(Base64.decode(it))))
|
def json = slurper.parseText(it)
|
||||||
|
distrusted.add(new Persona(new ByteArrayInputStream(Base64.decode(json.persona))))
|
||||||
}
|
}
|
||||||
|
|
||||||
assert trusted.size() == 1
|
assert trusted.size() == 1
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
group = com.muwire
|
group = com.muwire
|
||||||
version = 0.6.0
|
version = 0.6.2
|
||||||
i2pVersion = 0.9.43
|
i2pVersion = 0.9.43
|
||||||
groovyVersion = 2.4.15
|
groovyVersion = 2.4.15
|
||||||
slf4jVersion = 1.7.25
|
slf4jVersion = 1.7.25
|
||||||
|
@@ -106,4 +106,9 @@ mvcGroups {
|
|||||||
view = 'com.muwire.gui.SharedFileView'
|
view = 'com.muwire.gui.SharedFileView'
|
||||||
controller = 'com.muwire.gui.SharedFileController'
|
controller = 'com.muwire.gui.SharedFileController'
|
||||||
}
|
}
|
||||||
|
'download-preview' {
|
||||||
|
model = "com.muwire.gui.DownloadPreviewModel"
|
||||||
|
view = "com.muwire.gui.DownloadPreviewView"
|
||||||
|
controller = "com.muwire.gui.DownloadPreviewController"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,13 @@
|
|||||||
|
package com.muwire.gui
|
||||||
|
|
||||||
|
import griffon.core.artifact.GriffonController
|
||||||
|
import griffon.core.controller.ControllerAction
|
||||||
|
import griffon.inject.MVCMember
|
||||||
|
import griffon.metadata.ArtifactProviderFor
|
||||||
|
import javax.annotation.Nonnull
|
||||||
|
|
||||||
|
@ArtifactProviderFor(GriffonController)
|
||||||
|
class DownloadPreviewController {
|
||||||
|
@MVCMember @Nonnull
|
||||||
|
DownloadPreviewModel model
|
||||||
|
}
|
@@ -205,6 +205,14 @@ class MainFrameController {
|
|||||||
core.eventBus.publish(new UIDownloadPausedEvent())
|
core.eventBus.publish(new UIDownloadPausedEvent())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ControllerAction
|
||||||
|
void preview() {
|
||||||
|
def downloader = model.downloads[selectedDownload()].downloader
|
||||||
|
def params = [:]
|
||||||
|
params['downloader'] = downloader
|
||||||
|
mvcGroup.createMVCGroup("download-preview", params)
|
||||||
|
}
|
||||||
|
|
||||||
@ControllerAction
|
@ControllerAction
|
||||||
void clear() {
|
void clear() {
|
||||||
def toRemove = []
|
def toRemove = []
|
||||||
|
@@ -0,0 +1,12 @@
|
|||||||
|
package com.muwire.gui
|
||||||
|
|
||||||
|
import com.muwire.core.download.Downloader
|
||||||
|
|
||||||
|
import griffon.core.artifact.GriffonModel
|
||||||
|
import griffon.transform.Observable
|
||||||
|
import griffon.metadata.ArtifactProviderFor
|
||||||
|
|
||||||
|
@ArtifactProviderFor(GriffonModel)
|
||||||
|
class DownloadPreviewModel {
|
||||||
|
Downloader downloader
|
||||||
|
}
|
@@ -100,6 +100,7 @@ class MainFrameModel {
|
|||||||
@Observable boolean retryButtonEnabled
|
@Observable boolean retryButtonEnabled
|
||||||
@Observable boolean pauseButtonEnabled
|
@Observable boolean pauseButtonEnabled
|
||||||
@Observable boolean clearButtonEnabled
|
@Observable boolean clearButtonEnabled
|
||||||
|
@Observable boolean previewButtonEnabled
|
||||||
@Observable String resumeButtonText
|
@Observable String resumeButtonText
|
||||||
@Observable boolean addCommentButtonEnabled
|
@Observable boolean addCommentButtonEnabled
|
||||||
@Observable boolean subscribeButtonEnabled
|
@Observable boolean subscribeButtonEnabled
|
||||||
|
@@ -0,0 +1,61 @@
|
|||||||
|
package com.muwire.gui
|
||||||
|
|
||||||
|
import griffon.core.artifact.GriffonView
|
||||||
|
import griffon.inject.MVCMember
|
||||||
|
import griffon.metadata.ArtifactProviderFor
|
||||||
|
|
||||||
|
import javax.swing.Box
|
||||||
|
import javax.swing.JDialog
|
||||||
|
import javax.swing.JOptionPane
|
||||||
|
import javax.swing.SwingConstants
|
||||||
|
|
||||||
|
import java.awt.event.WindowAdapter
|
||||||
|
import java.awt.event.WindowEvent
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull
|
||||||
|
|
||||||
|
@ArtifactProviderFor(GriffonView)
|
||||||
|
class DownloadPreviewView {
|
||||||
|
@MVCMember @Nonnull
|
||||||
|
FactoryBuilderSupport builder
|
||||||
|
@MVCMember @Nonnull
|
||||||
|
DownloadPreviewModel model
|
||||||
|
|
||||||
|
def mainFrame
|
||||||
|
def dialog
|
||||||
|
def panel
|
||||||
|
|
||||||
|
void initUI() {
|
||||||
|
mainFrame = application.windowManager.findWindow("main-frame")
|
||||||
|
|
||||||
|
dialog = new JDialog(mainFrame, "Generating Preview", true)
|
||||||
|
|
||||||
|
panel = builder.panel {
|
||||||
|
vbox {
|
||||||
|
label(text : "Generating preview for "+model.downloader.file.getName())
|
||||||
|
Box.createVerticalGlue()
|
||||||
|
progressBar(indeterminate : true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.getContentPane().add(panel)
|
||||||
|
dialog.pack()
|
||||||
|
dialog.setResizable(false)
|
||||||
|
dialog.setLocationRelativeTo(mainFrame)
|
||||||
|
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
|
||||||
|
dialog.addWindowListener(new WindowAdapter() {
|
||||||
|
public void windowClosed(WindowEvent e) {
|
||||||
|
mainFrame.setVisible(false)
|
||||||
|
mvcGroup.destroy()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
void mvcGroupInit(Map<String, String> args) {
|
||||||
|
if (!model.downloader.isSequential())
|
||||||
|
JOptionPane.showMessageDialog(mainFrame, "This download is not sequential, there may not be much to preview")
|
||||||
|
DownloadPreviewer previewer = new DownloadPreviewer(model.downloader, this)
|
||||||
|
previewer.execute()
|
||||||
|
dialog.show()
|
||||||
|
}
|
||||||
|
}
|
@@ -216,6 +216,7 @@ class MainFrameView {
|
|||||||
button(text: "Pause", enabled : bind {model.pauseButtonEnabled}, pauseAction)
|
button(text: "Pause", enabled : bind {model.pauseButtonEnabled}, pauseAction)
|
||||||
button(text: bind { model.resumeButtonText }, enabled : bind {model.retryButtonEnabled}, resumeAction)
|
button(text: bind { model.resumeButtonText }, enabled : bind {model.retryButtonEnabled}, resumeAction)
|
||||||
button(text: "Cancel", enabled : bind {model.cancelButtonEnabled }, cancelAction)
|
button(text: "Cancel", enabled : bind {model.cancelButtonEnabled }, cancelAction)
|
||||||
|
button(text: "Preview", enabled : bind {model.previewButtonEnabled}, previewAction)
|
||||||
button(text: "Clear Done", enabled : bind {model.clearButtonEnabled}, clearAction)
|
button(text: "Clear Done", enabled : bind {model.clearButtonEnabled}, clearAction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -536,6 +537,7 @@ class MainFrameView {
|
|||||||
model.cancelButtonEnabled = false
|
model.cancelButtonEnabled = false
|
||||||
model.retryButtonEnabled = false
|
model.retryButtonEnabled = false
|
||||||
model.pauseButtonEnabled = false
|
model.pauseButtonEnabled = false
|
||||||
|
model.previewButtonEnabled = false
|
||||||
model.downloader = null
|
model.downloader = null
|
||||||
downloadDetailsPanel.getLayout().show(downloadDetailsPanel,"select-download")
|
downloadDetailsPanel.getLayout().show(downloadDetailsPanel,"select-download")
|
||||||
return
|
return
|
||||||
@@ -544,6 +546,7 @@ class MainFrameView {
|
|||||||
if (downloader == null)
|
if (downloader == null)
|
||||||
return
|
return
|
||||||
model.downloader = downloader
|
model.downloader = downloader
|
||||||
|
model.previewButtonEnabled = true
|
||||||
downloadDetailsPanel.getLayout().show(downloadDetailsPanel,"download-selected")
|
downloadDetailsPanel.getLayout().show(downloadDetailsPanel,"download-selected")
|
||||||
switch(downloader.getCurrentState()) {
|
switch(downloader.getCurrentState()) {
|
||||||
case Downloader.DownloadState.CONNECTING :
|
case Downloader.DownloadState.CONNECTING :
|
||||||
|
36
gui/src/main/groovy/com/muwire/gui/DownloadPreviewer.groovy
Normal file
36
gui/src/main/groovy/com/muwire/gui/DownloadPreviewer.groovy
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package com.muwire.gui
|
||||||
|
|
||||||
|
import java.awt.Desktop
|
||||||
|
|
||||||
|
import javax.swing.JDialog
|
||||||
|
import javax.swing.JOptionPane
|
||||||
|
import javax.swing.SwingWorker
|
||||||
|
|
||||||
|
import com.muwire.core.download.Downloader
|
||||||
|
|
||||||
|
class DownloadPreviewer extends SwingWorker {
|
||||||
|
|
||||||
|
private final Downloader downloader
|
||||||
|
private final DownloadPreviewView view
|
||||||
|
|
||||||
|
DownloadPreviewer(Downloader downloader, DownloadPreviewView view) {
|
||||||
|
this.downloader = downloader
|
||||||
|
this.view = view
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Object doInBackground() throws Exception {
|
||||||
|
downloader.generatePreview()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void done() {
|
||||||
|
File previewFile = get()
|
||||||
|
view.dialog.setVisible(false)
|
||||||
|
view.mvcGroup.destroy()
|
||||||
|
if (previewFile == null)
|
||||||
|
JOptionPane.showMessageDialog(null, "Generating preview file failed", "Preview Failed", JOptionPane.ERROR_MESSAGE)
|
||||||
|
else
|
||||||
|
Desktop.getDesktop().open(previewFile)
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user