Compare commits
11 Commits
muwire-0.1
...
muwire-0.1
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b88334f19a | ||
![]() |
81e186ad1f | ||
![]() |
33a45c3835 | ||
![]() |
32b7867e44 | ||
![]() |
5b313276f4 | ||
![]() |
abba4cc6fa | ||
![]() |
15b4804968 | ||
![]() |
942a01a501 | ||
![]() |
502a8d91da | ||
![]() |
5414e8679b | ||
![]() |
14e42dd7c2 |
@@ -31,6 +31,5 @@ The first time you run MuWire it will ask you to select a nickname. This nickna
|
||||
### Known bugs and limitations
|
||||
|
||||
* Many UI features you would expect are not there yet
|
||||
* On windows the preferences are stored in %HOME%\.MuWire instead of the user profile directory
|
||||
* Downloads in progress do not get remembered between restarts
|
||||
|
||||
|
@@ -34,7 +34,7 @@ class Cli {
|
||||
|
||||
Core core
|
||||
try {
|
||||
core = new Core(props, home, "0.1.1")
|
||||
core = new Core(props, home, "0.1.3")
|
||||
} 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.1.1")
|
||||
core = new Core(props, home, "0.1.3")
|
||||
} catch (Exception bad) {
|
||||
bad.printStackTrace(System.out)
|
||||
println "Failed to initialize core, exiting"
|
||||
|
@@ -253,7 +253,7 @@ public class Core {
|
||||
}
|
||||
}
|
||||
|
||||
Core core = new Core(props, home, "0.1.1")
|
||||
Core core = new Core(props, home, "0.1.3")
|
||||
core.startServices()
|
||||
|
||||
// ... at the end, sleep or execute script
|
||||
|
@@ -1,6 +1,9 @@
|
||||
package com.muwire.core.files
|
||||
|
||||
import com.muwire.core.InfoHash
|
||||
|
||||
import net.i2p.data.Base64
|
||||
|
||||
import java.nio.MappedByteBuffer
|
||||
import java.nio.channels.FileChannel
|
||||
import java.nio.channels.FileChannel.MapMode
|
||||
@@ -79,6 +82,6 @@ class FileHasher {
|
||||
file = file.getAbsoluteFile()
|
||||
def hasher = new FileHasher()
|
||||
def infohash = hasher.hashFile(file)
|
||||
println infohash
|
||||
println Base64.encode(infohash.getRoot())
|
||||
}
|
||||
}
|
||||
|
@@ -42,7 +42,7 @@ class SearchIndex {
|
||||
terms.each {
|
||||
Set<String> forWord = keywords.getOrDefault(it,[])
|
||||
if (rv == null) {
|
||||
rv = forWord
|
||||
rv = new HashSet<>(forWord)
|
||||
} else {
|
||||
rv.retainAll(forWord)
|
||||
}
|
||||
|
@@ -24,9 +24,9 @@ class FileHasherTest extends GroovyTestCase {
|
||||
|
||||
@Test
|
||||
void testPieceSize() {
|
||||
assert 18 == FileHasher.getPieceSize(1000000)
|
||||
assert 20 == FileHasher.getPieceSize(100000000)
|
||||
assert 30 == FileHasher.getPieceSize(FileHasher.MAX_SIZE)
|
||||
assert 17 == FileHasher.getPieceSize(1000000)
|
||||
assert 17 == FileHasher.getPieceSize(100000000)
|
||||
assert 27 == FileHasher.getPieceSize(FileHasher.MAX_SIZE)
|
||||
shouldFail IllegalArgumentException, {
|
||||
FileHasher.getPieceSize(Long.MAX_VALUE)
|
||||
}
|
||||
@@ -48,7 +48,7 @@ class FileHasherTest extends GroovyTestCase {
|
||||
fos.write b
|
||||
fos.close()
|
||||
def ih = hasher.hashFile tmp
|
||||
assert ih.getHashList().length == 32
|
||||
assert ih.getHashList().length == 64
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -58,7 +58,7 @@ class FileHasherTest extends GroovyTestCase {
|
||||
fos.write b
|
||||
fos.close()
|
||||
def ih = hasher.hashFile tmp
|
||||
assert ih.getHashList().length == 64
|
||||
assert ih.getHashList().length == 96
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -68,7 +68,7 @@ class FileHasherTest extends GroovyTestCase {
|
||||
fos.write b
|
||||
fos.close()
|
||||
def ih = hasher.hashFile tmp
|
||||
assert ih.getHashList().length == 64
|
||||
assert ih.getHashList().length == 128
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -78,6 +78,6 @@ class FileHasherTest extends GroovyTestCase {
|
||||
fos.write b
|
||||
fos.close()
|
||||
def ih = hasher.hashFile tmp
|
||||
assert ih.getHashList().length == 32 * 3
|
||||
assert ih.getHashList().length == 160
|
||||
}
|
||||
}
|
||||
|
@@ -99,7 +99,7 @@ class PersisterServiceLoadingTest {
|
||||
FileHasher fh = new FileHasher()
|
||||
InfoHash ih1 = fh.hashFile(sharedFile1)
|
||||
|
||||
assert ih1.getHashList().length == 2 * 32
|
||||
assert ih1.getHashList().length == 96
|
||||
|
||||
def json = [:]
|
||||
json.file = getSharedFileJsonName(sharedFile1)
|
||||
@@ -111,7 +111,9 @@ class PersisterServiceLoadingTest {
|
||||
String hash1 = Base64.encode(tmp)
|
||||
System.arraycopy(ih1.getHashList(), 32, tmp, 0, 32)
|
||||
String hash2 = Base64.encode(tmp)
|
||||
json.hashList = [hash1, hash2]
|
||||
System.arraycopy(ih1.getHashList(), 64, tmp, 0, 32)
|
||||
String hash3 = Base64.encode(tmp)
|
||||
json.hashList = [hash1, hash2, hash3]
|
||||
|
||||
json = JsonOutput.toJson(json)
|
||||
|
||||
|
@@ -30,7 +30,18 @@ class SearchIndexTest {
|
||||
assert found.size() == 2
|
||||
assert found.contains("a b.c")
|
||||
assert found.contains("c d.e")
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDrillDownDoesNotModifyIndex() {
|
||||
initIndex(["a b.c", "c d.e"])
|
||||
index.search(["c","e"])
|
||||
def found = index.search(["c"])
|
||||
assert found.size() == 2
|
||||
assert found.contains("a b.c")
|
||||
assert found.contains("c d.e")
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDrillDown() {
|
||||
|
@@ -131,12 +131,18 @@ Sent by a leaf or ultrapeer when performing a search. Contains the reply-to per
|
||||
firstHop: false,
|
||||
keywords : ["keyword1","keyword2"...]
|
||||
infohash: "asdfasdf...",
|
||||
replyTo : "asdfasf...b64"
|
||||
replyTo : "asdfasf...b64",
|
||||
originator : "asfasdf...",
|
||||
"oobHashlist" : true
|
||||
}
|
||||
```
|
||||
|
||||
A search can contain either the query entered by the user in the UI or the infohash if the user is looking for a specific file. If both are present, the infohash takes precedence and the keyword query is ignored.
|
||||
|
||||
The "originator" field contains the Base64-encoded persona of the originator of the query. It is used for display purposes only. The I2P destination in that persona must match the one in the "replyTo" field.
|
||||
|
||||
The oobHashlist flag indicates support for out-of-band hashlist delivery, which is not yet implemented. Nevertheless, this flag gets propagated through the network for future-proofing.
|
||||
|
||||
### Ultrapeer to leaf
|
||||
|
||||
The "Search" message is also sent from an ultrapeer to a leaf.
|
||||
|
@@ -1,5 +1,5 @@
|
||||
group = com.muwire
|
||||
version = 0.1.0
|
||||
version = 0.1.3
|
||||
groovyVersion = 2.4.15
|
||||
slf4jVersion = 1.7.25
|
||||
spockVersion = 1.1-groovy-2.4
|
||||
|
@@ -82,12 +82,19 @@ class MainFrameController {
|
||||
int row = table.getSelectedRow()
|
||||
if (row == -1)
|
||||
return
|
||||
def sortEvt = group.view.lastSortEvent
|
||||
if (sortEvt != null) {
|
||||
row = sortEvt.convertPreviousRowIndexToModel(row)
|
||||
}
|
||||
group.model.results[row]
|
||||
}
|
||||
|
||||
private def selectedDownload() {
|
||||
private int selectedDownload() {
|
||||
def selected = builder.getVariable("downloads-table").getSelectedRow()
|
||||
model.downloads[selected].downloader
|
||||
def sortEvt = mvcGroup.view.lastDownloadSortEvent
|
||||
if (sortEvt != null)
|
||||
selected = sortEvt.convertPreviousRowIndexToModel(selected)
|
||||
selected
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
@@ -124,13 +131,13 @@ class MainFrameController {
|
||||
|
||||
@ControllerAction
|
||||
void cancel() {
|
||||
def downloader = selectedDownload()
|
||||
def downloader = model.downloads[selectedDownload()].downloader
|
||||
downloader.cancel()
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void resume() {
|
||||
def downloader = selectedDownload()
|
||||
def downloader = model.downloads[selectedDownload()].downloader
|
||||
downloader.resume()
|
||||
}
|
||||
|
||||
|
@@ -37,6 +37,9 @@ class MainFrameView {
|
||||
@MVCMember @Nonnull
|
||||
MainFrameModel model
|
||||
|
||||
def downloadsTable
|
||||
def lastDownloadSortEvent
|
||||
|
||||
void initUI() {
|
||||
builder.with {
|
||||
application(size : [1024,768], id: 'main-frame',
|
||||
@@ -105,10 +108,10 @@ class MainFrameView {
|
||||
panel (constraints : JSplitPane.BOTTOM) {
|
||||
borderLayout()
|
||||
scrollPane (constraints : BorderLayout.CENTER) {
|
||||
table(id : "downloads-table", autoCreateRowSorter : true) {
|
||||
downloadsTable = table(id : "downloads-table", autoCreateRowSorter : true) {
|
||||
tableModel(list: model.downloads) {
|
||||
closureColumn(header: "Name", preferredWidth: 350, type: String, read : {row -> row.downloader.file.getName()})
|
||||
closureColumn(header: "Status", preferredWidth: 50, type: String, read : {row -> row.downloader.getCurrentState()})
|
||||
closureColumn(header: "Status", preferredWidth: 50, type: String, read : {row -> row.downloader.getCurrentState().toString()})
|
||||
closureColumn(header: "Progress", preferredWidth: 20, type: String, read: { row ->
|
||||
int pieces = row.downloader.nPieces
|
||||
int done = row.downloader.donePieces()
|
||||
@@ -260,7 +263,7 @@ class MainFrameView {
|
||||
def selectionModel = downloadsTable.getSelectionModel()
|
||||
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
|
||||
selectionModel.addListSelectionListener({
|
||||
int selectedRow = downloadsTable.getSelectedRow()
|
||||
int selectedRow = selectedDownloaderRow()
|
||||
def downloader = model.downloads[selectedRow].downloader
|
||||
switch(downloader.getCurrentState()) {
|
||||
case Downloader.DownloadState.CONNECTING :
|
||||
@@ -280,9 +283,18 @@ class MainFrameView {
|
||||
|
||||
def centerRenderer = new DefaultTableCellRenderer()
|
||||
centerRenderer.setHorizontalAlignment(JLabel.CENTER)
|
||||
builder.getVariable("downloads-table").setDefaultRenderer(Integer.class, centerRenderer)
|
||||
downloadsTable.setDefaultRenderer(Integer.class, centerRenderer)
|
||||
|
||||
downloadsTable.rowSorter.addRowSorterListener({evt -> lastDownloadSortEvent = evt})
|
||||
}
|
||||
|
||||
int selectedDownloaderRow() {
|
||||
int selected = builder.getVariable("downloads-table").getSelectedRow()
|
||||
if (lastDownloadSortEvent != null)
|
||||
selected = lastDownloadSortEvent.convertPreviousRowIndexToModel(selected)
|
||||
selected
|
||||
}
|
||||
|
||||
def showSearchWindow = {
|
||||
def cardsPanel = builder.getVariable("cards-panel")
|
||||
cardsPanel.getLayout().show(cardsPanel, "search window")
|
||||
|
@@ -26,19 +26,20 @@ class SearchTabView {
|
||||
def parent
|
||||
def searchTerms
|
||||
def resultsTable
|
||||
def lastSortEvent
|
||||
|
||||
void initUI() {
|
||||
builder.with {
|
||||
def resultsTable
|
||||
def pane = scrollPane {
|
||||
resultsTable = table(id : "results-table") {
|
||||
tableModel(list: model.results, autoCreateRowSorter : true) {
|
||||
resultsTable = table(id : "results-table", autoCreateRowSorter : true) {
|
||||
tableModel(list: model.results) {
|
||||
closureColumn(header: "Name", preferredWidth: 350, type: String, read : {row -> row.name.replace('<','_')})
|
||||
closureColumn(header: "Size", preferredWidth: 50, type: String, read : {row -> DataHelper.formatSize2Decimal(row.size, false)+"B"})
|
||||
closureColumn(header: "Sources", preferredWidth: 10, type : Integer, read : { row -> model.hashBucket[row.infohash].size()})
|
||||
closureColumn(header: "Sender", preferredWidth: 170, type: String, read : {row -> row.sender.getHumanReadableName()})
|
||||
closureColumn(header: "Trust", preferredWidth: 50, type: String, read : {row ->
|
||||
model.core.trustService.getLevel(row.sender.destination)
|
||||
model.core.trustService.getLevel(row.sender.destination).toString()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -84,6 +85,8 @@ class SearchTabView {
|
||||
resultsTable.columnModel.getColumn(1).setCellRenderer(centerRenderer)
|
||||
resultsTable.setDefaultRenderer(Integer.class,centerRenderer)
|
||||
resultsTable.columnModel.getColumn(4).setCellRenderer(centerRenderer)
|
||||
|
||||
resultsTable.rowSorter.addRowSorterListener({ evt -> lastSortEvent = evt})
|
||||
}
|
||||
|
||||
def closeTab = {
|
||||
|
Reference in New Issue
Block a user