Compare commits
13 Commits
auto-downl
...
fix-double
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d830d9261f | ||
![]() |
f5e1833a48 | ||
![]() |
9feb2a3c8f | ||
![]() |
b27665f5dd | ||
![]() |
4465aa4134 | ||
![]() |
ad766ac748 | ||
![]() |
d9e7d67d86 | ||
![]() |
3fefbc94b3 | ||
![]() |
21034209a5 | ||
![]() |
7c04c0f83c | ||
![]() |
f5293d65dd | ||
![]() |
8191bf6066 | ||
![]() |
29b6bfd463 |
@@ -23,7 +23,7 @@ Some of the UI tests will fail because they haven't been written yet :-/
|
||||
|
||||
### 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.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.
|
||||
|
||||
|
6
TODO.md
6
TODO.md
@@ -20,10 +20,6 @@ For helping users make better decisions whom to trust
|
||||
|
||||
To allow every user to not route queries for content they do not like. This is mostly GUI work, the backend part is simple
|
||||
|
||||
##### Packaging With JRE, Embedded Router
|
||||
|
||||
For ease of deployment for new users, and so that users do not need to run a separate I2P router
|
||||
|
||||
##### Web UI, REST Interface, etc.
|
||||
|
||||
Basically any non-gui non-cli user interface
|
||||
@@ -36,5 +32,5 @@ To enable parsing of metadata from known file types and the user editing it or a
|
||||
|
||||
* Wrapper of some kind for in-place upgrades
|
||||
* Download file sequentially
|
||||
* Unsharing of files
|
||||
* Unsharing of files (half done)
|
||||
* Multiple-selection download, Ctrl-A
|
||||
|
@@ -35,7 +35,7 @@ class Cli {
|
||||
|
||||
Core core
|
||||
try {
|
||||
core = new Core(props, home, "0.4.2")
|
||||
core = new Core(props, home, "0.4.4")
|
||||
} catch (Exception bad) {
|
||||
bad.printStackTrace(System.out)
|
||||
println "Failed to initialize core, exiting"
|
||||
|
@@ -53,7 +53,7 @@ class CliDownloader {
|
||||
|
||||
Core core
|
||||
try {
|
||||
core = new Core(props, home, "0.4.2")
|
||||
core = new Core(props, home, "0.4.4")
|
||||
} catch (Exception bad) {
|
||||
bad.printStackTrace(System.out)
|
||||
println "Failed to initialize core, exiting"
|
||||
|
@@ -9,5 +9,5 @@ class Constants {
|
||||
public static final int MAX_HEADER_SIZE = 0x1 << 14
|
||||
public static final int MAX_HEADERS = 16
|
||||
|
||||
public static final String SPLIT_PATTERN = "[\\*\\+\\-,\\.:;\\(\\)=_/\\\\\\!\\\"\\\'\\\$%\\|\\[\\]\\{\\}]"
|
||||
public static final String SPLIT_PATTERN = "[\\*\\+\\-,\\.:;\\(\\)=_/\\\\\\!\\\"\\\'\\\$%\\|\\[\\]\\{\\}\\?]"
|
||||
}
|
||||
|
@@ -352,7 +352,7 @@ public class Core {
|
||||
}
|
||||
}
|
||||
|
||||
Core core = new Core(props, home, "0.4.2")
|
||||
Core core = new Core(props, home, "0.4.4")
|
||||
core.startServices()
|
||||
|
||||
// ... at the end, sleep or execute script
|
||||
|
@@ -14,6 +14,7 @@ class MuWireSettings {
|
||||
int downloadRetryInterval
|
||||
int updateCheckInterval
|
||||
boolean autoDownloadUpdate
|
||||
String updateType
|
||||
String nickname
|
||||
File downloadLocation
|
||||
CrawlerResponse crawlerResponse
|
||||
@@ -39,6 +40,7 @@ class MuWireSettings {
|
||||
downloadRetryInterval = Integer.parseInt(props.getProperty("downloadRetryInterval","1"))
|
||||
updateCheckInterval = Integer.parseInt(props.getProperty("updateCheckInterval","24"))
|
||||
autoDownloadUpdate = Boolean.parseBoolean(props.getProperty("autoDownloadUpdate","true"))
|
||||
updateType = props.getProperty("updateType","jar")
|
||||
shareDownloadedFiles = Boolean.parseBoolean(props.getProperty("shareDownloadedFiles","true"))
|
||||
downloadSequentialRatio = Float.valueOf(props.getProperty("downloadSequentialRatio","0.8"))
|
||||
hostClearInterval = Integer.valueOf(props.getProperty("hostClearInterval","60"))
|
||||
@@ -65,6 +67,7 @@ class MuWireSettings {
|
||||
props.setProperty("downloadRetryInterval", String.valueOf(downloadRetryInterval))
|
||||
props.setProperty("updateCheckInterval", String.valueOf(updateCheckInterval))
|
||||
props.setProperty("autoDownloadUpdate", String.valueOf(autoDownloadUpdate))
|
||||
props.setProperty("updateType",updateType)
|
||||
props.setProperty("shareDownloadedFiles", String.valueOf(shareDownloadedFiles))
|
||||
props.setProperty("downloadSequentialRatio", String.valueOf(downloadSequentialRatio))
|
||||
props.setProperty("hostClearInterval", String.valueOf(hostClearInterval))
|
||||
|
@@ -82,4 +82,13 @@ public class Persona {
|
||||
Persona other = (Persona)o
|
||||
name.equals(other.name) && destination.equals(other.destination)
|
||||
}
|
||||
|
||||
public static void main(String []args) {
|
||||
if (args.length != 1) {
|
||||
println "This utility decodes a bas64-encoded persona"
|
||||
System.exit(1)
|
||||
}
|
||||
Persona p = new Persona(new ByteArrayInputStream(Base64.decode(args[0])))
|
||||
println p.getHumanReadableName()
|
||||
}
|
||||
}
|
||||
|
@@ -198,6 +198,7 @@ class DownloadSession {
|
||||
|
||||
mapped.clear()
|
||||
digest.update(mapped)
|
||||
DataUtil.tryUnmap(mapped)
|
||||
byte [] hash = digest.digest()
|
||||
byte [] expected = new byte[32]
|
||||
System.arraycopy(infoHash.getHashList(), piece * 32, expected, 0, 32)
|
||||
|
@@ -120,7 +120,7 @@ class DirectoryWatcher {
|
||||
|
||||
private static File join(Path parent, Path path) {
|
||||
File parentFile = parent.toFile().getCanonicalFile()
|
||||
new File(parentFile, path.toFile().getName())
|
||||
new File(parentFile, path.toFile().getName()).getCanonicalFile()
|
||||
}
|
||||
|
||||
private void publish() {
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package com.muwire.core.files
|
||||
|
||||
import com.muwire.core.InfoHash
|
||||
import com.muwire.core.util.DataUtil
|
||||
|
||||
import net.i2p.data.Base64
|
||||
|
||||
@@ -18,6 +19,8 @@ class FileHasher {
|
||||
/**
|
||||
* @param size of the file to be shared
|
||||
* @return the size of each piece in power of 2
|
||||
* piece size is minimum 128 KBytees and maximum 16 MBytes in power of 2 steps (2^17 - 2^24)
|
||||
* there can be up to 8192 pieces maximum per file
|
||||
*/
|
||||
static int getPieceSize(long size) {
|
||||
if (size <= 0x1 << 30)
|
||||
@@ -57,6 +60,7 @@ class FileHasher {
|
||||
for (int i = 0; i < numPieces - 1; i++) {
|
||||
buf = raf.getChannel().map(MapMode.READ_ONLY, ((long)size) * i, size)
|
||||
digest.update buf
|
||||
DataUtil.tryUnmap(buf)
|
||||
output.write(digest.digest(), 0, 32)
|
||||
}
|
||||
def lastPieceLength = length - (numPieces - 1) * ((long)size)
|
||||
|
@@ -24,7 +24,7 @@ class HasherService {
|
||||
}
|
||||
|
||||
void onFileSharedEvent(FileSharedEvent evt) {
|
||||
if (fileManager.fileToSharedFile.containsKey(evt.file))
|
||||
if (fileManager.fileToSharedFile.containsKey(evt.file.getCanonicalFile()))
|
||||
return
|
||||
executor.execute( { -> process(evt.file) } as Runnable)
|
||||
}
|
||||
|
@@ -140,13 +140,20 @@ class UpdateClient {
|
||||
log.info("no new version available")
|
||||
return
|
||||
}
|
||||
|
||||
String infoHash
|
||||
if (settings.updateType == "jar") {
|
||||
infoHash = payload.infoHash
|
||||
} else
|
||||
infoHash = payload[settings.updateType]
|
||||
|
||||
|
||||
if (!settings.autoDownloadUpdate) {
|
||||
log.info("new version $payload.version available, publishing event")
|
||||
eventBus.publish(new UpdateAvailableEvent(version : payload.version, signer : payload.signer, infoHash : payload.infoHash))
|
||||
eventBus.publish(new UpdateAvailableEvent(version : payload.version, signer : payload.signer, infoHash : infoHash))
|
||||
} else {
|
||||
log.info("new version $payload.version available")
|
||||
updateInfoHash = new InfoHash(Base64.decode(payload.infoHash))
|
||||
updateInfoHash = new InfoHash(Base64.decode(infoHash))
|
||||
if (fileManager.rootToFiles.containsKey(updateInfoHash))
|
||||
eventBus.publish(new UpdateDownloadedEvent(version : payload.version, signer : payload.signer))
|
||||
else {
|
||||
|
@@ -56,7 +56,7 @@ class ContentUploader extends Uploader {
|
||||
writeMesh(request.downloader)
|
||||
os.write("\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||
|
||||
FileChannel channel
|
||||
FileChannel channel = null
|
||||
try {
|
||||
channel = Files.newByteChannel(file.toPath(), EnumSet.of(StandardOpenOption.READ))
|
||||
mapped = channel.map(FileChannel.MapMode.READ_ONLY, range.start, range.end - range.start + 1)
|
||||
@@ -72,6 +72,10 @@ class ContentUploader extends Uploader {
|
||||
} finally {
|
||||
try {channel?.close() } catch (IOException ignored) {}
|
||||
endpoint.getOutputStream().flush()
|
||||
synchronized(this) {
|
||||
DataUtil.tryUnmap(mapped)
|
||||
mapped = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,8 @@
|
||||
package com.muwire.core.util
|
||||
|
||||
import java.lang.reflect.Field
|
||||
import java.lang.reflect.Method
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
import com.muwire.core.Constants
|
||||
@@ -115,4 +118,39 @@ class DataUtil {
|
||||
e = e.getCause()
|
||||
e
|
||||
}
|
||||
|
||||
public static void tryUnmap(ByteBuffer cb) {
|
||||
if (cb==null || !cb.isDirect()) return;
|
||||
// we could use this type cast and call functions without reflection code,
|
||||
// but static import from sun.* package is risky for non-SUN virtual machine.
|
||||
//try { ((sun.nio.ch.DirectBuffer)cb).cleaner().clean(); } catch (Exception ex) { }
|
||||
|
||||
// JavaSpecVer: 1.6, 1.7, 1.8, 9, 10
|
||||
boolean isOldJDK = System.getProperty("java.specification.version","99").startsWith("1.");
|
||||
try {
|
||||
if (isOldJDK) {
|
||||
Method cleaner = cb.getClass().getMethod("cleaner");
|
||||
cleaner.setAccessible(true);
|
||||
Method clean = Class.forName("sun.misc.Cleaner").getMethod("clean");
|
||||
clean.setAccessible(true);
|
||||
clean.invoke(cleaner.invoke(cb));
|
||||
} else {
|
||||
Class unsafeClass;
|
||||
try {
|
||||
unsafeClass = Class.forName("sun.misc.Unsafe");
|
||||
} catch(Exception ex) {
|
||||
// jdk.internal.misc.Unsafe doesn't yet have an invokeCleaner() method,
|
||||
// but that method should be added if sun.misc.Unsafe is removed.
|
||||
unsafeClass = Class.forName("jdk.internal.misc.Unsafe");
|
||||
}
|
||||
Method clean = unsafeClass.getMethod("invokeCleaner", ByteBuffer.class);
|
||||
clean.setAccessible(true);
|
||||
Field theUnsafeField = unsafeClass.getDeclaredField("theUnsafe");
|
||||
theUnsafeField.setAccessible(true);
|
||||
Object theUnsafe = theUnsafeField.get(null);
|
||||
clean.invoke(theUnsafe, cb);
|
||||
}
|
||||
} catch(Exception ex) { }
|
||||
cb = null;
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
group = com.muwire
|
||||
version = 0.4.2
|
||||
version = 0.4.4
|
||||
groovyVersion = 2.4.15
|
||||
slf4jVersion = 1.7.25
|
||||
spockVersion = 1.1-groovy-2.4
|
||||
|
@@ -14,12 +14,14 @@ import javax.inject.Inject
|
||||
|
||||
import com.muwire.core.Constants
|
||||
import com.muwire.core.Core
|
||||
import com.muwire.core.SharedFile
|
||||
import com.muwire.core.download.DownloadStartedEvent
|
||||
import com.muwire.core.download.UIDownloadCancelledEvent
|
||||
import com.muwire.core.download.UIDownloadEvent
|
||||
import com.muwire.core.download.UIDownloadPausedEvent
|
||||
import com.muwire.core.download.UIDownloadResumedEvent
|
||||
import com.muwire.core.files.DirectoryUnsharedEvent
|
||||
import com.muwire.core.files.FileUnsharedEvent
|
||||
import com.muwire.core.search.QueryEvent
|
||||
import com.muwire.core.search.SearchEvent
|
||||
import com.muwire.core.trust.TrustEvent
|
||||
@@ -33,6 +35,8 @@ class MainFrameController {
|
||||
|
||||
@MVCMember @Nonnull
|
||||
MainFrameModel model
|
||||
@MVCMember @Nonnull
|
||||
MainFrameView view
|
||||
|
||||
private volatile Core core
|
||||
|
||||
@@ -206,8 +210,11 @@ class MainFrameController {
|
||||
markTrust("trusted-table", TrustLevel.NEUTRAL, model.trusted)
|
||||
}
|
||||
|
||||
void unshareSelectedFiles() {
|
||||
println "unsharing selected files"
|
||||
void unshareSelectedFile() {
|
||||
SharedFile sf = view.selectedSharedFile()
|
||||
if (sf == null)
|
||||
return
|
||||
core.eventBus.publish(new FileUnsharedEvent(unsharedFile : sf))
|
||||
}
|
||||
|
||||
void stopWatchingDirectory() {
|
||||
|
@@ -49,6 +49,7 @@ class Ready extends AbstractLifecycleHandler {
|
||||
log.info("creating new properties")
|
||||
props = new MuWireSettings()
|
||||
props.embeddedRouter = Boolean.parseBoolean(System.getProperties().getProperty("embeddedRouter"))
|
||||
props.updateType = System.getProperty("updateType")
|
||||
def nickname
|
||||
while (true) {
|
||||
nickname = JOptionPane.showInputDialog(null,
|
||||
|
@@ -202,7 +202,7 @@ class MainFrameModel {
|
||||
|
||||
void onUIResultBatchEvent(UIResultBatchEvent e) {
|
||||
MVCGroup resultsGroup = results.get(e.uuid)
|
||||
resultsGroup?.model.handleResultBatch(e.results)
|
||||
resultsGroup?.model?.handleResultBatch(e.results)
|
||||
}
|
||||
|
||||
void onDownloadStartedEvent(DownloadStartedEvent e) {
|
||||
|
@@ -367,11 +367,12 @@ class MainFrameView {
|
||||
sharedFilesTable.rowSorter.setSortsOnUpdates(true)
|
||||
|
||||
JPopupMenu sharedFilesMenu = new JPopupMenu()
|
||||
// JMenuItem unshareSelectedFiles = new JMenuItem("Unshare selected files")
|
||||
// unshareSelectedFiles.addActionListener({mvcGroup.controller.unshareSelectedFiles()})
|
||||
JMenuItem copyHashToClipboard = new JMenuItem("Copy hash to clipboard")
|
||||
copyHashToClipboard.addActionListener({mvcGroup.view.copyHashToClipboard(sharedFilesTable)})
|
||||
copyHashToClipboard.addActionListener({mvcGroup.view.copyHashToClipboard()})
|
||||
sharedFilesMenu.add(copyHashToClipboard)
|
||||
JMenuItem unshareSelectedFiles = new JMenuItem("Unshare selected files")
|
||||
unshareSelectedFiles.addActionListener({mvcGroup.controller.unshareSelectedFile()})
|
||||
sharedFilesMenu.add(unshareSelectedFiles)
|
||||
sharedFilesTable.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
@@ -431,13 +432,21 @@ class MainFrameView {
|
||||
menu.show(event.getComponent(), event.getX(), event.getY())
|
||||
}
|
||||
|
||||
def copyHashToClipboard(JTable sharedFilesTable) {
|
||||
def selectedSharedFile() {
|
||||
def sharedFilesTable = builder.getVariable("shared-files-table")
|
||||
int selected = sharedFilesTable.getSelectedRow()
|
||||
if (selected < 0)
|
||||
return
|
||||
if (lastSharedSortEvent != null)
|
||||
return null
|
||||
if (lastSharedSortEvent != null)
|
||||
selected = sharedFilesTable.rowSorter.convertRowIndexToModel(selected)
|
||||
String root = Base64.encode(model.shared[selected].infoHash.getRoot())
|
||||
model.shared[selected]
|
||||
}
|
||||
|
||||
def copyHashToClipboard() {
|
||||
def selected = selectedSharedFile()
|
||||
if (selected == null)
|
||||
return
|
||||
String root = Base64.encode(selected.infoHash.getRoot())
|
||||
StringSelection selection = new StringSelection(root)
|
||||
def clipboard = Toolkit.getDefaultToolkit().getSystemClipboard()
|
||||
clipboard.setContents(selection, null)
|
||||
|
Reference in New Issue
Block a user