Compare commits

...

11 Commits

Author SHA1 Message Date
Zlatin Balevsky
b88334f19a Release 0.1.3 for sorting fixes 2019-06-08 17:57:36 +01:00
Zlatin Balevsky
81e186ad1f fix sorting by download status and trust, fix events on downloads table 2019-06-08 17:55:39 +01:00
Zlatin Balevsky
33a45c3835 fix buttons when tables are sorted 2019-06-08 17:09:44 +01:00
Zlatin Balevsky
32b7867e44 Release 0.1.2 for search index test 2019-06-08 13:09:28 +01:00
Zlatin Balevsky
5b313276f4 fix tests broken by piece size change 2019-06-08 13:08:20 +01:00
Zlatin Balevsky
abba4cc6fa fix a bug where multi-term search modifies the index 2019-06-08 12:55:47 +01:00
Zlatin Balevsky
15b4804968 update wire protocol with originator and oobHashlist fields 2019-06-08 12:40:38 +01:00
Zlatin Balevsky
942a01a501 forgot to commit 2019-06-08 09:33:16 +01:00
Zlatin Balevsky
502a8d91da print only the root 2019-06-08 09:30:01 +01:00
Zlatin Balevsky
5414e8679b update readme 2019-06-08 09:07:13 +01:00
Zlatin Balevsky
14e42dd7c2 correct element 2019-06-08 08:46:28 +01:00
14 changed files with 71 additions and 28 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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