From 366a2ef84141809d13c22493ad3546a508579474 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Sun, 8 Mar 2020 15:46:36 +0000 Subject: [PATCH 01/52] published flag and timestamp in shared files --- .../core/files/BasePersisterService.groovy | 18 +++++++++++++++++- .../main/java/com/muwire/core/SharedFile.java | 15 +++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/core/src/main/groovy/com/muwire/core/files/BasePersisterService.groovy b/core/src/main/groovy/com/muwire/core/files/BasePersisterService.groovy index 356bb419..9adca00e 100644 --- a/core/src/main/groovy/com/muwire/core/files/BasePersisterService.groovy +++ b/core/src/main/groovy/com/muwire/core/files/BasePersisterService.groovy @@ -47,7 +47,7 @@ abstract class BasePersisterService extends Service{ int pieceSize = 0 if (json.pieceSize != null) pieceSize = json.pieceSize - + if (json.sources != null) { List sources = (List)json.sources Set sourceSet = sources.stream().map({ d -> new Destination(d.toString())}).collect Collectors.toSet() @@ -94,10 +94,19 @@ abstract class BasePersisterService extends Service{ if (json.pieceSize != null) pieceSize = json.pieceSize + boolean published = false + long publishedTimestamp = -1 + if (json.published != null && json.published) { + published = true + publishedTimestamp = json.publishedTimestamp + } + if (json.sources != null) { List sources = (List)json.sources Set sourceSet = sources.stream().map({ d -> new Destination(d.toString())}).collect Collectors.toSet() DownloadedFile df = new DownloadedFile(file, ih.getRoot(), pieceSize, sourceSet) + if (published) + df.publish(publishedTimestamp) df.setComment(json.comment) return new FileLoadedEvent(loadedFile : df, infoHash: ih) } @@ -105,6 +114,8 @@ abstract class BasePersisterService extends Service{ SharedFile sf = new SharedFile(file, ih.getRoot(), pieceSize) sf.setComment(json.comment) + if (published) + sf.published(publishedTimestamp) if (json.downloaders != null) sf.getDownloaders().addAll(json.downloaders) if (json.searchers != null) { @@ -146,6 +157,11 @@ abstract class BasePersisterService extends Service{ if (sf instanceof DownloadedFile) { json.sources = sf.sources.stream().map( {d -> d.toBase64()}).collect(Collectors.toList()) } + + if (sf.isPublished()) { + json.published = true + json.publishedTimestamp = sf.getPublishedTimestamp() + } json } diff --git a/core/src/main/java/com/muwire/core/SharedFile.java b/core/src/main/java/com/muwire/core/SharedFile.java index 8021a494..221fa439 100644 --- a/core/src/main/java/com/muwire/core/SharedFile.java +++ b/core/src/main/java/com/muwire/core/SharedFile.java @@ -31,6 +31,8 @@ public class SharedFile { private volatile String comment; private final Set downloaders = Collections.synchronizedSet(new HashSet<>()); private final Set searches = Collections.synchronizedSet(new HashSet<>()); + private volatile boolean published; + private volatile long publishedTimestamp; public SharedFile(File file, byte[] root, int pieceSize) throws IOException { this.file = file; @@ -114,6 +116,19 @@ public class SharedFile { public void addDownloader(String name) { downloaders.add(name); } + + public void publish(long timestamp) { + published = true; + publishedTimestamp = timestamp; + } + + public boolean isPublished() { + return published; + } + + public long getPublishedTimestamp() { + return publishedTimestamp; + } @Override public int hashCode() { From 2b6565d107039ec30cc4dc0fddacd59a2a50535b Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Sun, 8 Mar 2020 16:01:23 +0000 Subject: [PATCH 02/52] unpublish method --- core/src/main/java/com/muwire/core/SharedFile.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/src/main/java/com/muwire/core/SharedFile.java b/core/src/main/java/com/muwire/core/SharedFile.java index 221fa439..ab257f54 100644 --- a/core/src/main/java/com/muwire/core/SharedFile.java +++ b/core/src/main/java/com/muwire/core/SharedFile.java @@ -122,6 +122,11 @@ public class SharedFile { publishedTimestamp = timestamp; } + public void unpublish() { + published = false; + publishedTimestamp = 0; + } + public boolean isPublished() { return published; } From 49cf56fabb1fd7ecfc6e477d7a74aeb350c05cb0 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Sun, 8 Mar 2020 16:01:50 +0000 Subject: [PATCH 03/52] UI Publish & Unpublish events --- .../com/muwire/core/filefeeds/UIFIlePublishedEvent.groovy | 8 ++++++++ .../muwire/core/filefeeds/UIFileUnpublishedEvent.groovy | 7 +++++++ 2 files changed, 15 insertions(+) create mode 100644 core/src/main/groovy/com/muwire/core/filefeeds/UIFIlePublishedEvent.groovy create mode 100644 core/src/main/groovy/com/muwire/core/filefeeds/UIFileUnpublishedEvent.groovy diff --git a/core/src/main/groovy/com/muwire/core/filefeeds/UIFIlePublishedEvent.groovy b/core/src/main/groovy/com/muwire/core/filefeeds/UIFIlePublishedEvent.groovy new file mode 100644 index 00000000..e2a18aed --- /dev/null +++ b/core/src/main/groovy/com/muwire/core/filefeeds/UIFIlePublishedEvent.groovy @@ -0,0 +1,8 @@ +package com.muwire.core.filefeeds + +import com.muwire.core.Event +import com.muwire.core.SharedFile + +class UIFIlePublishedEvent extends Event { + SharedFile sf +} diff --git a/core/src/main/groovy/com/muwire/core/filefeeds/UIFileUnpublishedEvent.groovy b/core/src/main/groovy/com/muwire/core/filefeeds/UIFileUnpublishedEvent.groovy new file mode 100644 index 00000000..b6fc47e2 --- /dev/null +++ b/core/src/main/groovy/com/muwire/core/filefeeds/UIFileUnpublishedEvent.groovy @@ -0,0 +1,7 @@ +package com.muwire.core.filefeeds + +import com.muwire.core.SharedFile + +class UIFileUnpublishedEvent { + SharedFile sf +} From 8e3a433afb888a80f7224a989ea5efcd9db2f01f Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Sun, 8 Mar 2020 16:06:28 +0000 Subject: [PATCH 04/52] persist shared file on publish/unpublish --- core/src/main/groovy/com/muwire/core/Core.groovy | 4 ++++ .../core/filefeeds/UIFileUnpublishedEvent.groovy | 3 ++- .../muwire/core/files/PersisterFolderService.groovy | 11 +++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/core/src/main/groovy/com/muwire/core/Core.groovy b/core/src/main/groovy/com/muwire/core/Core.groovy index e60a26cd..9409725b 100644 --- a/core/src/main/groovy/com/muwire/core/Core.groovy +++ b/core/src/main/groovy/com/muwire/core/Core.groovy @@ -32,6 +32,8 @@ import com.muwire.core.filecert.CertificateManager import com.muwire.core.filecert.UICreateCertificateEvent import com.muwire.core.filecert.UIFetchCertificatesEvent import com.muwire.core.filecert.UIImportCertificateEvent +import com.muwire.core.filefeeds.UIFIlePublishedEvent +import com.muwire.core.filefeeds.UIFileUnpublishedEvent import com.muwire.core.files.FileDownloadedEvent import com.muwire.core.files.FileHashedEvent import com.muwire.core.files.FileHasher @@ -269,6 +271,8 @@ public class Core { eventBus.register(FileHashedEvent.class, persisterFolderService) eventBus.register(FileUnsharedEvent.class, persisterFolderService) eventBus.register(UICommentEvent.class, persisterFolderService) + eventBus.register(UIFIlePublishedEvent.class, persisterFolderService) + eventBus.register(UIFileUnpublishedEvent.class, persisterFolderService) log.info("initializing host cache") File hostStorage = new File(home, "hosts.json") diff --git a/core/src/main/groovy/com/muwire/core/filefeeds/UIFileUnpublishedEvent.groovy b/core/src/main/groovy/com/muwire/core/filefeeds/UIFileUnpublishedEvent.groovy index b6fc47e2..ee6f5ce0 100644 --- a/core/src/main/groovy/com/muwire/core/filefeeds/UIFileUnpublishedEvent.groovy +++ b/core/src/main/groovy/com/muwire/core/filefeeds/UIFileUnpublishedEvent.groovy @@ -1,7 +1,8 @@ package com.muwire.core.filefeeds +import com.muwire.core.Event import com.muwire.core.SharedFile -class UIFileUnpublishedEvent { +class UIFileUnpublishedEvent extends Event { SharedFile sf } diff --git a/core/src/main/groovy/com/muwire/core/files/PersisterFolderService.groovy b/core/src/main/groovy/com/muwire/core/files/PersisterFolderService.groovy index b2899f05..0592f9f9 100644 --- a/core/src/main/groovy/com/muwire/core/files/PersisterFolderService.groovy +++ b/core/src/main/groovy/com/muwire/core/files/PersisterFolderService.groovy @@ -1,6 +1,9 @@ package com.muwire.core.files import com.muwire.core.* +import com.muwire.core.filefeeds.UIFIlePublishedEvent +import com.muwire.core.filefeeds.UIFileUnpublishedEvent + import groovy.json.JsonOutput import groovy.json.JsonSlurper import groovy.util.logging.Log @@ -92,6 +95,14 @@ class PersisterFolderService extends BasePersisterService { void onUICommentEvent(UICommentEvent e) { persistFile(e.sharedFile,null) } + + void onUIFilePublishedEvent(UIFIlePublishedEvent e) { + persistFile(e.sf, null) + } + + void onUIFileUnpublishedEvent(UIFileUnpublishedEvent e) { + persistFile(e.sf, null) + } void load() { log.fine("Loading...") From d60d57ee435a3f6efbe6d7e06c096dd641c4137a Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Sun, 8 Mar 2020 17:04:11 +0000 Subject: [PATCH 05/52] wip on server side feed handling --- .../core/connection/ConnectionAcceptor.groovy | 51 +++++++++++++++++++ .../muwire/core/filefeeds/FeedItems.groovy | 28 ++++++++++ .../com/muwire/core/files/FileManager.groovy | 12 +++++ 3 files changed, 91 insertions(+) create mode 100644 core/src/main/groovy/com/muwire/core/filefeeds/FeedItems.groovy diff --git a/core/src/main/groovy/com/muwire/core/connection/ConnectionAcceptor.groovy b/core/src/main/groovy/com/muwire/core/connection/ConnectionAcceptor.groovy index b1fcbaf3..16c67c1c 100644 --- a/core/src/main/groovy/com/muwire/core/connection/ConnectionAcceptor.groovy +++ b/core/src/main/groovy/com/muwire/core/connection/ConnectionAcceptor.groovy @@ -15,9 +15,11 @@ import com.muwire.core.EventBus import com.muwire.core.InfoHash import com.muwire.core.MuWireSettings import com.muwire.core.Persona +import com.muwire.core.SharedFile import com.muwire.core.chat.ChatServer import com.muwire.core.filecert.Certificate import com.muwire.core.filecert.CertificateManager +import com.muwire.core.filefeeds.FeedItems import com.muwire.core.files.FileManager import com.muwire.core.hostcache.HostCache import com.muwire.core.trust.TrustLevel @@ -161,6 +163,9 @@ class ConnectionAcceptor { case (byte)'I': processIRC(e) break + case (byte)'F': + processFEED(e) + break default: throw new Exception("Invalid read $read") } @@ -524,5 +529,51 @@ class ConnectionAcceptor { throw new Exception("Invalid IRC connection") chatServer.handle(e) } + + private void processFEED(Endpoint e) { + try { + byte[] EED = new byte[5]; + DataInputStream dis = new DataInputStream(e.getInputStream()) + dis.readFully(EED); + if (EED != "EED\r\n".getBytes(StandardCharsets.US_ASCII)) + throw new Exception("Invalid FEED connection") + + + Map headers = DataUtil.readAllHeaders(dis) + if (!headers.containsKey("Persona")) + throw new Exception("Persona header missing") + Persona requestor = new Persona(new ByteArrayInputStream(Base64.decode(headers['Persona']))) + if (requestor.destination != e.destination) + throw new Exception("Requestor persona mismatch") + + // TODO: check settings if feed is permitted at all + + long timestamp = 0 + if (headers.containsKey("Timestamp")) { + timestamp = Long.parseLong(headers['Timestamp']) + } + + List published = fileManager.getPublishedSince(timestamp) + + OutputStream os = e.getOutputStream() + os.write("200 OK\r\n".getBytes(StandardCharsets.US_ASCII)) + os.write("Count: ${published.size()}\r\n".getBytes(StandardCharsets.US_ASCII)); + os.write("\r\n".getBytes(StandardCharsets.US_ASCII)) + + DataOutputStream dos = new DataOutputStream(new GZIPOutputStream(os)) + JsonOutput jsonOutput = new JsonOutput() + published.each { + int certificates = certificateManager.getByInfoHash(new InfoHash(it.getRoot())).size() + def obj = FeedItems.sharedFileToObj(it, certificates) + def json = jsonOutput.toJson(obj) + dos.writeShort((short)json.length()) + dos.write(json.getBytes(StandardCharsets.US_ASCII)) + } + dos.flush() + dos.close() + } finally { + e.close() + } + } } diff --git a/core/src/main/groovy/com/muwire/core/filefeeds/FeedItems.groovy b/core/src/main/groovy/com/muwire/core/filefeeds/FeedItems.groovy new file mode 100644 index 00000000..c2519aa6 --- /dev/null +++ b/core/src/main/groovy/com/muwire/core/filefeeds/FeedItems.groovy @@ -0,0 +1,28 @@ +package com.muwire.core.filefeeds + +import com.muwire.core.SharedFile +import com.muwire.core.util.DataUtil + +import net.i2p.data.Base64 + +class FeedItems { + + public static def sharedFileToObj(SharedFile sf, int certificates) { + def json = [:] + json.type = "FeedItem" + json.version = 1 + json.name = Base64.encode(DataUtil.encodei18nString(sf.getFile().getName())) + json.infoHash = Base64.encode(sf.getRoot()) + json.size = sf.getCachedLength() + json.pieceSize = sf.getPieceSize() + + if (sf.getComment() != null) + json.comment = sf.getComment() + + json.certificates = certificates + + json.timestamp = sf.getPublishedTimestamp() + + json + } +} diff --git a/core/src/main/groovy/com/muwire/core/files/FileManager.groovy b/core/src/main/groovy/com/muwire/core/files/FileManager.groovy index 16864be7..a130d336 100644 --- a/core/src/main/groovy/com/muwire/core/files/FileManager.groovy +++ b/core/src/main/groovy/com/muwire/core/files/FileManager.groovy @@ -1,5 +1,8 @@ package com.muwire.core.files +import java.util.stream.Collectors +import java.util.stream.Stream + import com.muwire.core.EventBus import com.muwire.core.InfoHash import com.muwire.core.MuWireSettings @@ -254,4 +257,13 @@ class FileManager { settings.negativeFileTree.clear() settings.negativeFileTree.addAll(negativeTree.fileToNode.keySet().collect { it.getAbsolutePath() }) } + + public List getPublishedSince(long timestamp) { + synchronized(fileToSharedFile) { + fileToSharedFile.values().stream(). + filter({sf -> sf.isPublished()}). + filter({sf -> sf.getPublishedTimestamp() >= timestamp}). + collect(Collectors.toList()) + } + } } From 9f3942c1c7339b7433aff12a49a617f6c4ebb6ce Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Sun, 8 Mar 2020 17:15:00 +0000 Subject: [PATCH 06/52] settings to disable or not advertise file feed --- .../groovy/com/muwire/core/MuWireSettings.groovy | 6 ++++++ .../muwire/core/connection/ConnectionAcceptor.groovy | 12 ++++++++++-- .../com/muwire/core/search/ResultsSender.groovy | 2 ++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy b/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy index b820ce1e..073d79a5 100644 --- a/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy +++ b/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy @@ -31,6 +31,8 @@ class MuWireSettings { boolean shareHiddenFiles boolean searchComments boolean browseFiles + boolean fileFeed + boolean advertiseFeed boolean startChatServer int maxChatConnections boolean advertiseChat @@ -82,6 +84,8 @@ class MuWireSettings { outBw = Integer.valueOf(props.getProperty("outBw","128")) searchComments = Boolean.valueOf(props.getProperty("searchComments","true")) browseFiles = Boolean.valueOf(props.getProperty("browseFiles","true")) + fileFeed = Boolean.valueOf(props.getProperty("fileFeed","true")) + advertiseFeed = Boolean.valueOf(props.getProperty("advertiseFeed","true")) speedSmoothSeconds = Integer.valueOf(props.getProperty("speedSmoothSeconds","60")) totalUploadSlots = Integer.valueOf(props.getProperty("totalUploadSlots","-1")) uploadSlotsPerUser = Integer.valueOf(props.getProperty("uploadSlotsPerUser","-1")) @@ -137,6 +141,8 @@ class MuWireSettings { props.setProperty("outBw", String.valueOf(outBw)) props.setProperty("searchComments", String.valueOf(searchComments)) props.setProperty("browseFiles", String.valueOf(browseFiles)) + props.setProperty("fileFeed", String.valueOf(fileFeed)) + props.setProperty("advertiseFeed", String.valueOf(advertiseFeed)) props.setProperty("speedSmoothSeconds", String.valueOf(speedSmoothSeconds)) props.setProperty("totalUploadSlots", String.valueOf(totalUploadSlots)) props.setProperty("uploadSlotsPerUser", String.valueOf(uploadSlotsPerUser)) diff --git a/core/src/main/groovy/com/muwire/core/connection/ConnectionAcceptor.groovy b/core/src/main/groovy/com/muwire/core/connection/ConnectionAcceptor.groovy index 16c67c1c..25266649 100644 --- a/core/src/main/groovy/com/muwire/core/connection/ConnectionAcceptor.groovy +++ b/core/src/main/groovy/com/muwire/core/connection/ConnectionAcceptor.groovy @@ -379,6 +379,9 @@ class ConnectionAcceptor { boolean chat = chatServer.running.get() && settings.advertiseChat os.write("Chat: ${chat}\r\n".getBytes(StandardCharsets.US_ASCII)) + boolean feed = settings.fileFeed && settings.advertiseFeed + os.write("Feed: ${feed}\r\n".getBytes(StandardCharsets.US_ASCII)) + os.write("\r\n".getBytes(StandardCharsets.US_ASCII)) DataOutputStream dos = new DataOutputStream(new GZIPOutputStream(os)) @@ -538,6 +541,7 @@ class ConnectionAcceptor { if (EED != "EED\r\n".getBytes(StandardCharsets.US_ASCII)) throw new Exception("Invalid FEED connection") + OutputStream os = e.getOutputStream() Map headers = DataUtil.readAllHeaders(dis) if (!headers.containsKey("Persona")) @@ -546,7 +550,12 @@ class ConnectionAcceptor { if (requestor.destination != e.destination) throw new Exception("Requestor persona mismatch") - // TODO: check settings if feed is permitted at all + if (!settings.fileFeed) { + os.write("403 Not Allowed\r\n\r\n".getBytes(StandardCharsets.US_ASCII)) + os.flush() + e.close() + return + } long timestamp = 0 if (headers.containsKey("Timestamp")) { @@ -555,7 +564,6 @@ class ConnectionAcceptor { List published = fileManager.getPublishedSince(timestamp) - OutputStream os = e.getOutputStream() os.write("200 OK\r\n".getBytes(StandardCharsets.US_ASCII)) os.write("Count: ${published.size()}\r\n".getBytes(StandardCharsets.US_ASCII)); os.write("\r\n".getBytes(StandardCharsets.US_ASCII)) diff --git a/core/src/main/groovy/com/muwire/core/search/ResultsSender.groovy b/core/src/main/groovy/com/muwire/core/search/ResultsSender.groovy index b79df77a..c61ebc5a 100644 --- a/core/src/main/groovy/com/muwire/core/search/ResultsSender.groovy +++ b/core/src/main/groovy/com/muwire/core/search/ResultsSender.groovy @@ -138,6 +138,8 @@ class ResultsSender { os.write("Count: $results.length\r\n".getBytes(StandardCharsets.US_ASCII)) boolean chat = chatServer.running.get() && settings.advertiseChat os.write("Chat: $chat\r\n".getBytes(StandardCharsets.US_ASCII)) + boolean feed = settings.fileFeed && settings.advertiseFeed + os.write("Feed: $feed\r\n".getBytes(StandardCharsets.US_ASCII)) os.write("\r\n".getBytes(StandardCharsets.US_ASCII)) DataOutputStream dos = new DataOutputStream(new GZIPOutputStream(os)) results.each { From 8e274f940e7fd5707e2ea33c7244cf83cfa08763 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Sun, 8 Mar 2020 19:30:04 +0000 Subject: [PATCH 07/52] Feed item representation and serialization --- .../muwire/core/filefeeds/FeedItems.groovy | 51 ++++++++++++ .../com/muwire/core/filefeeds/FeedItem.java | 79 +++++++++++++++++++ .../filefeeds/InvalidFeedItemException.java | 30 +++++++ 3 files changed, 160 insertions(+) create mode 100644 core/src/main/java/com/muwire/core/filefeeds/FeedItem.java create mode 100644 core/src/main/java/com/muwire/core/filefeeds/InvalidFeedItemException.java diff --git a/core/src/main/groovy/com/muwire/core/filefeeds/FeedItems.groovy b/core/src/main/groovy/com/muwire/core/filefeeds/FeedItems.groovy index c2519aa6..41b16959 100644 --- a/core/src/main/groovy/com/muwire/core/filefeeds/FeedItems.groovy +++ b/core/src/main/groovy/com/muwire/core/filefeeds/FeedItems.groovy @@ -1,6 +1,9 @@ package com.muwire.core.filefeeds +import com.muwire.core.InfoHash +import com.muwire.core.Persona import com.muwire.core.SharedFile +import com.muwire.core.files.FileHasher import com.muwire.core.util.DataUtil import net.i2p.data.Base64 @@ -25,4 +28,52 @@ class FeedItems { json } + + public static FeedItem objToFeedItem(def obj, Persona publisher) throws InvalidFeedItemException { + if (obj.timestamp == null) + throw new InvalidFeedItemException("No timestamp"); + if (obj.name == null) + throw new InvalidFeedItemException("No name"); + if (obj.size == null || obj.size <= 0 || obj.size > FileHasher.MAX_SIZE) + throw new InvalidFeedItemException("length missing or invalid ${obj.size}") + if (obj.pieceSize == null || obj.pieceSize < FileHasher.MIN_PIECE_SIZE_POW2 || obj.pieceSize > FileHasher.MAX_PIECE_SIZE_POW2) + throw new InvalidFeedItemException("piece size missing or invalid ${obj.pieceSize}") + if (obj.infoHash == null) + throw new InvalidFeedItemException("Infohash missing") + + + InfoHash infoHash + try { + infoHash = new InfoHash(Base64.decode(obj.infoHash)) + } catch (Exception bad) { + throw new InvalidFeedItemException("Invalid infohash", bad) + } + + String name + try { + name = DataUtil.readi18nString(Base64.decode(obj.name)) + } catch (Exception bad) { + throw new InvalidFeedItemException("Invalid name", bad) + } + + int certificates = 0 + if (obj.certificates != null) + certificates = obj.certificates + + new FeedItem(publisher, obj.timestamp, name, obj.size, obj.pieceSize, infoHash, certificates, obj.comment) + } + + public static def feedItemToObj(FeedItem item) { + def json = [:] + json.type = "FeedItem" + json.version = 1 + json.name = Base64.encode(DataUtil.encodei18nString(item.getName())) + json.infoHash = Base64.encode(item.getInfoHash().getRoot()) + json.size = item.getSize() + json.pieceSize = item.getPieceSize() + json.timestamp = item.getTimestamp() + json.certificates = item.getCertificates() + json.comment = item.getComment() + json + } } diff --git a/core/src/main/java/com/muwire/core/filefeeds/FeedItem.java b/core/src/main/java/com/muwire/core/filefeeds/FeedItem.java new file mode 100644 index 00000000..a1d5da5b --- /dev/null +++ b/core/src/main/java/com/muwire/core/filefeeds/FeedItem.java @@ -0,0 +1,79 @@ +package com.muwire.core.filefeeds; + +import java.util.Objects; + +import com.muwire.core.InfoHash; +import com.muwire.core.Persona; + +public class FeedItem { + + private final Persona publisher; + private final long timestamp; + private final String name; + private final long size; + private final int pieceSize; + private final InfoHash infoHash; + private final int certificates; + private final String comment; + + public FeedItem(Persona publisher, long timestamp, String name, long size, int pieceSize, InfoHash infoHash, + int certificates, String comment) { + super(); + this.publisher = publisher; + this.timestamp = timestamp; + this.name = name; + this.size = size; + this.pieceSize = pieceSize; + this.infoHash = infoHash; + this.certificates = certificates; + this.comment = comment; + } + + public Persona getPublisher() { + return publisher; + } + + public long getTimestamp() { + return timestamp; + } + + public String getName() { + return name; + } + + public long getSize() { + return size; + } + + public int getPieceSize() { + return pieceSize; + } + + public InfoHash getInfoHash() { + return infoHash; + } + + public int getCertificates() { + return certificates; + } + + public String getComment() { + return comment; + } + + @Override + public int hashCode() { + return Objects.hash(publisher, timestamp, name, infoHash); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof FeedItem)) + return false; + FeedItem other = (FeedItem)o; + return Objects.equals(publisher, other.publisher) && + timestamp == other.timestamp && + Objects.equals(name, other.name) && + Objects.equals(infoHash, other.infoHash); + } +} diff --git a/core/src/main/java/com/muwire/core/filefeeds/InvalidFeedItemException.java b/core/src/main/java/com/muwire/core/filefeeds/InvalidFeedItemException.java new file mode 100644 index 00000000..4c4e430d --- /dev/null +++ b/core/src/main/java/com/muwire/core/filefeeds/InvalidFeedItemException.java @@ -0,0 +1,30 @@ +package com.muwire.core.filefeeds; + +public class InvalidFeedItemException extends Exception { + + public InvalidFeedItemException() { + super(); + } + + public InvalidFeedItemException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + // TODO Auto-generated constructor stub + } + + public InvalidFeedItemException(String message, Throwable cause) { + super(message, cause); + // TODO Auto-generated constructor stub + } + + public InvalidFeedItemException(String message) { + super(message); + // TODO Auto-generated constructor stub + } + + public InvalidFeedItemException(Throwable cause) { + super(cause); + // TODO Auto-generated constructor stub + } + +} From bfe198e1a6b18efe591010ee938e5a6b8b60c5ff Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Sun, 8 Mar 2020 19:38:48 +0000 Subject: [PATCH 08/52] represenation of a feed --- .../filefeeds/UIFeedConfigurationEvent.groovy | 11 ++++ .../java/com/muwire/core/filefeeds/Feed.java | 54 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 core/src/main/groovy/com/muwire/core/filefeeds/UIFeedConfigurationEvent.groovy create mode 100644 core/src/main/java/com/muwire/core/filefeeds/Feed.java diff --git a/core/src/main/groovy/com/muwire/core/filefeeds/UIFeedConfigurationEvent.groovy b/core/src/main/groovy/com/muwire/core/filefeeds/UIFeedConfigurationEvent.groovy new file mode 100644 index 00000000..9917c30c --- /dev/null +++ b/core/src/main/groovy/com/muwire/core/filefeeds/UIFeedConfigurationEvent.groovy @@ -0,0 +1,11 @@ +package com.muwire.core.filefeeds + +import com.muwire.core.Event + +/** + * Emitted when configuration of a feed changes. + * The object should already contain the updated values. + */ +class UIFeedConfigurationEvent extends Event { + Feed feed +} diff --git a/core/src/main/java/com/muwire/core/filefeeds/Feed.java b/core/src/main/java/com/muwire/core/filefeeds/Feed.java new file mode 100644 index 00000000..610dbb5a --- /dev/null +++ b/core/src/main/java/com/muwire/core/filefeeds/Feed.java @@ -0,0 +1,54 @@ +package com.muwire.core.filefeeds; + +import com.muwire.core.Persona; + +public class Feed { + + private final Persona publisher; + + private int updateInterval; + private long lastUpdated; + private int itemsToKeep; + private boolean autoDownload; + + public Feed(Persona publisher) { + this.publisher = publisher; + } + + public int getUpdateInterval() { + return updateInterval; + } + + public void setUpdateInterval(int updateInterval) { + this.updateInterval = updateInterval; + } + + public long getLastUpdated() { + return lastUpdated; + } + + public void setLastUpdated(long lastUpdated) { + this.lastUpdated = lastUpdated; + } + + public int getItemsToKeep() { + return itemsToKeep; + } + + public void setItemsToKeep(int itemsToKeep) { + this.itemsToKeep = itemsToKeep; + } + + public boolean isAutoDownload() { + return autoDownload; + } + + public void setAutoDownload(boolean autoDownload) { + this.autoDownload = autoDownload; + } + + public Persona getPublisher() { + return publisher; + } + +} From 57c75978b69d28154010497ba5420f190856b66c Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Sun, 8 Mar 2020 20:19:37 +0000 Subject: [PATCH 09/52] wip on feed manager deserialization --- .../core/filefeeds/FeedItemLoadedEvent.groovy | 7 ++ .../core/filefeeds/FeedLoadedEvent.groovy | 7 ++ .../muwire/core/filefeeds/FeedManager.groovy | 93 +++++++++++++++++++ 3 files changed, 107 insertions(+) create mode 100644 core/src/main/groovy/com/muwire/core/filefeeds/FeedItemLoadedEvent.groovy create mode 100644 core/src/main/groovy/com/muwire/core/filefeeds/FeedLoadedEvent.groovy create mode 100644 core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy diff --git a/core/src/main/groovy/com/muwire/core/filefeeds/FeedItemLoadedEvent.groovy b/core/src/main/groovy/com/muwire/core/filefeeds/FeedItemLoadedEvent.groovy new file mode 100644 index 00000000..146f3708 --- /dev/null +++ b/core/src/main/groovy/com/muwire/core/filefeeds/FeedItemLoadedEvent.groovy @@ -0,0 +1,7 @@ +package com.muwire.core.filefeeds + +import com.muwire.core.Event + +class FeedItemLoadedEvent extends Event { + FeedItem item +} diff --git a/core/src/main/groovy/com/muwire/core/filefeeds/FeedLoadedEvent.groovy b/core/src/main/groovy/com/muwire/core/filefeeds/FeedLoadedEvent.groovy new file mode 100644 index 00000000..7e92554e --- /dev/null +++ b/core/src/main/groovy/com/muwire/core/filefeeds/FeedLoadedEvent.groovy @@ -0,0 +1,7 @@ +package com.muwire.core.filefeeds + +import com.muwire.core.Event + +class FeedLoadedEvent extends Event { + Feed feed +} diff --git a/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy b/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy new file mode 100644 index 00000000..3214b163 --- /dev/null +++ b/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy @@ -0,0 +1,93 @@ +package com.muwire.core.filefeeds + +import java.nio.file.Files +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors +import java.util.concurrent.ThreadFactory + +import com.muwire.core.EventBus +import com.muwire.core.Persona + +import groovy.json.JsonSlurper +import groovy.util.logging.Log + +import net.i2p.data.Base64 + +@Log +class FeedManager { + + private final EventBus eventBus + private final File metadataFolder, itemsFolder + private final Map feeds = new ConcurrentHashMap<>() + private final Map> feedItems = new ConcurrentHashMap<>() + + private final ExecutorService persister = Executors.newSingleThreadExecutor({r -> + new Thread(r, "feed persister") + } as ThreadFactory) + + + FeedManager(EventBus eventBus, File home) { + this.eventBus = eventBus + File feedsFolder = new File(home, "filefeeds") + if (!feedsFolder.exists()) + feedsFolder.mkdir() + this.metadataFolder = new File(feedsFolder, "metadata") + if (!metadataFolder.exists()) + metadataFolder.mkdir() + this.itemsFolder = new File(feedsFolder, "items") + if (!itemsFolder.exists()) + itemsFolder.mkdir() + } + + void start() { + log.info("starting feed manager") + persister.submit({loadFeeds()} as Runnable) + persister.submit({loadItems()} as Runnable) + } + + void stop() { + persister.shutdown() + } + + private void loadFeeds() { + def slurper = new JsonSlurper() + Files.walk(metadataFolder.toPath()). + filter( { it.getFileName().toString().endsWith(".json")}). + forEach( { + def parsed = slurper.parse(it.toFile()) + Persona publisher = new Persona(new ByteArrayInputStream(Base64.decode(parsed.publisher))) + Feed feed = new Feed(publisher) + feed.setUpdateInterval(parsed.updateInterval) + feed.setLastUpdated(parsed.lastUpdated) + feed.setItemsToKeep(parsed.itemsToKeep) + feed.setAutoDownload(parsed.autoDownload) + + feeds.put(feed.getPublisher(), feed) + + eventBus.publish(new FeedLoadedEvent(feed : feed)) + }) + } + + private void loadItems() { + def slurper = new JsonSlurper() + feeds.keySet().each { + File itemsFile = new File(itemsFolder, it.destination.toBase32() + ".json") + if (!itemsFile.exists()) + return // no items yet? + itemsFile.eachLine { line -> + def parsed = slurper.parse(line) + FeedItem item = FeedItems.objToFeedItem(parsed, it) + + Set items = feedItems.get(it) + if (items == null) { + items = new HashSet<>() + feedItems.put(it, items) + } + items.add(item) + + eventBus.publish(new FeedItemLoadedEvent(item : item)) + } + } + } +} From 12e56b1c9a9e215a663982ff8a649d9f5a962c0b Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Mon, 9 Mar 2020 17:37:17 +0000 Subject: [PATCH 10/52] events associated with updating a feed --- .../com/muwire/core/filefeeds/FeedFetchEvent.groovy | 10 ++++++++++ .../com/muwire/core/filefeeds/FeedFetchStatus.java | 5 +++++ .../muwire/core/filefeeds/FeedItemFetchedEvent.groovy | 7 +++++++ 3 files changed, 22 insertions(+) create mode 100644 core/src/main/groovy/com/muwire/core/filefeeds/FeedFetchEvent.groovy create mode 100644 core/src/main/groovy/com/muwire/core/filefeeds/FeedFetchStatus.java create mode 100644 core/src/main/groovy/com/muwire/core/filefeeds/FeedItemFetchedEvent.groovy diff --git a/core/src/main/groovy/com/muwire/core/filefeeds/FeedFetchEvent.groovy b/core/src/main/groovy/com/muwire/core/filefeeds/FeedFetchEvent.groovy new file mode 100644 index 00000000..202e8151 --- /dev/null +++ b/core/src/main/groovy/com/muwire/core/filefeeds/FeedFetchEvent.groovy @@ -0,0 +1,10 @@ +package com.muwire.core.filefeeds + +import com.muwire.core.Event +import com.muwire.core.Persona + +class FeedFetchEvent extends Event { + Persona host + FeedFetchStatus status + int totalItems +} diff --git a/core/src/main/groovy/com/muwire/core/filefeeds/FeedFetchStatus.java b/core/src/main/groovy/com/muwire/core/filefeeds/FeedFetchStatus.java new file mode 100644 index 00000000..7545252e --- /dev/null +++ b/core/src/main/groovy/com/muwire/core/filefeeds/FeedFetchStatus.java @@ -0,0 +1,5 @@ +package com.muwire.core.filefeeds; + +public enum FeedFetchStatus { + CONNECTING, FETCHING, FINISHED, FAILED +} diff --git a/core/src/main/groovy/com/muwire/core/filefeeds/FeedItemFetchedEvent.groovy b/core/src/main/groovy/com/muwire/core/filefeeds/FeedItemFetchedEvent.groovy new file mode 100644 index 00000000..75885f5a --- /dev/null +++ b/core/src/main/groovy/com/muwire/core/filefeeds/FeedItemFetchedEvent.groovy @@ -0,0 +1,7 @@ +package com.muwire.core.filefeeds + +import com.muwire.core.Event + +class FeedItemFetchedEvent extends Event { + FeedItem item +} From 032338bb483f124fc83cd672538725c3df45e421 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Mon, 9 Mar 2020 18:31:10 +0000 Subject: [PATCH 11/52] Persist feed metadata and items on successful fetch. Register feed manager for various events --- .../main/groovy/com/muwire/core/Core.groovy | 16 +++++ .../muwire/core/filefeeds/FeedManager.groovy | 66 ++++++++++++++++++- 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/core/src/main/groovy/com/muwire/core/Core.groovy b/core/src/main/groovy/com/muwire/core/Core.groovy index 9409725b..f3b047c1 100644 --- a/core/src/main/groovy/com/muwire/core/Core.groovy +++ b/core/src/main/groovy/com/muwire/core/Core.groovy @@ -32,7 +32,11 @@ import com.muwire.core.filecert.CertificateManager import com.muwire.core.filecert.UICreateCertificateEvent import com.muwire.core.filecert.UIFetchCertificatesEvent import com.muwire.core.filecert.UIImportCertificateEvent +import com.muwire.core.filefeeds.FeedFetchEvent +import com.muwire.core.filefeeds.FeedItemFetchedEvent +import com.muwire.core.filefeeds.FeedManager import com.muwire.core.filefeeds.UIFIlePublishedEvent +import com.muwire.core.filefeeds.UIFeedConfigurationEvent import com.muwire.core.filefeeds.UIFileUnpublishedEvent import com.muwire.core.files.FileDownloadedEvent import com.muwire.core.files.FileHashedEvent @@ -118,6 +122,7 @@ public class Core { final CertificateManager certificateManager final ChatServer chatServer final ChatManager chatManager + final FeedManager feedManager private final Router router @@ -315,6 +320,14 @@ public class Core { register(TrustEvent.class, chatServer) } + log.info("initializing feed manager") + feedManager = new FeedManager(eventBus, home) + eventBus.with { + register(FeedItemFetchedEvent.class, feedManager) + register(FeedFetchEvent.class, feedManager) + register(UIFeedConfigurationEvent.class, feedManager) + } + log.info "initializing results sender" ResultsSender resultsSender = new ResultsSender(eventBus, i2pConnector, me, props, certificateManager, chatServer) @@ -395,6 +408,7 @@ public class Core { connectionEstablisher.start() hostCache.waitForLoad() updateClient?.start() + feedManager.start() } public void shutdown() { @@ -428,6 +442,8 @@ public class Core { chatServer.stop() log.info("shutting down chat manager") chatManager.shutdown() + log.info("shutting down feed manager") + feedManager.stop() log.info("shutting down connection manager") connectionManager.shutdown() log.info("killing i2p session") diff --git a/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy b/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy index 3214b163..72f3eab1 100644 --- a/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy +++ b/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy @@ -9,10 +9,12 @@ import java.util.concurrent.ThreadFactory import com.muwire.core.EventBus import com.muwire.core.Persona +import groovy.json.JsonOutput import groovy.json.JsonSlurper import groovy.util.logging.Log import net.i2p.data.Base64 +import net.i2p.util.ConcurrentHashSet @Log class FeedManager { @@ -40,6 +42,10 @@ class FeedManager { itemsFolder.mkdir() } + public Feed getFeed(Persona persona) { + feeds.get(persona) + } + void start() { log.info("starting feed manager") persister.submit({loadFeeds()} as Runnable) @@ -81,7 +87,7 @@ class FeedManager { Set items = feedItems.get(it) if (items == null) { - items = new HashSet<>() + items = new ConcurrentHashSet<>() feedItems.put(it, items) } items.add(item) @@ -90,4 +96,62 @@ class FeedManager { } } } + + void onFeedItemFetchedEvent(FeedItemFetchedEvent e) { + Set set = feedItems.get(e.item.getPublisher()) + if (set == null) { + set = new ConcurrentHashSet<>() + feedItems.put(e.getItem().getPublisher(), set) + } + set.add(e.item) + } + + void onFeedFetchEvent(FeedFetchEvent e) { + if (e.status != FeedFetchStatus.FINISHED) + return + Feed feed = feeds.get(e.host) + if (feed == null) { + log.severe("Finished fetching non-existent feed " + e.host.getHumanReadableName()) + return + } + feed.setLastUpdated(e.getTimestamp()) + // save feed items, then save feed + persister.submit({saveFeedItems(e.host)} as Runnable) + persister.submit({saveFeedMetadata(feed)} as Runnable) + } + + void onUIFeedConfigurationEvent(UIFeedConfigurationEvent e) { + feeds.put(e.feed.getPublisher(), e.feed) + persister.submit({saveFeedMetadata(e.feed)} as Runnable) + } + + private void saveFeedItems(Persona publisher) { + Set set = feedItems.get(publisher) + if (set == null) + return // can happen if nothing was published + + File itemsFile = new File(itemsFolder, publisher.destination.toBase32() + ".json") + + itemsFile.withPrintWriter { writer -> + set.each { item -> + def obj = FeedItems.feedItemToObj(item) + def json = JsonOutput.toJson(obj) + writer.println(json) + } + } + } + + private void saveFeedMetadata(Feed feed) { + File metadataFile = new File(metadataFolder, feed.publisher.destination.toBase32() + ".json") + metadataFile.withPrintWriter { writer -> + def json = [:] + json.publisher = feed.getPublisher().toBase64() + json.itemsToKeep = feed.getItemsToKeep() + json.lastUpdated = feed.getLastUpdated() + json.updateInterval = feed.getUpdateInterval() + json.autoDownload = feed.isAutoDownload() + json = JsonOutput.toJson(json) + writer.println(json) + } + } } From 28425e93dc9fbee3869f9b0e5c5458117216e353 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Mon, 9 Mar 2020 18:53:43 +0000 Subject: [PATCH 12/52] persist only as many items as configured to keep --- .../muwire/core/filefeeds/FeedManager.groovy | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy b/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy index 72f3eab1..22cb0f4d 100644 --- a/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy +++ b/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy @@ -129,11 +129,26 @@ class FeedManager { Set set = feedItems.get(publisher) if (set == null) return // can happen if nothing was published - - File itemsFile = new File(itemsFolder, publisher.destination.toBase32() + ".json") + Feed feed = feeds[publisher] + if (feed == null) { + log.severe("Persisting items for non-existing feed " + publisher.getHumanReadableName()) + return + } + + List list = new ArrayList<>(set) + if (list.size() > feed.getItemsToKeep()) { + log.info("will persist ${feed.getItemsToKeep()}/${list.size()} items") + list.sort({l, r -> + Long.compare(r.getTimestamp(), l.getTimestamp()) + } as Comparator) + list = list[0..feed.getItemsToKeep()] + } + + + File itemsFile = new File(itemsFolder, publisher.destination.toBase32() + ".json") itemsFile.withPrintWriter { writer -> - set.each { item -> + list.each { item -> def obj = FeedItems.feedItemToObj(item) def json = JsonOutput.toJson(obj) writer.println(json) From c10c1118e87b889a36a32c3b8859b246584caa2b Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Mon, 9 Mar 2020 19:28:42 +0000 Subject: [PATCH 13/52] feed client --- .../main/groovy/com/muwire/core/Core.groovy | 10 ++ .../muwire/core/filefeeds/FeedClient.groovy | 109 ++++++++++++++++++ .../muwire/core/filefeeds/FeedManager.groovy | 8 ++ .../core/filefeeds/UIFeedUpdateEvent.groovy | 8 ++ 4 files changed, 135 insertions(+) create mode 100644 core/src/main/groovy/com/muwire/core/filefeeds/FeedClient.groovy create mode 100644 core/src/main/groovy/com/muwire/core/filefeeds/UIFeedUpdateEvent.groovy diff --git a/core/src/main/groovy/com/muwire/core/Core.groovy b/core/src/main/groovy/com/muwire/core/Core.groovy index f3b047c1..c954f79d 100644 --- a/core/src/main/groovy/com/muwire/core/Core.groovy +++ b/core/src/main/groovy/com/muwire/core/Core.groovy @@ -32,11 +32,13 @@ import com.muwire.core.filecert.CertificateManager import com.muwire.core.filecert.UICreateCertificateEvent import com.muwire.core.filecert.UIFetchCertificatesEvent import com.muwire.core.filecert.UIImportCertificateEvent +import com.muwire.core.filefeeds.FeedClient import com.muwire.core.filefeeds.FeedFetchEvent import com.muwire.core.filefeeds.FeedItemFetchedEvent import com.muwire.core.filefeeds.FeedManager import com.muwire.core.filefeeds.UIFIlePublishedEvent import com.muwire.core.filefeeds.UIFeedConfigurationEvent +import com.muwire.core.filefeeds.UIFeedUpdateEvent import com.muwire.core.filefeeds.UIFileUnpublishedEvent import com.muwire.core.files.FileDownloadedEvent import com.muwire.core.files.FileHashedEvent @@ -123,6 +125,7 @@ public class Core { final ChatServer chatServer final ChatManager chatManager final FeedManager feedManager + private final FeedClient feedClient private final Router router @@ -328,6 +331,10 @@ public class Core { register(UIFeedConfigurationEvent.class, feedManager) } + log.info("initializing feed client") + feedClient = new FeedClient(i2pConnector, eventBus, me, feedManager) + eventBus.register(UIFeedUpdateEvent.class, feedClient) + log.info "initializing results sender" ResultsSender resultsSender = new ResultsSender(eventBus, i2pConnector, me, props, certificateManager, chatServer) @@ -409,6 +416,7 @@ public class Core { hostCache.waitForLoad() updateClient?.start() feedManager.start() + feedClient.start() } public void shutdown() { @@ -444,6 +452,8 @@ public class Core { chatManager.shutdown() log.info("shutting down feed manager") feedManager.stop() + log.info("shutting down feed client") + feedClient.stop() log.info("shutting down connection manager") connectionManager.shutdown() log.info("killing i2p session") diff --git a/core/src/main/groovy/com/muwire/core/filefeeds/FeedClient.groovy b/core/src/main/groovy/com/muwire/core/filefeeds/FeedClient.groovy new file mode 100644 index 00000000..7defb831 --- /dev/null +++ b/core/src/main/groovy/com/muwire/core/filefeeds/FeedClient.groovy @@ -0,0 +1,109 @@ +package com.muwire.core.filefeeds + +import java.lang.System.Logger.Level +import java.nio.charset.StandardCharsets +import java.util.concurrent.Executor +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors +import java.util.zip.GZIPInputStream + +import com.muwire.core.EventBus +import com.muwire.core.Persona +import com.muwire.core.connection.Endpoint +import com.muwire.core.connection.I2PConnector +import com.muwire.core.util.DataUtil + +import groovy.json.JsonSlurper +import groovy.util.logging.Log + +@Log +class FeedClient { + + private final I2PConnector connector + private final EventBus eventBus + private final Persona me + private final FeedManager feedManager + + private final ExecutorService feedFetcher = Executors.newCachedThreadPool() + private final Timer feedUpdater = new Timer("feed-updater", true) + + FeedClient(I2PConnector connector, EventBus eventBus, Persona me, FeedManager feedManager) { + this.connector = connector + this.eventBus = eventBus + this.me = me + this.feedManager = feedManager + } + + private void start() { + feedUpdater.schedule({updateAnyFeeds()} as TimerTask, 60000, 60000) + } + + private void stop() { + feedUpdater.cancel() + feedFetcher.shutdown() + } + + private void updateAnyFeeds() { + feedManager.getFeedsToUpdate().each { + feedFetcher.execute({updateFeed(it)} as Runnable) + } + } + + void onUIFeedUpdateEvent(UIFeedUpdateEvent e) { + Feed feed = feedManager.getFeed(e.host) + if (feed == null) { + log.severe("UI request to update non-existent feed " + e.host.getHumanReadableName()) + return + } + + feedFetcher.execute({updateFeed(feed)} as Runnable) + } + + private void updateFeed(Feed feed) { + log.info("updating feed " + feed.getPublisher().getHumanReadableName()) + Endpoint endpoint = null + try { + eventBus.publish(new FeedFetchEvent(host : feed.getPublisher(), status : FeedFetchStatus.CONNECTING)) + endpoint = connector.connect(feed.getPublisher().getDestination()) + OutputStream os = endpoint.getOutputStream() + os.write("FEED\r\n".getBytes(StandardCharsets.US_ASCII)) + os.write("Persona:${me.toBase64()}\r\n".getBytes(StandardCharsets.US_ASCII)) + os.write("Timestamp:${feed.getLastUpdated()}\r\n".getBytes(StandardCharsets.US_ASCII)) + os.write("\r\n".getBytes(StandardCharsets.US_ASCII)) + os.flush() + + InputStream is = endpoint.getInputStream() + String code = DataUtil.readTillRN(is) + if (!code.startsWith("200")) + throw new IOException("Invalid code $code") + + // parse all headers + Map headers = DataUtil.readAllHeaders(is) + + if (!headers.containsKey("Count")) + throw new IOException("No count header") + + int items = Integer.parseInt(headers['Count']) + + eventBus.publish(new FeedFetchEvent(host : feed.getPublisher(), status : FeedFetchStatus.FETCHING, totalItems: items)) + + JsonSlurper slurper = new JsonSlurper() + DataInputStream dis = new DataInputStream(new GZIPInputStream(is)) + for (int i = 0; i < items; i++) { + int size = dis.readUnsignedShort() + byte [] tmp = new byte[size] + dis.readFully(tmp) + def json = slurper.parse(tmp) + FeedItem item = FeedItems.objToFeedItem(json, feed.getPublisher()) + eventBus.publish(new FeedItemFetchedEvent(item: item)) + } + + eventBus.publish(new FeedFetchEvent(host : feed.getPublisher(), status : FeedFetchStatus.FINISHED)) + } catch (Exception bad) { + log.log(Level.WARNING, "Feed update failed", bad) + eventBus.publish(new FeedFetchEvent(host : feed.getPublisher(), status : FeedFetchStatus.FAILED)) + } finally { + endpoint?.close() + } + } +} diff --git a/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy b/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy index 22cb0f4d..592a5752 100644 --- a/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy +++ b/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy @@ -5,6 +5,7 @@ import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.concurrent.ThreadFactory +import java.util.stream.Collectors import com.muwire.core.EventBus import com.muwire.core.Persona @@ -46,6 +47,13 @@ class FeedManager { feeds.get(persona) } + public List getFeedsToUpdate() { + long now = System.currentTimeMillis() + feeds.values().stream(). + filter({Feed f -> f.getLastUpdated() + f.getUpdateInterval() <= now}) + .collect(Collectors.toList()) + } + void start() { log.info("starting feed manager") persister.submit({loadFeeds()} as Runnable) diff --git a/core/src/main/groovy/com/muwire/core/filefeeds/UIFeedUpdateEvent.groovy b/core/src/main/groovy/com/muwire/core/filefeeds/UIFeedUpdateEvent.groovy new file mode 100644 index 00000000..836cd4f9 --- /dev/null +++ b/core/src/main/groovy/com/muwire/core/filefeeds/UIFeedUpdateEvent.groovy @@ -0,0 +1,8 @@ +package com.muwire.core.filefeeds + +import com.muwire.core.Event +import com.muwire.core.Persona + +class UIFeedUpdateEvent extends Event { + Persona host +} From 17cd60afe3757a7f7d6422b77424eb4f46f9c2c1 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 00:58:43 +0000 Subject: [PATCH 14/52] deleting of feeds --- .../main/groovy/com/muwire/core/Core.groovy | 2 ++ .../muwire/core/filefeeds/FeedManager.groovy | 30 +++++++++++++++++-- .../core/filefeeds/UIFeedDeletedEvent.groovy | 8 +++++ 3 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 core/src/main/groovy/com/muwire/core/filefeeds/UIFeedDeletedEvent.groovy diff --git a/core/src/main/groovy/com/muwire/core/Core.groovy b/core/src/main/groovy/com/muwire/core/Core.groovy index c954f79d..f80e61e3 100644 --- a/core/src/main/groovy/com/muwire/core/Core.groovy +++ b/core/src/main/groovy/com/muwire/core/Core.groovy @@ -38,6 +38,7 @@ import com.muwire.core.filefeeds.FeedItemFetchedEvent import com.muwire.core.filefeeds.FeedManager import com.muwire.core.filefeeds.UIFIlePublishedEvent import com.muwire.core.filefeeds.UIFeedConfigurationEvent +import com.muwire.core.filefeeds.UIFeedDeletedEvent import com.muwire.core.filefeeds.UIFeedUpdateEvent import com.muwire.core.filefeeds.UIFileUnpublishedEvent import com.muwire.core.files.FileDownloadedEvent @@ -329,6 +330,7 @@ public class Core { register(FeedItemFetchedEvent.class, feedManager) register(FeedFetchEvent.class, feedManager) register(UIFeedConfigurationEvent.class, feedManager) + register(UIFeedDeletedEvent.class, feedManager) } log.info("initializing feed client") diff --git a/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy b/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy index 592a5752..ed73a708 100644 --- a/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy +++ b/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy @@ -86,7 +86,7 @@ class FeedManager { private void loadItems() { def slurper = new JsonSlurper() feeds.keySet().each { - File itemsFile = new File(itemsFolder, it.destination.toBase32() + ".json") + File itemsFile = getItemsFile(feeds[it]) if (!itemsFile.exists()) return // no items yet? itemsFile.eachLine { line -> @@ -133,6 +133,15 @@ class FeedManager { persister.submit({saveFeedMetadata(e.feed)} as Runnable) } + void onUIFeedDeletedEvent(UIFeedDeletedEvent e) { + Feed f = feeds.get(e.host) + if (f == null) { + log.severe("Deleting a non-existing feed " + e.host.getHumanReadableName()) + return + } + persister.submit({deleteFeed(f)} as Runnable) + } + private void saveFeedItems(Persona publisher) { Set set = feedItems.get(publisher) if (set == null) @@ -154,7 +163,7 @@ class FeedManager { } - File itemsFile = new File(itemsFolder, publisher.destination.toBase32() + ".json") + File itemsFile = getItemsFile(feed) itemsFile.withPrintWriter { writer -> list.each { item -> def obj = FeedItems.feedItemToObj(item) @@ -165,7 +174,7 @@ class FeedManager { } private void saveFeedMetadata(Feed feed) { - File metadataFile = new File(metadataFolder, feed.publisher.destination.toBase32() + ".json") + File metadataFile = getMetadataFile(feed) metadataFile.withPrintWriter { writer -> def json = [:] json.publisher = feed.getPublisher().toBase64() @@ -177,4 +186,19 @@ class FeedManager { writer.println(json) } } + + private void deleteFeed(Feed feed) { + feeds.remove(feed.getPublisher()) + feedItems.remove(feed.getPublisher()) + getItemsFile(feed).delete() + getMetadataFile(feed).delete() + } + + private File getItemsFile(Feed feed) { + return new File(itemsFolder, feed.getPublisher().destination.toBase32() + ".json") + } + + private File getMetadataFile(Feed feed) { + return new File(metadataFolder, feed.getPublisher().destination.toBase32() + ".json") + } } diff --git a/core/src/main/groovy/com/muwire/core/filefeeds/UIFeedDeletedEvent.groovy b/core/src/main/groovy/com/muwire/core/filefeeds/UIFeedDeletedEvent.groovy new file mode 100644 index 00000000..db574870 --- /dev/null +++ b/core/src/main/groovy/com/muwire/core/filefeeds/UIFeedDeletedEvent.groovy @@ -0,0 +1,8 @@ +package com.muwire.core.filefeeds + +import com.muwire.core.Event +import com.muwire.core.Persona + +class UIFeedDeletedEvent extends Event { + Persona host +} From 8798ea38e80a3d3398594f38aae710050b06bf2a Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 01:39:55 +0000 Subject: [PATCH 15/52] button for publishing, column in the shared files table --- .../com/muwire/gui/MainFrameController.groovy | 18 +++++++++++++ .../com/muwire/gui/MainFrameModel.groovy | 3 +++ .../views/com/muwire/gui/MainFrameView.groovy | 27 ++++++++++++++++--- 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy index 298e9b43..d3839bb6 100644 --- a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy +++ b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy @@ -505,6 +505,24 @@ class MainFrameController { clipboard.setContents(selection, null) } + @ControllerAction + void publish() { + def selectedFiles = view.selectedSharedFiles() + if (selectedFiles == null || selectedFiles.isEmpty()) + return + + if (model.publishButtonText == "Unpublish") { + selectedFiles.each { + it.unpublish() + } + } else { + long now = System.currentTimeMillis() + selectedFiles.stream().filter({!it.isPublished()}).forEach({it.publish(now)}) + } + // TODO: issue event to core + view.refreshSharedFiles() + } + void startChat(Persona p) { if (!mvcGroup.getChildrenGroups().containsKey(p.getHumanReadableName())) { def params = [:] diff --git a/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy b/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy index 32351c1c..0195147a 100644 --- a/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy +++ b/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy @@ -103,6 +103,8 @@ class MainFrameModel { @Observable boolean previewButtonEnabled @Observable String resumeButtonText @Observable boolean addCommentButtonEnabled + @Observable boolean publishButtonEnabled + @Observable String publishButtonText @Observable boolean subscribeButtonEnabled @Observable boolean markNeutralFromTrustedButtonEnabled @Observable boolean markDistrustedButtonEnabled @@ -253,6 +255,7 @@ class MainFrameModel { distrusted.addAll(core.trustService.bad.values()) resumeButtonText = "Retry" + publishButtonText = "Publish" searchesPaneButtonEnabled = false downloadsPaneButtonEnabled = true diff --git a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy index 6949a5d5..91c39394 100644 --- a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy +++ b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy @@ -292,6 +292,7 @@ class MainFrameView { Core core = application.context.get("core") core.certificateManager.hasLocalCertificate(new InfoHash(it.getRoot())) }) + closureColumn(header : "Published", preferredWidth : 50, type : Boolean, read : {row -> row.isPublished()}) closureColumn(header : "Search Hits", preferredWidth: 50, type : Integer, read : {it.getHits()}) closureColumn(header : "Downloaders", preferredWidth: 50, type : Integer, read : {it.getDownloaders().size()}) } @@ -316,9 +317,11 @@ class MainFrameView { radioButton(text : "Table", selected : false, buttonGroup : sharedViewType, actionPerformed : showSharedFilesTable) } panel { - button(text : "Share", actionPerformed : shareFiles) - button(text : "Add Comment", enabled : bind {model.addCommentButtonEnabled}, addCommentAction) - button(text : "Certify", enabled : bind {model.addCommentButtonEnabled}, issueCertificateAction) + gridBagLayout() + button(text : "Share", constraints : gbc(gridx: 0), actionPerformed : shareFiles) + button(text : "Add Comment", enabled : bind {model.addCommentButtonEnabled}, constraints : gbc(gridx: 1), addCommentAction) + button(text : "Certify", enabled : bind {model.addCommentButtonEnabled}, constraints : gbc(gridx: 2), issueCertificateAction) + button(text : bind {model.publishButtonText}, enabled : bind {model.publishButtonEnabled}, constraints : gbc(gridx:3), publishAction) } panel { panel { @@ -682,14 +685,30 @@ class MainFrameView { if (selectedFiles == null || selectedFiles.isEmpty()) return model.addCommentButtonEnabled = true + model.publishButtonEnabled = true + boolean unpublish = true + selectedFiles.each { + unpublish &= it.isPublished() + } + model.publishButtonText = unpublish ? "Unpublish" : "Publish" }) + def sharedFilesTree = builder.getVariable("shared-files-tree") sharedFilesTree.addMouseListener(sharedFilesMouseListener) sharedFilesTree.addTreeSelectionListener({ def selectedNode = sharedFilesTree.getLastSelectedPathComponent() model.addCommentButtonEnabled = selectedNode != null - + model.publishButtonEnabled = selectedNode != null + + def selectedFiles = selectedSharedFiles() + if (selectedFiles == null || selectedFiles.isEmpty()) + return + boolean unpublish = true + selectedFiles.each { + unpublish &= it.isPublished() + } + model.publishButtonText = unpublish ? "Unpublish" : "Publish" }) sharedFilesTree.addTreeExpansionListener(expansionListener) From 48cfce71a812ad53a5ce224db532e3dda3e584ce Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 05:47:42 +0000 Subject: [PATCH 16/52] emit event on publishing --- .../com/muwire/gui/MainFrameController.groovy | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy index d3839bb6..5610d08d 100644 --- a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy +++ b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy @@ -30,6 +30,8 @@ import com.muwire.core.download.UIDownloadCancelledEvent import com.muwire.core.download.UIDownloadPausedEvent import com.muwire.core.download.UIDownloadResumedEvent import com.muwire.core.filecert.UICreateCertificateEvent +import com.muwire.core.filefeeds.UIFIlePublishedEvent +import com.muwire.core.filefeeds.UIFileUnpublishedEvent import com.muwire.core.files.FileUnsharedEvent import com.muwire.core.search.QueryEvent import com.muwire.core.search.SearchEvent @@ -514,12 +516,15 @@ class MainFrameController { if (model.publishButtonText == "Unpublish") { selectedFiles.each { it.unpublish() + model.core.eventBus.publish(new UIFileUnpublishedEvent(sf : it)) } } else { long now = System.currentTimeMillis() - selectedFiles.stream().filter({!it.isPublished()}).forEach({it.publish(now)}) + selectedFiles.stream().filter({!it.isPublished()}).forEach({ + it.publish(now) + model.core.eventBus.publish(new UIFIlePublishedEvent(sf : it)) + }) } - // TODO: issue event to core view.refreshSharedFiles() } From aa0fcfb7def7c6e27beab7e1ae20796c602785a1 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 05:50:54 +0000 Subject: [PATCH 17/52] fix capitalization in event name --- core/src/main/groovy/com/muwire/core/Core.groovy | 4 ++-- ...IFIlePublishedEvent.groovy => UIFilePublishedEvent.groovy} | 2 +- .../com/muwire/core/files/PersisterFolderService.groovy | 4 ++-- .../controllers/com/muwire/gui/MainFrameController.groovy | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) rename core/src/main/groovy/com/muwire/core/filefeeds/{UIFIlePublishedEvent.groovy => UIFilePublishedEvent.groovy} (73%) diff --git a/core/src/main/groovy/com/muwire/core/Core.groovy b/core/src/main/groovy/com/muwire/core/Core.groovy index f80e61e3..3b7a83f6 100644 --- a/core/src/main/groovy/com/muwire/core/Core.groovy +++ b/core/src/main/groovy/com/muwire/core/Core.groovy @@ -36,7 +36,7 @@ import com.muwire.core.filefeeds.FeedClient import com.muwire.core.filefeeds.FeedFetchEvent import com.muwire.core.filefeeds.FeedItemFetchedEvent import com.muwire.core.filefeeds.FeedManager -import com.muwire.core.filefeeds.UIFIlePublishedEvent +import com.muwire.core.filefeeds.UIFilePublishedEvent import com.muwire.core.filefeeds.UIFeedConfigurationEvent import com.muwire.core.filefeeds.UIFeedDeletedEvent import com.muwire.core.filefeeds.UIFeedUpdateEvent @@ -280,7 +280,7 @@ public class Core { eventBus.register(FileHashedEvent.class, persisterFolderService) eventBus.register(FileUnsharedEvent.class, persisterFolderService) eventBus.register(UICommentEvent.class, persisterFolderService) - eventBus.register(UIFIlePublishedEvent.class, persisterFolderService) + eventBus.register(UIFilePublishedEvent.class, persisterFolderService) eventBus.register(UIFileUnpublishedEvent.class, persisterFolderService) log.info("initializing host cache") diff --git a/core/src/main/groovy/com/muwire/core/filefeeds/UIFIlePublishedEvent.groovy b/core/src/main/groovy/com/muwire/core/filefeeds/UIFilePublishedEvent.groovy similarity index 73% rename from core/src/main/groovy/com/muwire/core/filefeeds/UIFIlePublishedEvent.groovy rename to core/src/main/groovy/com/muwire/core/filefeeds/UIFilePublishedEvent.groovy index e2a18aed..b494ce71 100644 --- a/core/src/main/groovy/com/muwire/core/filefeeds/UIFIlePublishedEvent.groovy +++ b/core/src/main/groovy/com/muwire/core/filefeeds/UIFilePublishedEvent.groovy @@ -3,6 +3,6 @@ package com.muwire.core.filefeeds import com.muwire.core.Event import com.muwire.core.SharedFile -class UIFIlePublishedEvent extends Event { +class UIFilePublishedEvent extends Event { SharedFile sf } diff --git a/core/src/main/groovy/com/muwire/core/files/PersisterFolderService.groovy b/core/src/main/groovy/com/muwire/core/files/PersisterFolderService.groovy index 0592f9f9..220f5361 100644 --- a/core/src/main/groovy/com/muwire/core/files/PersisterFolderService.groovy +++ b/core/src/main/groovy/com/muwire/core/files/PersisterFolderService.groovy @@ -1,7 +1,7 @@ package com.muwire.core.files import com.muwire.core.* -import com.muwire.core.filefeeds.UIFIlePublishedEvent +import com.muwire.core.filefeeds.UIFilePublishedEvent import com.muwire.core.filefeeds.UIFileUnpublishedEvent import groovy.json.JsonOutput @@ -96,7 +96,7 @@ class PersisterFolderService extends BasePersisterService { persistFile(e.sharedFile,null) } - void onUIFilePublishedEvent(UIFIlePublishedEvent e) { + void onUIFilePublishedEvent(UIFilePublishedEvent e) { persistFile(e.sf, null) } diff --git a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy index 5610d08d..3b1d0947 100644 --- a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy +++ b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy @@ -30,7 +30,7 @@ import com.muwire.core.download.UIDownloadCancelledEvent import com.muwire.core.download.UIDownloadPausedEvent import com.muwire.core.download.UIDownloadResumedEvent import com.muwire.core.filecert.UICreateCertificateEvent -import com.muwire.core.filefeeds.UIFIlePublishedEvent +import com.muwire.core.filefeeds.UIFilePublishedEvent import com.muwire.core.filefeeds.UIFileUnpublishedEvent import com.muwire.core.files.FileUnsharedEvent import com.muwire.core.search.QueryEvent @@ -522,7 +522,7 @@ class MainFrameController { long now = System.currentTimeMillis() selectedFiles.stream().filter({!it.isPublished()}).forEach({ it.publish(now) - model.core.eventBus.publish(new UIFIlePublishedEvent(sf : it)) + model.core.eventBus.publish(new UIFilePublishedEvent(sf : it)) }) } view.refreshSharedFiles() From f2bf921d4c7030bac558673543a7e7a395f5b540 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 06:06:57 +0000 Subject: [PATCH 18/52] parse feed flag in results --- .../com/muwire/core/connection/ConnectionAcceptor.groovy | 4 ++++ .../main/groovy/com/muwire/core/search/UIResultEvent.groovy | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/core/src/main/groovy/com/muwire/core/connection/ConnectionAcceptor.groovy b/core/src/main/groovy/com/muwire/core/connection/ConnectionAcceptor.groovy index 25266649..720387ef 100644 --- a/core/src/main/groovy/com/muwire/core/connection/ConnectionAcceptor.groovy +++ b/core/src/main/groovy/com/muwire/core/connection/ConnectionAcceptor.groovy @@ -315,6 +315,9 @@ class ConnectionAcceptor { boolean chat = false if (headers.containsKey('Chat')) chat = Boolean.parseBoolean(headers['Chat']) + boolean feed = false + if (headers.containsKey('Feed')) + feed = Boolean.parseBoolean(headers['Feed']) byte [] personaBytes = Base64.decode(headers['Sender']) Persona sender = new Persona(new ByteArrayInputStream(personaBytes)) @@ -334,6 +337,7 @@ class ConnectionAcceptor { def json = slurper.parse(payload) results[i] = ResultsParser.parse(sender, resultsUUID, json) results[i].chat = chat + results[i].feed = feed } eventBus.publish(new UIResultBatchEvent(uuid: resultsUUID, results: results)) } catch (IOException bad) { diff --git a/core/src/main/groovy/com/muwire/core/search/UIResultEvent.groovy b/core/src/main/groovy/com/muwire/core/search/UIResultEvent.groovy index 4fad4f90..77ea0957 100644 --- a/core/src/main/groovy/com/muwire/core/search/UIResultEvent.groovy +++ b/core/src/main/groovy/com/muwire/core/search/UIResultEvent.groovy @@ -18,7 +18,8 @@ class UIResultEvent extends Event { boolean browse int certificates boolean chat - + boolean feed + @Override public String toString() { super.toString() + "name:$name size:$size sender:${sender.getHumanReadableName()} pieceSize $pieceSize" From a0cb214e2bd40091ce0a28fb01fbcc272835215e Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 06:22:40 +0000 Subject: [PATCH 19/52] placeholder feeds panel --- .../com/muwire/gui/MainFrameModel.groovy | 2 ++ .../views/com/muwire/gui/MainFrameView.groovy | 22 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy b/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy index 0195147a..b15a8163 100644 --- a/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy +++ b/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy @@ -120,6 +120,7 @@ class MainFrameModel { @Observable boolean downloadsPaneButtonEnabled @Observable boolean uploadsPaneButtonEnabled @Observable boolean monitorPaneButtonEnabled + @Observable boolean feedsPaneButtonEnabled @Observable boolean trustPaneButtonEnabled @Observable boolean chatPaneButtonEnabled @@ -261,6 +262,7 @@ class MainFrameModel { downloadsPaneButtonEnabled = true uploadsPaneButtonEnabled = true monitorPaneButtonEnabled = true + feedsPaneButtonEnabled = true trustPaneButtonEnabled = true chatPaneButtonEnabled = true diff --git a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy index 91c39394..731a60d0 100644 --- a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy +++ b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy @@ -151,6 +151,7 @@ class MainFrameView { button(text: "Uploads", enabled : bind{model.uploadsPaneButtonEnabled}, actionPerformed : showUploadsWindow) if (settings.showMonitor) button(text: "Monitor", enabled: bind{model.monitorPaneButtonEnabled},actionPerformed : showMonitorWindow) + button(text: "Feeds", enabled: bind {model.feedsPaneButtonEnabled}, actionPerformed : showFeedsWindow) button(text: "Trust", enabled:bind{model.trustPaneButtonEnabled},actionPerformed : showTrustWindow) button(text: "Chat", enabled : bind{model.chatPaneButtonEnabled}, actionPerformed : showChatWindow) } @@ -428,6 +429,9 @@ class MainFrameView { } } } + panel(constraints : "feeds window") { + label(text : "Feeds go here") + } panel(constraints : "trust window") { gridLayout(rows : 2, cols : 1) panel { @@ -1088,6 +1092,7 @@ class MainFrameView { model.downloadsPaneButtonEnabled = false model.uploadsPaneButtonEnabled = true model.monitorPaneButtonEnabled = true + model.feedsPaneButtonEnabled = true model.trustPaneButtonEnabled = true model.chatPaneButtonEnabled = true chatNotificator.mainWindowDeactivated() @@ -1100,6 +1105,7 @@ class MainFrameView { model.downloadsPaneButtonEnabled = true model.uploadsPaneButtonEnabled = false model.monitorPaneButtonEnabled = true + model.feedsPaneButtonEnabled = true model.trustPaneButtonEnabled = true model.chatPaneButtonEnabled = true chatNotificator.mainWindowDeactivated() @@ -1112,6 +1118,20 @@ class MainFrameView { model.downloadsPaneButtonEnabled = true model.uploadsPaneButtonEnabled = true model.monitorPaneButtonEnabled = false + model.feedsPaneButtonEnabled = true + model.trustPaneButtonEnabled = true + model.chatPaneButtonEnabled = true + chatNotificator.mainWindowDeactivated() + } + + def showFeedsWindow = { + def cardsPanel = builder.getVariable("cards-panel") + cardsPanel.getLayout().show(cardsPanel,"feeds window") + model.searchesPaneButtonEnabled = true + model.downloadsPaneButtonEnabled = true + model.uploadsPaneButtonEnabled = true + model.monitorPaneButtonEnabled = true + model.feedsPaneButtonEnabled = false model.trustPaneButtonEnabled = true model.chatPaneButtonEnabled = true chatNotificator.mainWindowDeactivated() @@ -1124,6 +1144,7 @@ class MainFrameView { model.downloadsPaneButtonEnabled = true model.uploadsPaneButtonEnabled = true model.monitorPaneButtonEnabled = true + model.feedsPaneButtonEnabled = true model.trustPaneButtonEnabled = false model.chatPaneButtonEnabled = true chatNotificator.mainWindowDeactivated() @@ -1136,6 +1157,7 @@ class MainFrameView { model.downloadsPaneButtonEnabled = true model.uploadsPaneButtonEnabled = true model.monitorPaneButtonEnabled = true + model.feedsPaneButtonEnabled = true model.trustPaneButtonEnabled = true model.chatPaneButtonEnabled = false chatNotificator.mainWindowActivated() From e2a9db8056f2b8a964d0fe67d4ab26fee0221c78 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 07:32:45 +0000 Subject: [PATCH 20/52] add an IDLE status to feeds for display purposes --- .../com/muwire/core/filefeeds/FeedFetchStatus.java | 2 +- .../com/muwire/core/filefeeds/FeedManager.groovy | 12 ++++++++++-- .../main/java/com/muwire/core/filefeeds/Feed.java | 8 ++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/core/src/main/groovy/com/muwire/core/filefeeds/FeedFetchStatus.java b/core/src/main/groovy/com/muwire/core/filefeeds/FeedFetchStatus.java index 7545252e..57bb36f6 100644 --- a/core/src/main/groovy/com/muwire/core/filefeeds/FeedFetchStatus.java +++ b/core/src/main/groovy/com/muwire/core/filefeeds/FeedFetchStatus.java @@ -1,5 +1,5 @@ package com.muwire.core.filefeeds; public enum FeedFetchStatus { - CONNECTING, FETCHING, FINISHED, FAILED + IDLE, CONNECTING, FETCHING, FINISHED, FAILED } diff --git a/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy b/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy index ed73a708..ca0d9727 100644 --- a/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy +++ b/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy @@ -77,6 +77,8 @@ class FeedManager { feed.setItemsToKeep(parsed.itemsToKeep) feed.setAutoDownload(parsed.autoDownload) + feed.setStatus(FeedFetchStatus.IDLE.name()) + feeds.put(feed.getPublisher(), feed) eventBus.publish(new FeedLoadedEvent(feed : feed)) @@ -115,13 +117,19 @@ class FeedManager { } void onFeedFetchEvent(FeedFetchEvent e) { - if (e.status != FeedFetchStatus.FINISHED) - return + Feed feed = feeds.get(e.host) if (feed == null) { log.severe("Finished fetching non-existent feed " + e.host.getHumanReadableName()) return } + + feed.setStatus(e.status.name()) + + if (e.status != FeedFetchStatus.FINISHED) + return + + feed.setStatus(FeedFetchStatus.IDLE.name()) feed.setLastUpdated(e.getTimestamp()) // save feed items, then save feed persister.submit({saveFeedItems(e.host)} as Runnable) diff --git a/core/src/main/java/com/muwire/core/filefeeds/Feed.java b/core/src/main/java/com/muwire/core/filefeeds/Feed.java index 610dbb5a..6e4f6851 100644 --- a/core/src/main/java/com/muwire/core/filefeeds/Feed.java +++ b/core/src/main/java/com/muwire/core/filefeeds/Feed.java @@ -10,6 +10,7 @@ public class Feed { private long lastUpdated; private int itemsToKeep; private boolean autoDownload; + private String status; public Feed(Persona publisher) { this.publisher = publisher; @@ -51,4 +52,11 @@ public class Feed { return publisher; } + public void setStatus(String status) { + this.status = status; + } + + public String getStatus() { + return status; + } } From 1610766e012d743e69d1c36a778acafc00e121e0 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 07:33:29 +0000 Subject: [PATCH 21/52] wip on feeds table --- .../com/muwire/gui/MainFrameModel.groovy | 19 ++++++++ .../views/com/muwire/gui/MainFrameView.groovy | 45 ++++++++++++++++++- 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy b/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy index b15a8163..2f70305b 100644 --- a/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy +++ b/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy @@ -28,6 +28,8 @@ import com.muwire.core.content.ContentControlEvent import com.muwire.core.download.DownloadStartedEvent import com.muwire.core.download.Downloader import com.muwire.core.filecert.CertificateCreatedEvent +import com.muwire.core.filefeeds.FeedFetchEvent +import com.muwire.core.filefeeds.FeedLoadedEvent import com.muwire.core.files.AllFilesLoadedEvent import com.muwire.core.files.DirectoryUnsharedEvent import com.muwire.core.files.DirectoryWatchedEvent @@ -89,6 +91,8 @@ class MainFrameModel { def trusted = [] def distrusted = [] def subscriptions = [] + def feeds = [] + def feedItems = [] boolean sessionRestored @@ -218,6 +222,8 @@ class MainFrameModel { core.eventBus.register(TrustSubscriptionUpdatedEvent.class, this) core.eventBus.register(SearchEvent.class, this) core.eventBus.register(CertificateCreatedEvent.class, this) + core.eventBus.register(FeedLoadedEvent.class, this) + core.eventBus.register(FeedFetchEvent.class, this) core.muOptions.watchedKeywords.each { core.eventBus.publish(new ContentControlEvent(term : it, regex: false, add: true)) @@ -656,4 +662,17 @@ class MainFrameModel { int requests boolean finished } + + void onFeedLoadedEvent(FeedLoadedEvent e) { + runInsideUIAsync { + feeds << e.feed + view.refreshFeeds() + } + } + + void onFeedFetchEvent(FeedFetchEvent e) { + runInsideUIAsync { + view.refreshFeeds() + } + } } \ No newline at end of file diff --git a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy index 731a60d0..b8f8367c 100644 --- a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy +++ b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy @@ -76,6 +76,8 @@ class MainFrameView { def lastSharedSortEvent def trustTablesSortEvents = [:] def expansionListener = new TreeExpansions() + def lastFeedsSortEvent + def lastFeedItemsSortEvent UISettings settings @@ -430,7 +432,33 @@ class MainFrameView { } } panel(constraints : "feeds window") { - label(text : "Feeds go here") + gridLayout(rows : 2, cols : 1) + panel { + borderLayout() + panel (constraints : BorderLayout.NORTH) { + label(text: "Subscriptions") + } + scrollPane(constraints : BorderLayout.CENTER) { + table(id : "feeds-table", autoCreateRowSorter : true, rowHeight : rowHeight) { + tableModel(list : model.feeds) { + + } + } + } + } + panel { + borderLayout() + panel (constraints : BorderLayout.NORTH) { + label(text : "Published Items") + } + scrollPane(constraints : BorderLayout.CENTER) { + table(id : "feed-items-table", autoCreateRowSorter : true, rowHeight : rowHeight) { + tableModel(list : model.feedItems) { + + } + } + } + } } panel(constraints : "trust window") { gridLayout(rows : 2, cols : 1) @@ -768,6 +796,13 @@ class MainFrameView { } }) + // feeds table + def feedsTable = builder.getVariable("feeds-table") + feedsTable.rowSorter.addRowSorterListener({evt -> lastFeedsSortEvent = evt}) + selectionModel = feedsTable.getSelectionModel() + selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION) + // TODO: hook up with feedItems table + // subscription table def subscriptionTable = builder.getVariable("subscription-table") subscriptionTable.setDefaultRenderer(Integer.class, centerRenderer) @@ -1214,6 +1249,14 @@ class MainFrameView { builder.getVariable("shared-files-table").model.fireTableDataChanged() } + public void refreshFeeds() { + JTable feedsTable = builder.getVariable("feeds-table") + int selectedFeed = feedsTable.getSelectedRow() + feedsTable.model.fireTableDataChanged() + if (selectedFeed >= 0) + feedsTable.selectionModel.setSelectionInterval(selectedFeed, selectedFeed) + } + private void closeApplication() { Core core = application.getContext().get("core") From fcb5c573f9471a3d16effbea3b10eb8e3236e550 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 10:39:18 +0000 Subject: [PATCH 22/52] wip on feeds table --- .../core/filefeeds/FeedFetchStatus.java | 5 --- .../muwire/core/filefeeds/FeedManager.groovy | 10 +++-- .../java/com/muwire/core/filefeeds/Feed.java | 6 +-- .../core/filefeeds/FeedFetchStatus.java | 19 ++++++++++ .../com/muwire/gui/MainFrameController.groovy | 15 ++++++++ .../com/muwire/gui/MainFrameModel.groovy | 3 ++ .../views/com/muwire/gui/MainFrameView.groovy | 37 ++++++++++++++++++- 7 files changed, 83 insertions(+), 12 deletions(-) delete mode 100644 core/src/main/groovy/com/muwire/core/filefeeds/FeedFetchStatus.java create mode 100644 core/src/main/java/com/muwire/core/filefeeds/FeedFetchStatus.java diff --git a/core/src/main/groovy/com/muwire/core/filefeeds/FeedFetchStatus.java b/core/src/main/groovy/com/muwire/core/filefeeds/FeedFetchStatus.java deleted file mode 100644 index 57bb36f6..00000000 --- a/core/src/main/groovy/com/muwire/core/filefeeds/FeedFetchStatus.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.muwire.core.filefeeds; - -public enum FeedFetchStatus { - IDLE, CONNECTING, FETCHING, FINISHED, FAILED -} diff --git a/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy b/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy index ca0d9727..418eaa5b 100644 --- a/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy +++ b/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy @@ -47,6 +47,10 @@ class FeedManager { feeds.get(persona) } + public Set getFeedItems(Persona persona) { + feedItems.get(persona) + } + public List getFeedsToUpdate() { long now = System.currentTimeMillis() feeds.values().stream(). @@ -77,7 +81,7 @@ class FeedManager { feed.setItemsToKeep(parsed.itemsToKeep) feed.setAutoDownload(parsed.autoDownload) - feed.setStatus(FeedFetchStatus.IDLE.name()) + feed.setStatus(FeedFetchStatus.IDLE) feeds.put(feed.getPublisher(), feed) @@ -124,12 +128,12 @@ class FeedManager { return } - feed.setStatus(e.status.name()) + feed.setStatus(e.status) if (e.status != FeedFetchStatus.FINISHED) return - feed.setStatus(FeedFetchStatus.IDLE.name()) + feed.setStatus(FeedFetchStatus.IDLE) feed.setLastUpdated(e.getTimestamp()) // save feed items, then save feed persister.submit({saveFeedItems(e.host)} as Runnable) diff --git a/core/src/main/java/com/muwire/core/filefeeds/Feed.java b/core/src/main/java/com/muwire/core/filefeeds/Feed.java index 6e4f6851..8758265c 100644 --- a/core/src/main/java/com/muwire/core/filefeeds/Feed.java +++ b/core/src/main/java/com/muwire/core/filefeeds/Feed.java @@ -10,7 +10,7 @@ public class Feed { private long lastUpdated; private int itemsToKeep; private boolean autoDownload; - private String status; + private FeedFetchStatus status; public Feed(Persona publisher) { this.publisher = publisher; @@ -52,11 +52,11 @@ public class Feed { return publisher; } - public void setStatus(String status) { + public void setStatus(FeedFetchStatus status) { this.status = status; } - public String getStatus() { + public FeedFetchStatus getStatus() { return status; } } diff --git a/core/src/main/java/com/muwire/core/filefeeds/FeedFetchStatus.java b/core/src/main/java/com/muwire/core/filefeeds/FeedFetchStatus.java new file mode 100644 index 00000000..f6b1a3d7 --- /dev/null +++ b/core/src/main/java/com/muwire/core/filefeeds/FeedFetchStatus.java @@ -0,0 +1,19 @@ +package com.muwire.core.filefeeds; + +public enum FeedFetchStatus { + IDLE(false), + CONNECTING(true), + FETCHING(true), + FINISHED(false), + FAILED(false); + + private final boolean active; + + FeedFetchStatus(boolean active) { + this.active = active; + } + + public boolean isActive() { + return active; + } +} diff --git a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy index 3b1d0947..7682b5c0 100644 --- a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy +++ b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy @@ -528,6 +528,21 @@ class MainFrameController { view.refreshSharedFiles() } + @ControllerAction + void updateFileFeed() { + + } + + @ControllerAction + void unsubscribeFileFeed() { + + } + + @ControllerAction + void configureFileFeed() { + + } + void startChat(Persona p) { if (!mvcGroup.getChildrenGroups().containsKey(p.getHumanReadableName())) { def params = [:] diff --git a/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy b/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy index 2f70305b..c08752e3 100644 --- a/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy +++ b/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy @@ -109,6 +109,9 @@ class MainFrameModel { @Observable boolean addCommentButtonEnabled @Observable boolean publishButtonEnabled @Observable String publishButtonText + @Observable boolean updateFileFeedButtonEnabled + @Observable boolean unsubscribeFileFeedButtonEnabled + @Observable boolean configureFileFeedButtonEnabled @Observable boolean subscribeButtonEnabled @Observable boolean markNeutralFromTrustedButtonEnabled @Observable boolean markDistrustedButtonEnabled diff --git a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy index b8f8367c..d1f1291b 100644 --- a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy +++ b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy @@ -39,6 +39,8 @@ import com.muwire.core.InfoHash import com.muwire.core.MuWireSettings import com.muwire.core.SharedFile import com.muwire.core.download.Downloader +import com.muwire.core.filefeeds.Feed +import com.muwire.core.filefeeds.FeedFetchStatus import com.muwire.core.files.FileSharedEvent import com.muwire.core.trust.RemoteTrustList import java.awt.BorderLayout @@ -441,10 +443,18 @@ class MainFrameView { scrollPane(constraints : BorderLayout.CENTER) { table(id : "feeds-table", autoCreateRowSorter : true, rowHeight : rowHeight) { tableModel(list : model.feeds) { - + closureColumn(header : "Publisher", type : String, read : {it.getPublisher()}) + closureColumn(header : "Files", type : Integer, read : {model.core.feedManager.getFeedItems(it.getPublisher()).size()}) + closureColumn(header : "Last Updated", type : Long, read : {it.getLastUpdated()}) + closureColumn(header : "Status", type : String, read : {it.getStatus()}) } } } + panel (constraints : BorderLayout.SOUTH) { + button(text : "Update", enabled : bind {model.updateFileFeedButtonEnabled}, updateFileFeedAction) + button(text : "Unsubscribe", enabled : bind {model.unsubscribeFileFeedButtonEnabled}, unsubscribeFileFeedAction) + button(text : "Configure", enabled : bind {model.configureFileFeedButtonEnabled}, configureFileFeedAction) + } } panel { borderLayout() @@ -799,8 +809,23 @@ class MainFrameView { // feeds table def feedsTable = builder.getVariable("feeds-table") feedsTable.rowSorter.addRowSorterListener({evt -> lastFeedsSortEvent = evt}) + feedsTable.setDefaultRenderer(Integer.class, centerRenderer) + feedsTable.setDefaultRenderer(Long.class, new DateRenderer()) selectionModel = feedsTable.getSelectionModel() selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION) + selectionModel.addListSelectionListener({ + Feed selectedFeed = selectedFeed() + if (selectedFeed == null) { + model.updateFileFeedButtonEnabled = false + model.unsubscribeFileFeedButtonEnabled = false + model.configureFileFeedButtonEnabled = false + return + } + + model.unsubscribeFileFeedButtonEnabled = true + model.configureFileFeedButtonEnabled = true + model.updateFileFeedButtonEnabled = !selectedFeed.getStatus().isActive() + }) // TODO: hook up with feedItems table // subscription table @@ -1257,6 +1282,16 @@ class MainFrameView { feedsTable.selectionModel.setSelectionInterval(selectedFeed, selectedFeed) } + Feed selectedFeed() { + JTable feedsTable = builder.getVariable("feeds-table") + int row = feedsTable.getSelectedRow() + if (row < 0) + return null + if (lastFeedsSortEvent != null) + row = table.rowSorter.convertRowIndexToModel(row) + model.feeds[row] + } + private void closeApplication() { Core core = application.getContext().get("core") From b9333913c619f10d4f6df20e512f7181a4ec3df3 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 10:47:05 +0000 Subject: [PATCH 23/52] hook up feeds table to feed items table --- .../views/com/muwire/gui/MainFrameView.groovy | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy index d1f1291b..53ba8205 100644 --- a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy +++ b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy @@ -825,8 +825,17 @@ class MainFrameView { model.unsubscribeFileFeedButtonEnabled = true model.configureFileFeedButtonEnabled = true model.updateFileFeedButtonEnabled = !selectedFeed.getStatus().isActive() + + def items = model.core.feedManager.getFeedItems(selectedFeed.getPublisher()) + model.feedItems.clear() + model.feedItems.addAll(items) + + def feedItemsTable = builder.getVariable("feed-items-table") + int selectedItemRow = feedItemsTable.selectedRow() + feedItemsTable.model.fireTableDataChanged() + if (selectedItemRow >= 0 && selectedItemRow < items.size()) + feedItemsTable.selectionModel.setSelectionInterval(selectedItemRow, selectedItemRow) }) - // TODO: hook up with feedItems table // subscription table def subscriptionTable = builder.getVariable("subscription-table") From a07f01b641b8e942a0a6a1c649ee839f88a82747 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 11:50:09 +0000 Subject: [PATCH 24/52] utility method to check if an infohash is shared --- core/src/main/groovy/com/muwire/core/files/FileManager.groovy | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/main/groovy/com/muwire/core/files/FileManager.groovy b/core/src/main/groovy/com/muwire/core/files/FileManager.groovy index a130d336..b6bcf3a9 100644 --- a/core/src/main/groovy/com/muwire/core/files/FileManager.groovy +++ b/core/src/main/groovy/com/muwire/core/files/FileManager.groovy @@ -193,6 +193,10 @@ class FileManager { Set getSharedFiles(byte []root) { return rootToFiles.get(new InfoHash(root)) } + + boolean isShared(InfoHash infoHash) { + rootToFiles.containsKey(infoHash) + } void onSearchEvent(SearchEvent e) { // hash takes precedence From 0ff9ca8572a037b08465ddcaf53373c5e3e750aa Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 11:50:55 +0000 Subject: [PATCH 25/52] wip on feed items table --- .../com/muwire/gui/MainFrameController.groovy | 15 +++++ .../com/muwire/gui/MainFrameModel.groovy | 3 + .../views/com/muwire/gui/MainFrameView.groovy | 63 ++++++++++++++++++- 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy index 7682b5c0..83c4490d 100644 --- a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy +++ b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy @@ -543,6 +543,21 @@ class MainFrameController { } + @ControllerAction + void downloadFeedItem() { + + } + + @ControllerAction + void viewFeedItemComment() { + + } + + @ControllerAction + void viewFeedItemCertificates() { + + } + void startChat(Persona p) { if (!mvcGroup.getChildrenGroups().containsKey(p.getHumanReadableName())) { def params = [:] diff --git a/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy b/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy index c08752e3..2ebcc217 100644 --- a/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy +++ b/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy @@ -112,6 +112,9 @@ class MainFrameModel { @Observable boolean updateFileFeedButtonEnabled @Observable boolean unsubscribeFileFeedButtonEnabled @Observable boolean configureFileFeedButtonEnabled + @Observable boolean downloadFeedItemButtonEnabled + @Observable boolean viewFeedItemCommentButtonEnabled + @Observable boolean viewFeedItemCertificatesButtonEnabled @Observable boolean subscribeButtonEnabled @Observable boolean markNeutralFromTrustedButtonEnabled @Observable boolean markDistrustedButtonEnabled diff --git a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy index 53ba8205..6f237270 100644 --- a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy +++ b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy @@ -41,6 +41,7 @@ import com.muwire.core.SharedFile import com.muwire.core.download.Downloader import com.muwire.core.filefeeds.Feed import com.muwire.core.filefeeds.FeedFetchStatus +import com.muwire.core.filefeeds.FeedItem import com.muwire.core.files.FileSharedEvent import com.muwire.core.trust.RemoteTrustList import java.awt.BorderLayout @@ -459,15 +460,28 @@ class MainFrameView { panel { borderLayout() panel (constraints : BorderLayout.NORTH) { - label(text : "Published Items") + label(text : "Published Files") } scrollPane(constraints : BorderLayout.CENTER) { table(id : "feed-items-table", autoCreateRowSorter : true, rowHeight : rowHeight) { tableModel(list : model.feedItems) { - + closureColumn(header : "Name", type : String, read : {it.getName()}) + closureColumn(header : "Size", type : Long, read : {it.getSize()}) + closureColumn(header : "Comment", type : Boolean, read : {it.getComment() != null}) + closureColumn(header : "Certificates", type : Integer, read : {it.getCertificates()}) + closureColumn(header : "Downloaded", type : Boolean, read : { + InfoHash ih = it.getInfoHash() + model.core.fileManager.isShared(ih) + }) + closureColumn(header: "Date", type : Long, read : {it.getTimestamp()}) } } } + panel(constraints : BorderLayout.SOUTH) { + button(text : "Download", enabled : bind {model.downloadFeedItemButtonEnabled}, downloadFeedItemAction) + button(text : "View Comment", enabled : bind {model.viewFeedItemCommentButtonEnabled}, viewFeedItemCommentAction) + button(text : "View Certificates", enabled : bind {model.viewFeedItemCertificatesButtonEnabled}, viewFeedItemCertificatesAction ) + } } } panel(constraints : "trust window") { @@ -809,6 +823,7 @@ class MainFrameView { // feeds table def feedsTable = builder.getVariable("feeds-table") feedsTable.rowSorter.addRowSorterListener({evt -> lastFeedsSortEvent = evt}) + feedsTable.rowSorter.setSortsOnUpdates(true) feedsTable.setDefaultRenderer(Integer.class, centerRenderer) feedsTable.setDefaultRenderer(Long.class, new DateRenderer()) selectionModel = feedsTable.getSelectionModel() @@ -837,6 +852,34 @@ class MainFrameView { feedItemsTable.selectionModel.setSelectionInterval(selectedItemRow, selectedItemRow) }) + // feed items table + def feedItemsTable = builder.getVariable("feed-items-table") + feedItemsTable.rowSorter.addRowSorterListener({evt -> lastFeedItemsSortEvent = evt}) + feedItemsTable.rowSorter.setSortsOnUpdates(true) + feedItemsTable.setDefaultRenderer(Integer.class, centerRenderer) + feedItemsTable.columnModel.getColumn(1).setCellRenderer(new SizeRenderer()) + feedItemsTable.columnModel.getColumn(5).setCellRenderer(new DateRenderer()) + + selectionModel = feedItemsTable.getSelectionModel() + selectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION) + selectionModel.addListSelectionListener({ + List selectedItems = selectedFeedItems() + if (selectedItems == null || selectedItems.isEmpty()) { + model.downloadFeedItemButtonEnabled = false + model.viewFeedItemCommentButtonEnabled = false + model.viewFeedItemCertificatesButtonEnabled = false + return + } + model.downloadFeedItemButtonEnabled = true + model.viewFeedItemCommentButtonEnabled = false + model.viewFeedItemCertificatesButtonEnabled = false + if (selectedItems.size() == 1) { + FeedItem item = selectedItems.get(0) + model.viewFeedItemCommentButtonEnabled = item.getComment() != null + model.viewFeedItemCertificatesButtonEnabled = item.getCertificates() > 0 + } + }) + // subscription table def subscriptionTable = builder.getVariable("subscription-table") subscriptionTable.setDefaultRenderer(Integer.class, centerRenderer) @@ -1301,6 +1344,22 @@ class MainFrameView { model.feeds[row] } + List selectedFeedItems() { + JTable feedItemsTable = builder.getVariable("feed-items-table") + int [] selectedRows = feedItemsTable.getSelectedRows() + if (selectedRows.length == 0) + return null + List rv = new ArrayList<>() + if (lastFeedItemsSortEvent != null) { + for (int i = 0; i < selectedRows.length; i++) { + selectedRows[i] = feedItemsTable.rowSorter.convertRowIndexToModel(selectedRows[i]) + } + } + for (int selectedRow : selectedRows) + rv.add(model.feedItems[selectedRow]) + rv + } + private void closeApplication() { Core core = application.getContext().get("core") From fd75d8229bc0b632ce4db71bcddb0d736f61f1fa Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 12:15:52 +0000 Subject: [PATCH 26/52] fix feed checkbox for local results --- .../main/groovy/com/muwire/core/search/ResultsSender.groovy | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/groovy/com/muwire/core/search/ResultsSender.groovy b/core/src/main/groovy/com/muwire/core/search/ResultsSender.groovy index c61ebc5a..76c02139 100644 --- a/core/src/main/groovy/com/muwire/core/search/ResultsSender.groovy +++ b/core/src/main/groovy/com/muwire/core/search/ResultsSender.groovy @@ -88,7 +88,8 @@ class ResultsSender { sources : suggested, comment : comment, certificates : certificates, - chat : chatServer.running.get() && settings.advertiseChat + chat : chatServer.running.get() && settings.advertiseChat, + feed : settings.fileFeed && settings.advertiseFeed ) uiResultEvents << uiResultEvent } From e9f00c2995c5772a241f4ea6bc26197ea680cfd4 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 12:16:18 +0000 Subject: [PATCH 27/52] subscribe button in search tab --- .../com/muwire/gui/SearchTabController.groovy | 5 +++++ .../com/muwire/gui/SearchTabModel.groovy | 1 + .../views/com/muwire/gui/SearchTabView.groovy | 18 +++++++++++++++++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/gui/griffon-app/controllers/com/muwire/gui/SearchTabController.groovy b/gui/griffon-app/controllers/com/muwire/gui/SearchTabController.groovy index 3d6d7fa8..34a508f2 100644 --- a/gui/griffon-app/controllers/com/muwire/gui/SearchTabController.groovy +++ b/gui/griffon-app/controllers/com/muwire/gui/SearchTabController.groovy @@ -107,6 +107,11 @@ class SearchTabController { mvcGroup.createMVCGroup("browse", groupId, params) } + @ControllerAction + void subscribe() { + + } + @ControllerAction void chat() { def sender = view.selectedSender() diff --git a/gui/griffon-app/models/com/muwire/gui/SearchTabModel.groovy b/gui/griffon-app/models/com/muwire/gui/SearchTabModel.groovy index 6e7144aa..2ad0a2ee 100644 --- a/gui/griffon-app/models/com/muwire/gui/SearchTabModel.groovy +++ b/gui/griffon-app/models/com/muwire/gui/SearchTabModel.groovy @@ -25,6 +25,7 @@ class SearchTabModel { @Observable boolean viewCommentActionEnabled @Observable boolean viewCertificatesActionEnabled @Observable boolean chatActionEnabled + @Observable boolean subscribeActionEnabled @Observable boolean groupedByFile Core core diff --git a/gui/griffon-app/views/com/muwire/gui/SearchTabView.groovy b/gui/griffon-app/views/com/muwire/gui/SearchTabView.groovy index c83e2e88..6dbdaf89 100644 --- a/gui/griffon-app/views/com/muwire/gui/SearchTabView.groovy +++ b/gui/griffon-app/views/com/muwire/gui/SearchTabView.groovy @@ -74,6 +74,7 @@ class SearchTabView { closureColumn(header : "Sender", preferredWidth : 500, type: String, read : {row -> row.getHumanReadableName()}) closureColumn(header : "Results", preferredWidth : 20, type: Integer, read : {row -> model.sendersBucket[row].size()}) closureColumn(header : "Browse", preferredWidth : 20, type: Boolean, read : {row -> model.sendersBucket[row].first().browse}) + closureColumn(header : "Feed", preferredWidth : 20, type : Boolean, read : {row -> model.sendersBucket[row].first().feed}) closureColumn(header : "Chat", preferredWidth : 20, type : Boolean, read : {row -> model.sendersBucket[row].first().chat}) closureColumn(header : "Trust", preferredWidth : 50, type: String, read : { row -> model.core.trustService.getLevel(row.destination).toString() @@ -85,6 +86,7 @@ class SearchTabView { gridLayout(rows: 1, cols : 2) panel (border : etchedBorder()){ button(text : "Browse Host", enabled : bind {model.browseActionEnabled}, browseAction) + button(text : "Subscribe", enabled : bind {model.subscribeActionEnabled}, subscribeAction) button(text : "Chat", enabled : bind{model.chatActionEnabled}, chatAction) } panel (border : etchedBorder()){ @@ -156,6 +158,14 @@ class SearchTabView { } count }) + closureColumn(header : "Feeds", preferredWidth : 20, type : Integer, read : { + int count = 0 + model.hashBucket[it].each { + if (it.feed) + count++ + } + count + }) closureColumn(header : "Chat Hosts", preferredWidth : 20, type : Integer, read : { int count = 0 model.hashBucket[it].each { @@ -187,6 +197,7 @@ class SearchTabView { tableModel(list : model.senders2) { closureColumn(header : "Sender", preferredWidth : 350, type : String, read : {it.sender.getHumanReadableName()}) closureColumn(header : "Browse", preferredWidth : 20, type : Boolean, read : {it.browse}) + closureColumn(header : "Feed", preferredWidth : 20, type: Boolean, read : {it.feed}) closureColumn(header : "Chat", preferredWidth : 20, type : Boolean, read : {it.chat}) closureColumn(header : "Comment", preferredWidth : 20, type : Boolean, read : {it.comment != null}) closureColumn(header : "Certificates", preferredWidth : 20, type: Integer, read : {it.certificates}) @@ -200,6 +211,7 @@ class SearchTabView { gridLayout(rows : 1, cols : 2) panel (border : etchedBorder()) { button(text : "Browse Host", enabled : bind {model.browseActionEnabled}, browseAction) + button(text : "Subscribe", enabled : bind {model.subscribeActionEnabled}, subscribeAction) button(text : "Chat", enabled : bind{model.chatActionEnabled}, chatAction) button(text : "View Comment", enabled : bind {model.viewCommentActionEnabled}, showCommentAction) button(text : "View Certificates", enabled : bind {model.viewCertificatesActionEnabled}, viewCertificatesAction) @@ -308,6 +320,7 @@ class SearchTabView { if (result == null) { model.viewCommentActionEnabled = false model.viewCertificatesActionEnabled = false + model.subscribeActionEnabled = false return } else { model.viewCommentActionEnabled = result.comment != null @@ -326,12 +339,13 @@ class SearchTabView { if (row < 0) { model.trustButtonsEnabled = false model.browseActionEnabled = false - model.chatActionEnabled = false + model.subscribeActionEnabled = false return } else { Persona sender = model.senders[row] model.browseActionEnabled = model.sendersBucket[sender].first().browse model.chatActionEnabled = model.sendersBucket[sender].first().chat + model.subscribeActionEnabled = model.sendersBucket[sender].first().feed model.trustButtonsEnabled = true model.results.clear() model.results.addAll(model.sendersBucket[sender]) @@ -386,6 +400,7 @@ class SearchTabView { if (row < 0 || model.senders2[row] == null) { model.browseActionEnabled = false model.chatActionEnabled = false + model.subscribeActionEnabled = false model.viewCertificatesActionEnabled = false model.trustButtonsEnabled = false model.viewCommentActionEnabled = false @@ -393,6 +408,7 @@ class SearchTabView { } model.browseActionEnabled = model.senders2[row].browse model.chatActionEnabled = model.senders2[row].chat + model.subscribeActionEnabled = model.senders2[row].feed model.trustButtonsEnabled = true model.viewCommentActionEnabled = model.senders2[row].comment != null model.viewCertificatesActionEnabled = model.senders2[row].certificates > 0 From ed04c40420b9b784f73db9c2f65e94cd5d47baa4 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 12:43:49 +0000 Subject: [PATCH 28/52] return an empty set if no items are found --- .../main/groovy/com/muwire/core/filefeeds/FeedManager.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy b/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy index 418eaa5b..52b3baaf 100644 --- a/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy +++ b/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy @@ -48,7 +48,7 @@ class FeedManager { } public Set getFeedItems(Persona persona) { - feedItems.get(persona) + feedItems.getOrDefault(persona, Collections.emptySet()) } public List getFeedsToUpdate() { From e70bec3a51d0c33d501a57eab1cb5274fc6bac0e Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 12:44:25 +0000 Subject: [PATCH 29/52] hook up feed subscription --- .../core/filefeeds/UIFeedConfigurationEvent.groovy | 1 + .../com/muwire/gui/SearchTabController.groovy | 11 +++++++++++ .../models/com/muwire/gui/MainFrameModel.groovy | 13 +++++++++++++ .../views/com/muwire/gui/MainFrameView.groovy | 2 +- 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/core/src/main/groovy/com/muwire/core/filefeeds/UIFeedConfigurationEvent.groovy b/core/src/main/groovy/com/muwire/core/filefeeds/UIFeedConfigurationEvent.groovy index 9917c30c..3e0e176e 100644 --- a/core/src/main/groovy/com/muwire/core/filefeeds/UIFeedConfigurationEvent.groovy +++ b/core/src/main/groovy/com/muwire/core/filefeeds/UIFeedConfigurationEvent.groovy @@ -8,4 +8,5 @@ import com.muwire.core.Event */ class UIFeedConfigurationEvent extends Event { Feed feed + boolean newFeed } diff --git a/gui/griffon-app/controllers/com/muwire/gui/SearchTabController.groovy b/gui/griffon-app/controllers/com/muwire/gui/SearchTabController.groovy index 34a508f2..cc7f07d0 100644 --- a/gui/griffon-app/controllers/com/muwire/gui/SearchTabController.groovy +++ b/gui/griffon-app/controllers/com/muwire/gui/SearchTabController.groovy @@ -12,6 +12,8 @@ import javax.swing.JOptionPane import com.muwire.core.Core import com.muwire.core.Persona import com.muwire.core.download.UIDownloadEvent +import com.muwire.core.filefeeds.Feed +import com.muwire.core.filefeeds.UIFeedConfigurationEvent import com.muwire.core.search.UIResultEvent import com.muwire.core.trust.TrustEvent import com.muwire.core.trust.TrustLevel @@ -109,7 +111,16 @@ class SearchTabController { @ControllerAction void subscribe() { + def sender = view.selectedSender() + if (sender == null) + return + + Feed feed = new Feed(sender) + // TODO: defaults + feed.setUpdateInterval(60 * 1000) + core.eventBus.publish(new UIFeedConfigurationEvent(feed : feed, newFeed: true)) + mvcGroup.parentGroup.view.showFeedsWindow.call() } @ControllerAction diff --git a/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy b/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy index 2ebcc217..2259fc84 100644 --- a/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy +++ b/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy @@ -30,6 +30,7 @@ import com.muwire.core.download.Downloader import com.muwire.core.filecert.CertificateCreatedEvent import com.muwire.core.filefeeds.FeedFetchEvent import com.muwire.core.filefeeds.FeedLoadedEvent +import com.muwire.core.filefeeds.UIFeedConfigurationEvent import com.muwire.core.files.AllFilesLoadedEvent import com.muwire.core.files.DirectoryUnsharedEvent import com.muwire.core.files.DirectoryWatchedEvent @@ -230,6 +231,7 @@ class MainFrameModel { core.eventBus.register(CertificateCreatedEvent.class, this) core.eventBus.register(FeedLoadedEvent.class, this) core.eventBus.register(FeedFetchEvent.class, this) + core.eventBus.register(UIFeedConfigurationEvent.class, this) core.muOptions.watchedKeywords.each { core.eventBus.publish(new ContentControlEvent(term : it, regex: false, add: true)) @@ -681,4 +683,15 @@ class MainFrameModel { view.refreshFeeds() } } + + void onUIFeedConfigurationEvent(UIFeedConfigurationEvent e) { + if (!e.newFeed) + return + runInsideUIAsync { + if (feeds.contains(e.feed)) + return + feeds << e.feed + view.refreshFeeds() + } + } } \ No newline at end of file diff --git a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy index 6f237270..3375d8f5 100644 --- a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy +++ b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy @@ -444,7 +444,7 @@ class MainFrameView { scrollPane(constraints : BorderLayout.CENTER) { table(id : "feeds-table", autoCreateRowSorter : true, rowHeight : rowHeight) { tableModel(list : model.feeds) { - closureColumn(header : "Publisher", type : String, read : {it.getPublisher()}) + closureColumn(header : "Publisher", type : String, read : {it.getPublisher().getHumanReadableName()}) closureColumn(header : "Files", type : Integer, read : {model.core.feedManager.getFeedItems(it.getPublisher()).size()}) closureColumn(header : "Last Updated", type : Long, read : {it.getLastUpdated()}) closureColumn(header : "Status", type : String, read : {it.getStatus()}) From bb7385688cbb40b346a190c4b45ea51fd7a1cf8d Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 12:55:09 +0000 Subject: [PATCH 30/52] it always points to the innermost closure --- .../main/groovy/com/muwire/core/filefeeds/FeedClient.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/groovy/com/muwire/core/filefeeds/FeedClient.groovy b/core/src/main/groovy/com/muwire/core/filefeeds/FeedClient.groovy index 7defb831..069fac97 100644 --- a/core/src/main/groovy/com/muwire/core/filefeeds/FeedClient.groovy +++ b/core/src/main/groovy/com/muwire/core/filefeeds/FeedClient.groovy @@ -44,8 +44,8 @@ class FeedClient { } private void updateAnyFeeds() { - feedManager.getFeedsToUpdate().each { - feedFetcher.execute({updateFeed(it)} as Runnable) + feedManager.getFeedsToUpdate().each { feed -> + feedFetcher.execute({updateFeed(feed)} as Runnable) } } From 96d71ed08fb94f9f6a8d8ddcc081baa0649a3024 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 13:06:36 +0000 Subject: [PATCH 31/52] fix method name --- gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy index 3375d8f5..c98982c1 100644 --- a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy +++ b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy @@ -846,7 +846,7 @@ class MainFrameView { model.feedItems.addAll(items) def feedItemsTable = builder.getVariable("feed-items-table") - int selectedItemRow = feedItemsTable.selectedRow() + int selectedItemRow = feedItemsTable.getSelectedRow() feedItemsTable.model.fireTableDataChanged() if (selectedItemRow >= 0 && selectedItemRow < items.size()) feedItemsTable.selectionModel.setSelectionInterval(selectedItemRow, selectedItemRow) From 198c5b553850ca22be3aed211d2c06f178454910 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 13:06:47 +0000 Subject: [PATCH 32/52] fix json parsing --- .../com/muwire/core/filefeeds/FeedManager.groovy | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy b/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy index 52b3baaf..ebc47272 100644 --- a/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy +++ b/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy @@ -91,21 +91,19 @@ class FeedManager { private void loadItems() { def slurper = new JsonSlurper() - feeds.keySet().each { - File itemsFile = getItemsFile(feeds[it]) + feeds.keySet().each { persona -> + File itemsFile = getItemsFile(feeds[persona]) if (!itemsFile.exists()) return // no items yet? itemsFile.eachLine { line -> - def parsed = slurper.parse(line) - FeedItem item = FeedItems.objToFeedItem(parsed, it) - - Set items = feedItems.get(it) + def parsed = slurper.parseText(line) + FeedItem item = FeedItems.objToFeedItem(parsed, persona) + Set items = feedItems.get(persona) if (items == null) { items = new ConcurrentHashSet<>() - feedItems.put(it, items) + feedItems.put(persona, items) } items.add(item) - eventBus.publish(new FeedItemLoadedEvent(item : item)) } } From d724986ec6308af39d21d84e57852e626b083cf4 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 13:34:33 +0000 Subject: [PATCH 33/52] proper method name --- .../groovy/com/muwire/core/files/BasePersisterService.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/groovy/com/muwire/core/files/BasePersisterService.groovy b/core/src/main/groovy/com/muwire/core/files/BasePersisterService.groovy index 9adca00e..322556d5 100644 --- a/core/src/main/groovy/com/muwire/core/files/BasePersisterService.groovy +++ b/core/src/main/groovy/com/muwire/core/files/BasePersisterService.groovy @@ -115,7 +115,7 @@ abstract class BasePersisterService extends Service{ SharedFile sf = new SharedFile(file, ih.getRoot(), pieceSize) sf.setComment(json.comment) if (published) - sf.published(publishedTimestamp) + sf.publish(publishedTimestamp) if (json.downloaders != null) sf.getDownloaders().addAll(json.downloaders) if (json.searchers != null) { From 166b71f12879076cb325848251e23c74d783909f Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 13:47:31 +0000 Subject: [PATCH 34/52] fix NPE when logging is enabled --- .../main/groovy/com/muwire/core/files/FileHashedEvent.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/groovy/com/muwire/core/files/FileHashedEvent.groovy b/core/src/main/groovy/com/muwire/core/files/FileHashedEvent.groovy index cadaaa99..4db732e8 100644 --- a/core/src/main/groovy/com/muwire/core/files/FileHashedEvent.groovy +++ b/core/src/main/groovy/com/muwire/core/files/FileHashedEvent.groovy @@ -12,7 +12,7 @@ class FileHashedEvent extends Event { @Override public String toString() { - super.toString() + " sharedFile " + sharedFile?.file.getAbsolutePath() + " error: $error" + super.toString() + " sharedFile " + sharedFile?.file?.getAbsolutePath() + " error: $error" } } From 15430d6c0374a4832aac2e3944400ce23affdd5f Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 13:51:07 +0000 Subject: [PATCH 35/52] manual update and unsubscribe actions --- .../java/com/muwire/core/filefeeds/Feed.java | 1 + .../com/muwire/gui/MainFrameController.groovy | 18 ++++++++++++++++-- .../views/com/muwire/gui/MainFrameView.groovy | 3 +++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/muwire/core/filefeeds/Feed.java b/core/src/main/java/com/muwire/core/filefeeds/Feed.java index 8758265c..8dd61674 100644 --- a/core/src/main/java/com/muwire/core/filefeeds/Feed.java +++ b/core/src/main/java/com/muwire/core/filefeeds/Feed.java @@ -14,6 +14,7 @@ public class Feed { public Feed(Persona publisher) { this.publisher = publisher; + this.status = FeedFetchStatus.IDLE; } public int getUpdateInterval() { diff --git a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy index 83c4490d..4616c175 100644 --- a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy +++ b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy @@ -30,6 +30,9 @@ import com.muwire.core.download.UIDownloadCancelledEvent import com.muwire.core.download.UIDownloadPausedEvent import com.muwire.core.download.UIDownloadResumedEvent import com.muwire.core.filecert.UICreateCertificateEvent +import com.muwire.core.filefeeds.Feed +import com.muwire.core.filefeeds.UIFeedDeletedEvent +import com.muwire.core.filefeeds.UIFeedUpdateEvent import com.muwire.core.filefeeds.UIFilePublishedEvent import com.muwire.core.filefeeds.UIFileUnpublishedEvent import com.muwire.core.files.FileUnsharedEvent @@ -530,12 +533,23 @@ class MainFrameController { @ControllerAction void updateFileFeed() { - + Feed feed = view.selectedFeed() + if (feed == null) + return + model.core.eventBus.publish(new UIFeedUpdateEvent(host: feed.getPublisher())) } @ControllerAction void unsubscribeFileFeed() { - + Feed feed = view.selectedFeed() + if (feed == null) + return + model.core.eventBus.publish(new UIFeedDeletedEvent(host : feed.getPublisher())) + runInsideUIAsync { + model.feeds.remove(feed) + model.feedItems.clear() + view.refreshFeeds() + } } @ControllerAction diff --git a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy index c98982c1..558384f4 100644 --- a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy +++ b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy @@ -1332,6 +1332,9 @@ class MainFrameView { feedsTable.model.fireTableDataChanged() if (selectedFeed >= 0) feedsTable.selectionModel.setSelectionInterval(selectedFeed, selectedFeed) + + JTable feedItemsTable = builder.getVariable("feed-items-table") + feedItemsTable.model.fireTableDataChanged() } Feed selectedFeed() { From 8f710e68c24dec284df84e4657c0b8ba710a51bd Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 15:06:42 +0000 Subject: [PATCH 36/52] download feed item action --- .../main/groovy/com/muwire/core/Core.groovy | 2 ++ .../core/download/DownloadManager.groovy | 33 +++++++++++++------ .../filefeeds/UIDownloadFeedItemEvent.groovy | 9 +++++ .../java/com/muwire/core/filefeeds/Feed.java | 9 +++++ .../com/muwire/gui/MainFrameController.groovy | 12 ++++++- 5 files changed, 54 insertions(+), 11 deletions(-) create mode 100644 core/src/main/groovy/com/muwire/core/filefeeds/UIDownloadFeedItemEvent.groovy diff --git a/core/src/main/groovy/com/muwire/core/Core.groovy b/core/src/main/groovy/com/muwire/core/Core.groovy index 3b7a83f6..f225deec 100644 --- a/core/src/main/groovy/com/muwire/core/Core.groovy +++ b/core/src/main/groovy/com/muwire/core/Core.groovy @@ -36,6 +36,7 @@ import com.muwire.core.filefeeds.FeedClient import com.muwire.core.filefeeds.FeedFetchEvent import com.muwire.core.filefeeds.FeedItemFetchedEvent import com.muwire.core.filefeeds.FeedManager +import com.muwire.core.filefeeds.UIDownloadFeedItemEvent import com.muwire.core.filefeeds.UIFilePublishedEvent import com.muwire.core.filefeeds.UIFeedConfigurationEvent import com.muwire.core.filefeeds.UIFeedDeletedEvent @@ -348,6 +349,7 @@ public class Core { log.info("initializing download manager") downloadManager = new DownloadManager(eventBus, trustService, meshManager, props, i2pConnector, home, me) eventBus.register(UIDownloadEvent.class, downloadManager) + eventBus.register(UIDownloadFeedItemEvent.class, downloadManager) eventBus.register(UILoadedEvent.class, downloadManager) eventBus.register(FileDownloadedEvent.class, downloadManager) eventBus.register(UIDownloadCancelledEvent.class, downloadManager) diff --git a/core/src/main/groovy/com/muwire/core/download/DownloadManager.groovy b/core/src/main/groovy/com/muwire/core/download/DownloadManager.groovy index e9ac7c76..6638914f 100644 --- a/core/src/main/groovy/com/muwire/core/download/DownloadManager.groovy +++ b/core/src/main/groovy/com/muwire/core/download/DownloadManager.groovy @@ -1,6 +1,7 @@ package com.muwire.core.download import com.muwire.core.connection.I2PConnector +import com.muwire.core.filefeeds.UIDownloadFeedItemEvent import com.muwire.core.files.FileDownloadedEvent import com.muwire.core.files.FileHasher import com.muwire.core.mesh.Mesh @@ -62,11 +63,6 @@ public class DownloadManager { public void onUIDownloadEvent(UIDownloadEvent e) { - - File incompletes = muSettings.incompleteLocation - if (incompletes == null) - incompletes = new File(home, "incompletes") - incompletes.mkdirs() def size = e.result[0].size def infohash = e.result[0].infohash @@ -79,12 +75,29 @@ public class DownloadManager { destinations.addAll(e.sources) destinations.remove(me.destination) - Pieces pieces = getPieces(infohash, size, pieceSize, e.sequential) + doDownload(infohash, e.target, size, pieceSize, e.sequential, destinations) - def downloader = new Downloader(eventBus, this, me, e.target, size, - infohash, pieceSize, connector, destinations, - incompletes, pieces) - downloaders.put(infohash, downloader) + } + + public void onUIDownloadFeedItemEvent(UIDownloadFeedItemEvent e) { + Set singleSource = new HashSet<>() + singleSource.add(e.item.getPublisher().getDestination()) + doDownload(e.item.getInfoHash(), e.target, e.item.getSize(), e.item.getPieceSize(), + e.sequential, singleSource) + } + + private void doDownload(InfoHash infoHash, File target, long size, int pieceSize, + boolean sequential, Set destinations) { + File incompletes = muSettings.incompleteLocation + if (incompletes == null) + incompletes = new File(home, "incompletes") + incompletes.mkdirs() + + Pieces pieces = getPieces(infoHash, size, pieceSize, sequential) + def downloader = new Downloader(eventBus, this, me, target, size, + infoHash, pieceSize, connector, destinations, + incompletes, pieces) + downloaders.put(infoHash, downloader) persistDownloaders() executor.execute({downloader.download()} as Runnable) eventBus.publish(new DownloadStartedEvent(downloader : downloader)) diff --git a/core/src/main/groovy/com/muwire/core/filefeeds/UIDownloadFeedItemEvent.groovy b/core/src/main/groovy/com/muwire/core/filefeeds/UIDownloadFeedItemEvent.groovy new file mode 100644 index 00000000..06ba9b4a --- /dev/null +++ b/core/src/main/groovy/com/muwire/core/filefeeds/UIDownloadFeedItemEvent.groovy @@ -0,0 +1,9 @@ +package com.muwire.core.filefeeds + +import com.muwire.core.Event + +class UIDownloadFeedItemEvent extends Event { + FeedItem item + File target + boolean sequential +} diff --git a/core/src/main/java/com/muwire/core/filefeeds/Feed.java b/core/src/main/java/com/muwire/core/filefeeds/Feed.java index 8dd61674..36588d21 100644 --- a/core/src/main/java/com/muwire/core/filefeeds/Feed.java +++ b/core/src/main/java/com/muwire/core/filefeeds/Feed.java @@ -10,6 +10,7 @@ public class Feed { private long lastUpdated; private int itemsToKeep; private boolean autoDownload; + private boolean sequential; private FeedFetchStatus status; public Feed(Persona publisher) { @@ -60,4 +61,12 @@ public class Feed { public FeedFetchStatus getStatus() { return status; } + + public void setSequential(boolean sequential) { + this.sequential = sequential; + } + + public boolean isSequential() { + return sequential; + } } diff --git a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy index 4616c175..882bbf1b 100644 --- a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy +++ b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy @@ -31,6 +31,8 @@ import com.muwire.core.download.UIDownloadPausedEvent import com.muwire.core.download.UIDownloadResumedEvent import com.muwire.core.filecert.UICreateCertificateEvent import com.muwire.core.filefeeds.Feed +import com.muwire.core.filefeeds.FeedItem +import com.muwire.core.filefeeds.UIDownloadFeedItemEvent import com.muwire.core.filefeeds.UIFeedDeletedEvent import com.muwire.core.filefeeds.UIFeedUpdateEvent import com.muwire.core.filefeeds.UIFilePublishedEvent @@ -559,7 +561,15 @@ class MainFrameController { @ControllerAction void downloadFeedItem() { - + List items = view.selectedFeedItems() + if (items == null || items.isEmpty()) + return + Feed f = model.core.getFeedManager().getFeed(items.get(0).getPublisher()) + items.each { + File target = new File(application.context.get("muwire-settings").downloadLocation, it.getName()) + model.core.eventBus.publish(new UIDownloadFeedItemEvent(item : it, target : target, sequential : f.isSequential())) + } + view.showDownloadsWindow.call() } @ControllerAction From c3d0dce281ddbc14e32212aedc6f38aed8f53d2b Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 16:05:32 +0000 Subject: [PATCH 37/52] store last update attempt and do not retry active feeds --- .../muwire/core/filefeeds/FeedClient.groovy | 3 ++- .../muwire/core/filefeeds/FeedManager.groovy | 20 +++++++++++++------ .../java/com/muwire/core/filefeeds/Feed.java | 9 +++++++++ 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/core/src/main/groovy/com/muwire/core/filefeeds/FeedClient.groovy b/core/src/main/groovy/com/muwire/core/filefeeds/FeedClient.groovy index 069fac97..a1978a57 100644 --- a/core/src/main/groovy/com/muwire/core/filefeeds/FeedClient.groovy +++ b/core/src/main/groovy/com/muwire/core/filefeeds/FeedClient.groovy @@ -1,6 +1,6 @@ package com.muwire.core.filefeeds -import java.lang.System.Logger.Level +import java.util.logging.Level import java.nio.charset.StandardCharsets import java.util.concurrent.Executor import java.util.concurrent.ExecutorService @@ -64,6 +64,7 @@ class FeedClient { Endpoint endpoint = null try { eventBus.publish(new FeedFetchEvent(host : feed.getPublisher(), status : FeedFetchStatus.CONNECTING)) + feed.setLastUpdateAttempt(System.currentTimeMillis()) endpoint = connector.connect(feed.getPublisher().getDestination()) OutputStream os = endpoint.getOutputStream() os.write("FEED\r\n".getBytes(StandardCharsets.US_ASCII)) diff --git a/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy b/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy index ebc47272..de465650 100644 --- a/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy +++ b/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy @@ -54,7 +54,8 @@ class FeedManager { public List getFeedsToUpdate() { long now = System.currentTimeMillis() feeds.values().stream(). - filter({Feed f -> f.getLastUpdated() + f.getUpdateInterval() <= now}) + filter({Feed f -> !f.getStatus().isActive()}). + filter({Feed f -> f.getLastUpdateAttempt() + f.getUpdateInterval() <= now}) .collect(Collectors.toList()) } @@ -78,8 +79,10 @@ class FeedManager { Feed feed = new Feed(publisher) feed.setUpdateInterval(parsed.updateInterval) feed.setLastUpdated(parsed.lastUpdated) + feed.setLastUpdateAttempt(parsed.lastUpdateAttempt) feed.setItemsToKeep(parsed.itemsToKeep) feed.setAutoDownload(parsed.autoDownload) + feed.setSequential(parsed.sequential) feed.setStatus(FeedFetchStatus.IDLE) @@ -122,18 +125,21 @@ class FeedManager { Feed feed = feeds.get(e.host) if (feed == null) { - log.severe("Finished fetching non-existent feed " + e.host.getHumanReadableName()) + log.severe("Fetching non-existent feed " + e.host.getHumanReadableName()) return } feed.setStatus(e.status) - if (e.status != FeedFetchStatus.FINISHED) + if (e.status.isActive()) return - feed.setStatus(FeedFetchStatus.IDLE) - feed.setLastUpdated(e.getTimestamp()) - // save feed items, then save feed + if (e.status == FeedFetchStatus.FINISHED) { + feed.setStatus(FeedFetchStatus.IDLE) + feed.setLastUpdated(e.getTimestamp()) + } + // save feed items, then save feed. This will save partial fetches too + // which is ok because the items are stored in a Set persister.submit({saveFeedItems(e.host)} as Runnable) persister.submit({saveFeedMetadata(feed)} as Runnable) } @@ -192,6 +198,8 @@ class FeedManager { json.lastUpdated = feed.getLastUpdated() json.updateInterval = feed.getUpdateInterval() json.autoDownload = feed.isAutoDownload() + json.sequential = feed.isSequential() + json.lastUpdateAttempt = feed.getLastUpdateAttempt() json = JsonOutput.toJson(json) writer.println(json) } diff --git a/core/src/main/java/com/muwire/core/filefeeds/Feed.java b/core/src/main/java/com/muwire/core/filefeeds/Feed.java index 36588d21..e42894f6 100644 --- a/core/src/main/java/com/muwire/core/filefeeds/Feed.java +++ b/core/src/main/java/com/muwire/core/filefeeds/Feed.java @@ -8,6 +8,7 @@ public class Feed { private int updateInterval; private long lastUpdated; + private volatile long lastUpdateAttempt; private int itemsToKeep; private boolean autoDownload; private boolean sequential; @@ -69,4 +70,12 @@ public class Feed { public boolean isSequential() { return sequential; } + + public void setLastUpdateAttempt(long lastUpdateAttempt) { + this.lastUpdateAttempt = lastUpdateAttempt; + } + + public long getLastUpdateAttempt() { + return lastUpdateAttempt; + } } From 313358136377acaf615a3f8efe7e26c1b346607a Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 16:35:02 +0000 Subject: [PATCH 38/52] view comment functionality --- .../com/muwire/gui/MainFrameController.groovy | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy index 882bbf1b..37c035f2 100644 --- a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy +++ b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy @@ -574,7 +574,17 @@ class MainFrameController { @ControllerAction void viewFeedItemComment() { + List items = view.selectedFeedItems() + if (items == null || items.size() != 1) + return + FeedItem item = items.get(0) + String groupId = Base64.encode(item.getInfoHash().getRoot()) + Map params = new HashMap<>() + params['text'] = DataUtil.readi18nString(Base64.decode(item.getComment())) + params['name'] = item.getName() + + mvcGroup.createMVCGroup("show-comment", groupId, params) } @ControllerAction From a272a459283bdc0738c780dce71bef7ee92c5f99 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 16:41:31 +0000 Subject: [PATCH 39/52] persist the right number of feed items --- .../groovy/com/muwire/core/filefeeds/FeedManager.groovy | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy b/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy index de465650..d6dbdbcb 100644 --- a/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy +++ b/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy @@ -167,15 +167,18 @@ class FeedManager { if (feed == null) { log.severe("Persisting items for non-existing feed " + publisher.getHumanReadableName()) return - } - + } + + if (feed.getItemsToKeep() == 0) + return + List list = new ArrayList<>(set) if (list.size() > feed.getItemsToKeep()) { log.info("will persist ${feed.getItemsToKeep()}/${list.size()} items") list.sort({l, r -> Long.compare(r.getTimestamp(), l.getTimestamp()) } as Comparator) - list = list[0..feed.getItemsToKeep()] + list = list[0..feed.getItemsToKeep() - 1] } From cbb1de046b8b5cb9d7a83566df8d49a3b6cd86c4 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 16:53:28 +0000 Subject: [PATCH 40/52] fetch certificates functionality --- .../controllers/com/muwire/gui/BrowseController.groovy | 4 +++- .../com/muwire/gui/FetchCertificatesController.groovy | 2 +- .../com/muwire/gui/MainFrameController.groovy | 10 ++++++++++ .../com/muwire/gui/SearchTabController.groovy | 4 +++- .../com/muwire/gui/FetchCertificatesModel.groovy | 7 ++++++- .../views/com/muwire/gui/FetchCertificatesView.groovy | 2 +- 6 files changed, 24 insertions(+), 5 deletions(-) diff --git a/gui/griffon-app/controllers/com/muwire/gui/BrowseController.groovy b/gui/griffon-app/controllers/com/muwire/gui/BrowseController.groovy index fff00f5c..8c98d6c4 100644 --- a/gui/griffon-app/controllers/com/muwire/gui/BrowseController.groovy +++ b/gui/griffon-app/controllers/com/muwire/gui/BrowseController.groovy @@ -113,7 +113,9 @@ class BrowseController { return def params = [:] - params['result'] = result + params['host'] = result.getSender() + params['infoHash'] = result.getInfohash() + params['name'] = result.getName() params['core'] = core mvcGroup.createMVCGroup("fetch-certificates", params) } diff --git a/gui/griffon-app/controllers/com/muwire/gui/FetchCertificatesController.groovy b/gui/griffon-app/controllers/com/muwire/gui/FetchCertificatesController.groovy index 0caadd57..b66b7207 100644 --- a/gui/griffon-app/controllers/com/muwire/gui/FetchCertificatesController.groovy +++ b/gui/griffon-app/controllers/com/muwire/gui/FetchCertificatesController.groovy @@ -28,7 +28,7 @@ class FetchCertificatesController { core.eventBus.with { register(CertificateFetchEvent.class, this) register(CertificateFetchedEvent.class, this) - publish(new UIFetchCertificatesEvent(host : model.result.sender, infoHash : model.result.infohash)) + publish(new UIFetchCertificatesEvent(host : model.host, infoHash : model.infoHash)) } } diff --git a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy index 37c035f2..7bb11080 100644 --- a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy +++ b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy @@ -589,7 +589,17 @@ class MainFrameController { @ControllerAction void viewFeedItemCertificates() { + List items = view.selectedFeedItems() + if (items == null || items.size() != 1) + return + FeedItem item = items.get(0) + def params = [:] + params['core'] = core + params['host'] = item.getPublisher() + params['infoHash'] = item.getInfoHash() + params['name'] = item.getName() + mvcGroup.createMVCGroup("fetch-certificates", params) } void startChat(Persona p) { diff --git a/gui/griffon-app/controllers/com/muwire/gui/SearchTabController.groovy b/gui/griffon-app/controllers/com/muwire/gui/SearchTabController.groovy index cc7f07d0..2a1dd036 100644 --- a/gui/griffon-app/controllers/com/muwire/gui/SearchTabController.groovy +++ b/gui/griffon-app/controllers/com/muwire/gui/SearchTabController.groovy @@ -155,7 +155,9 @@ class SearchTabController { return def params = [:] - params['result'] = event + params['host'] = event.getSender() + params['infoHash'] = event.getInfohash() + params['name'] = event.getName() params['core'] = core mvcGroup.createMVCGroup("fetch-certificates", params) } diff --git a/gui/griffon-app/models/com/muwire/gui/FetchCertificatesModel.groovy b/gui/griffon-app/models/com/muwire/gui/FetchCertificatesModel.groovy index 3a7630e0..98f22deb 100644 --- a/gui/griffon-app/models/com/muwire/gui/FetchCertificatesModel.groovy +++ b/gui/griffon-app/models/com/muwire/gui/FetchCertificatesModel.groovy @@ -1,6 +1,9 @@ package com.muwire.gui +import com.muwire.core.InfoHash +import com.muwire.core.Persona import com.muwire.core.filecert.CertificateFetchStatus +import com.muwire.core.filefeeds.FeedItem import com.muwire.core.search.UIResultEvent import griffon.core.artifact.GriffonModel @@ -9,7 +12,9 @@ import griffon.metadata.ArtifactProviderFor @ArtifactProviderFor(GriffonModel) class FetchCertificatesModel { - UIResultEvent result + Persona host + InfoHash infoHash + String name @Observable CertificateFetchStatus status @Observable int totalCertificates diff --git a/gui/griffon-app/views/com/muwire/gui/FetchCertificatesView.groovy b/gui/griffon-app/views/com/muwire/gui/FetchCertificatesView.groovy index f2dcbdb9..e0f6689d 100644 --- a/gui/griffon-app/views/com/muwire/gui/FetchCertificatesView.groovy +++ b/gui/griffon-app/views/com/muwire/gui/FetchCertificatesView.groovy @@ -38,7 +38,7 @@ class FetchCertificatesView { void initUI() { int rowHeight = application.context.get("row-height") mainFrame = application.windowManager.findWindow("main-frame") - dialog = new JDialog(mainFrame, model.result.name, true) + dialog = new JDialog(mainFrame, model.name, true) dialog.setResizable(true) p = builder.panel { From 2882c73876c0c52dc6624e2df4ea3a4df0136978 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 17:05:44 +0000 Subject: [PATCH 41/52] enable button when switching to chat window --- gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy | 1 + 1 file changed, 1 insertion(+) diff --git a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy index 558384f4..3908d7de 100644 --- a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy +++ b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy @@ -1192,6 +1192,7 @@ class MainFrameView { model.downloadsPaneButtonEnabled = true model.uploadsPaneButtonEnabled = true model.monitorPaneButtonEnabled = true + model.feedsPaneButtonEnabled = true model.trustPaneButtonEnabled = true model.chatPaneButtonEnabled = true chatNotificator.mainWindowDeactivated() From fc393619d8ac47e09f12f4588488cd89ec26bf32 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 17:47:08 +0000 Subject: [PATCH 42/52] options for feeds --- .../com/muwire/core/MuWireSettings.groovy | 24 +++++++++++++ .../com/muwire/gui/OptionsController.groovy | 31 ++++++++++++++++ .../models/com/muwire/gui/OptionsModel.groovy | 17 +++++++++ .../views/com/muwire/gui/OptionsView.groovy | 36 +++++++++++++++++++ 4 files changed, 108 insertions(+) diff --git a/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy b/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy index 073d79a5..03d2b154 100644 --- a/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy +++ b/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy @@ -31,8 +31,16 @@ class MuWireSettings { boolean shareHiddenFiles boolean searchComments boolean browseFiles + boolean fileFeed boolean advertiseFeed + boolean autoPublishSharedFiles + boolean defaultFeedAutoDownload + int defaultFeedUpdateInterval + int defaultFeedItemsToKeep + boolean defaultFeedSequential + + boolean startChatServer int maxChatConnections boolean advertiseChat @@ -84,8 +92,16 @@ class MuWireSettings { outBw = Integer.valueOf(props.getProperty("outBw","128")) searchComments = Boolean.valueOf(props.getProperty("searchComments","true")) browseFiles = Boolean.valueOf(props.getProperty("browseFiles","true")) + + // feed settings fileFeed = Boolean.valueOf(props.getProperty("fileFeed","true")) advertiseFeed = Boolean.valueOf(props.getProperty("advertiseFeed","true")) + autoPublishSharedFiles = Boolean.valueOf(props.getProperty("autoPublishSharedFiles", "false")) + defaultFeedAutoDownload = Boolean.valueOf(props.getProperty("defaultFeedAutoDownload", "false")) + defaultFeedItemsToKeep = Integer.valueOf(props.getProperty("defaultFeedItemsToKeep", "1000")) + defaultFeedSequential = Boolean.valueOf(props.getProperty("defaultFeedSequential", "false")) + defaultFeedUpdateInterval = Integer.valueOf(props.getProperty("defaultFeedUpdateInterval", "60")) + speedSmoothSeconds = Integer.valueOf(props.getProperty("speedSmoothSeconds","60")) totalUploadSlots = Integer.valueOf(props.getProperty("totalUploadSlots","-1")) uploadSlotsPerUser = Integer.valueOf(props.getProperty("uploadSlotsPerUser","-1")) @@ -141,8 +157,16 @@ class MuWireSettings { props.setProperty("outBw", String.valueOf(outBw)) props.setProperty("searchComments", String.valueOf(searchComments)) props.setProperty("browseFiles", String.valueOf(browseFiles)) + + // feed settings props.setProperty("fileFeed", String.valueOf(fileFeed)) props.setProperty("advertiseFeed", String.valueOf(advertiseFeed)) + props.setProperty("autoPublishSharedFiles", String.valueOf(autoPublishSharedFiles)) + props.setProperty("defaultFeedAutoDownload", String.valueOf(defaultFeedAutoDownload)) + props.setProperty("defaultFeedItemsToKeep", String.valueOf(defaultFeedItemsToKeep)) + props.setProperty("defaultFeedSequential", String.valueOf(defaultFeedSequential)) + props.setProperty("defaultFeedUpdateInterval", String.valueOf(defaultFeedUpdateInterval)) + props.setProperty("speedSmoothSeconds", String.valueOf(speedSmoothSeconds)) props.setProperty("totalUploadSlots", String.valueOf(totalUploadSlots)) props.setProperty("uploadSlotsPerUser", String.valueOf(uploadSlotsPerUser)) diff --git a/gui/griffon-app/controllers/com/muwire/gui/OptionsController.groovy b/gui/griffon-app/controllers/com/muwire/gui/OptionsController.groovy index 17b854a9..2d5ce460 100644 --- a/gui/griffon-app/controllers/com/muwire/gui/OptionsController.groovy +++ b/gui/griffon-app/controllers/com/muwire/gui/OptionsController.groovy @@ -122,7 +122,38 @@ class OptionsController { model.outBw = text settings.outBw = Integer.valueOf(text) } + + // feed saving + + boolean fileFeed = view.fileFeedCheckbox.model.isSelected() + model.fileFeed = fileFeed + settings.fileFeed = fileFeed + + boolean advertiseFeed = view.advertiseFeedCheckbox.model.isSelected() + model.advertiseFeed = advertiseFeed + settings.advertiseFeed = advertiseFeed + + boolean autoPublishSharedFiles = view.autoPublishSharedFilesCheckbox.model.isSelected() + model.autoPublishSharedFiles = autoPublishSharedFiles + settings.autoPublishSharedFiles = autoPublishSharedFiles + + boolean defaultFeedAutoDownload = view.defaultFeedAutoDownloadCheckbox.model.isSelected() + model.defaultFeedAutoDownload = defaultFeedAutoDownload + settings.defaultFeedAutoDownload = defaultFeedAutoDownload + + boolean defaultFeedSequential = view.defaultFeedSequentialCheckbox.model.isSelected() + model.defaultFeedSequential = defaultFeedSequential + settings.defaultFeedSequential = defaultFeedSequential + + String defaultFeedItemsToKeep = view.defaultFeedItemsToKeepField.text + model.defaultFeedItemsToKeep = defaultFeedItemsToKeep + settings.defaultFeedItemsToKeep = Integer.parseInt(defaultFeedItemsToKeep) + + String defaultFeedUpdateInterval = view.defaultFeedUpdateIntervalField.text + model.defaultFeedUpdateInterval = defaultFeedUpdateInterval + settings.defaultFeedUpdateInterval = Integer.parseInt(defaultFeedUpdateInterval) + // trust saving boolean onlyTrusted = view.allowUntrustedCheckbox.model.isSelected() model.onlyTrusted = onlyTrusted diff --git a/gui/griffon-app/models/com/muwire/gui/OptionsModel.groovy b/gui/griffon-app/models/com/muwire/gui/OptionsModel.groovy index 6e0c6eaf..b0f62bc6 100644 --- a/gui/griffon-app/models/com/muwire/gui/OptionsModel.groovy +++ b/gui/griffon-app/models/com/muwire/gui/OptionsModel.groovy @@ -50,6 +50,15 @@ class OptionsModel { @Observable String inBw @Observable String outBw + // feed options + @Observable boolean fileFeed + @Observable boolean advertiseFeed + @Observable boolean autoPublishSharedFiles + @Observable boolean defaultFeedAutoDownload + @Observable String defaultFeedItemsToKeep + @Observable boolean defaultFeedSequential + @Observable String defaultFeedUpdateInterval + // trust options @Observable boolean onlyTrusted @Observable boolean searchExtraHop @@ -105,6 +114,14 @@ class OptionsModel { inBw = String.valueOf(settings.inBw) outBw = String.valueOf(settings.outBw) } + + fileFeed = settings.fileFeed + advertiseFeed = settings.advertiseFeed + autoPublishSharedFiles = settings.autoPublishSharedFiles + defaultFeedAutoDownload = settings.defaultFeedAutoDownload + defaultFeedItemsToKeep = String.valueOf(settings.defaultFeedItemsToKeep) + defaultFeedSequential = settings.defaultFeedSequential + defaultFeedUpdateInterval = String.valueOf(settings.defaultFeedUpdateInterval) onlyTrusted = !settings.allowUntrusted() searchExtraHop = settings.searchExtraHop diff --git a/gui/griffon-app/views/com/muwire/gui/OptionsView.groovy b/gui/griffon-app/views/com/muwire/gui/OptionsView.groovy index 3ab4464a..5054232a 100644 --- a/gui/griffon-app/views/com/muwire/gui/OptionsView.groovy +++ b/gui/griffon-app/views/com/muwire/gui/OptionsView.groovy @@ -32,6 +32,7 @@ class OptionsView { def i def u def bandwidth + def feed def trust def chat @@ -66,6 +67,14 @@ class OptionsView { def inBwField def outBwField + + def fileFeedCheckbox + def advertiseFeedCheckbox + def autoPublishSharedFilesCheckbox + def defaultFeedAutoDownloadCheckbox + def defaultFeedItemsToKeepField + def defaultFeedSequentialCheckbox + def defaultFeedUpdateIntervalField def allowUntrustedCheckbox def searchExtraHopCheckbox @@ -257,6 +266,32 @@ class OptionsView { } panel(constraints : gbc(gridx: 0, gridy: 1, weighty: 100)) } + feed = builder.panel { + gridBagLayout() + panel (border : titledBorder(title : "General Feed Settings", border : etchedBorder(), titlePosition : TitledBorder.TOP), + constraints : gbc(gridx : 0, gridy : 0, fill : GridBagConstraints.HORIZONTAL, weightx: 100)) { + gridBagLayout() + label(text : "Enable file feed", constraints : gbc(gridx: 0, gridy : 0, anchor : GridBagConstraints.LINE_START, weightx: 100)) + fileFeedCheckbox = checkBox(selected : bind {model.fileFeed}, constraints : gbc(gridx: 1, gridy : 0, anchor : GridBagConstraints.LINE_END)) + label(text : "Advertise feed in search results", constraints : gbc(gridx: 0, gridy : 1, anchor : GridBagConstraints.LINE_START, weightx: 100)) + advertiseFeedCheckbox = checkBox(selected : bind {model.advertiseFeed}, constraints : gbc(gridx: 1, gridy : 1, anchor : GridBagConstraints.LINE_END)) + label(text : "Automatically publish shared files", constraints : gbc(gridx: 0, gridy : 2, anchor : GridBagConstraints.LINE_START, weightx: 100)) + autoPublishSharedFilesCheckbox = checkBox(selected : bind {model.autoPublishSharedFiles}, constraints : gbc(gridx: 1, gridy : 2, anchor : GridBagConstraints.LINE_END)) + } + panel (border : titledBorder(title : "Default Settings For New Feeds", border : etchedBorder(), titlePosition : TitledBorder.TOP), + constraints : gbc(gridx : 0, gridy : 1, fill : GridBagConstraints.HORIZONTAL, weightx: 100)) { + gridBagLayout() + label(text : "Automatically download files from feed", constraints : gbc(gridx: 0, gridy : 0, anchor : GridBagConstraints.LINE_START, weightx: 100)) + defaultFeedAutoDownloadCheckbox = checkBox(selected : bind {model.defaultFeedAutoDownload}, constraints : gbc(gridx: 1, gridy : 0, anchor : GridBagConstraints.LINE_END)) + label(text : "Download files from feed sequentially", constraints : gbc(gridx: 0, gridy : 1, anchor : GridBagConstraints.LINE_START, weightx: 100)) + defaultFeedSequentialCheckbox = checkBox(selected : bind {model.defaultFeedSequential}, constraints : gbc(gridx: 1, gridy : 1, anchor : GridBagConstraints.LINE_END)) + label(text : "Feed items to store on disk (-1 means unlimited)", constraints : gbc(gridx: 0, gridy : 2, anchor : GridBagConstraints.LINE_START, weightx: 100)) + defaultFeedItemsToKeepField = textField(text : bind {model.defaultFeedItemsToKeep}, constraints:gbc(gridx :1, gridy:2, anchor : GridBagConstraints.LINE_END)) + label(text : "Feed refresh frequency in minutes", constraints : gbc(gridx: 0, gridy : 3, anchor : GridBagConstraints.LINE_START, weightx: 100)) + defaultFeedUpdateIntervalField = textField(text : bind {model.defaultFeedUpdateInterval}, constraints:gbc(gridx :1, gridy:3, anchor : GridBagConstraints.LINE_END)) + } + panel(constraints : gbc(gridx: 0, gridy : 2, weighty: 100)) + } trust = builder.panel { gridBagLayout() panel (border : titledBorder(title : "Trust Settings", border : etchedBorder(), titlePosition : TitledBorder.TOP), @@ -311,6 +346,7 @@ class OptionsView { if (core.router != null) { tabbedPane.addTab("Bandwidth", bandwidth) } + tabbedPane.addTab("Feed", feed) tabbedPane.addTab("Trust", trust) tabbedPane.addTab("Chat", chat) From ff952890bca16d8a892af3a474eebc406e45b02d Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 17:51:14 +0000 Subject: [PATCH 43/52] populate new feeds from defaults --- .../controllers/com/muwire/gui/SearchTabController.groovy | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gui/griffon-app/controllers/com/muwire/gui/SearchTabController.groovy b/gui/griffon-app/controllers/com/muwire/gui/SearchTabController.groovy index 2a1dd036..c5027cae 100644 --- a/gui/griffon-app/controllers/com/muwire/gui/SearchTabController.groovy +++ b/gui/griffon-app/controllers/com/muwire/gui/SearchTabController.groovy @@ -116,8 +116,10 @@ class SearchTabController { return Feed feed = new Feed(sender) - // TODO: defaults - feed.setUpdateInterval(60 * 1000) + feed.setAutoDownload(core.muOptions.defaultFeedAutoDownload) + feed.setSequential(core.muOptions.defaultFeedSequential) + feed.setItemsToKeep(core.muOptions.defaultFeedItemsToKeep) + feed.setUpdateInterval(core.muOptions.defaultFeedUpdateInterval * 60 * 1000) core.eventBus.publish(new UIFeedConfigurationEvent(feed : feed, newFeed: true)) mvcGroup.parentGroup.view.showFeedsWindow.call() From 2bb07ff7b5accf5e4056c4b33fd912fe75122647 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 17:53:17 +0000 Subject: [PATCH 44/52] do not trim feed items if setting is negative --- .../main/groovy/com/muwire/core/filefeeds/FeedManager.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy b/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy index d6dbdbcb..f0b6dcb5 100644 --- a/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy +++ b/core/src/main/groovy/com/muwire/core/filefeeds/FeedManager.groovy @@ -173,7 +173,7 @@ class FeedManager { return List list = new ArrayList<>(set) - if (list.size() > feed.getItemsToKeep()) { + if (feed.getItemsToKeep() > 0 && list.size() > feed.getItemsToKeep()) { log.info("will persist ${feed.getItemsToKeep()}/${list.size()} items") list.sort({l, r -> Long.compare(r.getTimestamp(), l.getTimestamp()) From c082e25c81fe5b58a876b3882d2a55d4b31856b4 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 18:48:20 +0000 Subject: [PATCH 45/52] individual feed configuration panel --- gui/griffon-app/conf/Config.groovy | 5 ++ .../gui/FeedConfigurationController.groovy | 36 +++++++++ .../com/muwire/gui/MainFrameController.groovy | 7 ++ .../muwire/gui/FeedConfigurationModel.groovy | 26 +++++++ .../muwire/gui/FeedConfigurationView.groovy | 73 +++++++++++++++++++ 5 files changed, 147 insertions(+) create mode 100644 gui/griffon-app/controllers/com/muwire/gui/FeedConfigurationController.groovy create mode 100644 gui/griffon-app/models/com/muwire/gui/FeedConfigurationModel.groovy create mode 100644 gui/griffon-app/views/com/muwire/gui/FeedConfigurationView.groovy diff --git a/gui/griffon-app/conf/Config.groovy b/gui/griffon-app/conf/Config.groovy index 53af343d..a7f1479c 100644 --- a/gui/griffon-app/conf/Config.groovy +++ b/gui/griffon-app/conf/Config.groovy @@ -126,4 +126,9 @@ mvcGroups { view = 'com.muwire.gui.ChatMonitorView' controller = 'com.muwire.gui.ChatMonitorController' } + 'feed-configuration' { + model = 'com.muwire.gui.FeedConfigurationModel' + view = 'com.muwire.gui.FeedConfigurationView' + controller = 'com.muwire.gui.FeedConfigurationController' + } } diff --git a/gui/griffon-app/controllers/com/muwire/gui/FeedConfigurationController.groovy b/gui/griffon-app/controllers/com/muwire/gui/FeedConfigurationController.groovy new file mode 100644 index 00000000..4cd712f3 --- /dev/null +++ b/gui/griffon-app/controllers/com/muwire/gui/FeedConfigurationController.groovy @@ -0,0 +1,36 @@ +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 + +import com.muwire.core.filefeeds.UIFeedConfigurationEvent + +@ArtifactProviderFor(GriffonController) +class FeedConfigurationController { + @MVCMember @Nonnull + FeedConfigurationModel model + @MVCMember @Nonnull + FeedConfigurationView view + + @ControllerAction + void save() { + + model.feed.setAutoDownload(view.autoDownloadCheckbox.model.isSelected()) + model.feed.setSequential(view.sequentialCheckbox.model.isSelected()) + model.feed.setItemsToKeep(Integer.parseInt(view.itemsToKeepField.text)) + model.feed.setUpdateInterval(Integer.parseInt(view.updateIntervalField.text) * 60000) + + model.core.eventBus.publish(new UIFeedConfigurationEvent(feed : model.feed)) + + cancel() + } + + @ControllerAction + void cancel() { + view.dialog.setVisible(false) + mvcGroup.destroy() + } +} \ No newline at end of file diff --git a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy index 7bb11080..def93f26 100644 --- a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy +++ b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy @@ -556,7 +556,14 @@ class MainFrameController { @ControllerAction void configureFileFeed() { + Feed feed = view.selectedFeed() + if (feed == null) + return + def params = [:] + params['core'] = core + params['feed'] = feed + mvcGroup.createMVCGroup("feed-configuration", params) } @ControllerAction diff --git a/gui/griffon-app/models/com/muwire/gui/FeedConfigurationModel.groovy b/gui/griffon-app/models/com/muwire/gui/FeedConfigurationModel.groovy new file mode 100644 index 00000000..a49b252d --- /dev/null +++ b/gui/griffon-app/models/com/muwire/gui/FeedConfigurationModel.groovy @@ -0,0 +1,26 @@ +package com.muwire.gui + +import com.muwire.core.Core +import com.muwire.core.filefeeds.Feed + +import griffon.core.artifact.GriffonModel +import griffon.transform.Observable +import griffon.metadata.ArtifactProviderFor + +@ArtifactProviderFor(GriffonModel) +class FeedConfigurationModel { + Core core + Feed feed + + @Observable boolean autoDownload + @Observable boolean sequential + @Observable int updateInterval + @Observable int itemsToKeep + + void mvcGroupInit(Map args) { + autoDownload = feed.isAutoDownload() + sequential = feed.isSequential() + updateInterval = feed.getUpdateInterval() / 60000 + itemsToKeep = feed.getItemsToKeep() + } +} \ No newline at end of file diff --git a/gui/griffon-app/views/com/muwire/gui/FeedConfigurationView.groovy b/gui/griffon-app/views/com/muwire/gui/FeedConfigurationView.groovy new file mode 100644 index 00000000..9cc23d02 --- /dev/null +++ b/gui/griffon-app/views/com/muwire/gui/FeedConfigurationView.groovy @@ -0,0 +1,73 @@ +package com.muwire.gui + +import griffon.core.artifact.GriffonView +import griffon.inject.MVCMember +import griffon.metadata.ArtifactProviderFor + +import javax.swing.JDialog +import javax.swing.SwingConstants + +import java.awt.BorderLayout +import java.awt.GridBagConstraints +import java.awt.event.WindowAdapter +import java.awt.event.WindowEvent + +import javax.annotation.Nonnull + +@ArtifactProviderFor(GriffonView) +class FeedConfigurationView { + @MVCMember @Nonnull + FactoryBuilderSupport builder + @MVCMember @Nonnull + FeedConfigurationModel model + + def dialog + def p + def mainFrame + + def autoDownloadCheckbox + def sequentialCheckbox + def itemsToKeepField + def updateIntervalField + + void initUI() { + mainFrame = application.windowManager.findWindow("main-frame") + dialog = new JDialog(mainFrame, "Feed Configuration", true) + dialog.setResizable(false) + + p = builder.panel { + borderLayout() + panel (constraints : BorderLayout.NORTH) { + label("Configuration for feed " + model.feed.getPublisher().getHumanReadableName()) + } + panel (constraints : BorderLayout.CENTER) { + gridBagLayout() + label(text : "Automatically download files from feed", constraints : gbc(gridx: 0, gridy : 0, anchor : GridBagConstraints.LINE_START, weightx: 100)) + autoDownloadCheckbox = checkBox(selected : bind {model.autoDownload}, constraints : gbc(gridx: 1, gridy : 0, anchor : GridBagConstraints.LINE_END)) + label(text : "Download files from feed sequentially", constraints : gbc(gridx: 0, gridy : 1, anchor : GridBagConstraints.LINE_START, weightx: 100)) + sequentialCheckbox = checkBox(selected : bind {model.sequential}, constraints : gbc(gridx: 1, gridy : 1, anchor : GridBagConstraints.LINE_END)) + label(text : "Feed items to store on disk (-1 means unlimited)", constraints : gbc(gridx: 0, gridy : 2, anchor : GridBagConstraints.LINE_START, weightx: 100)) + itemsToKeepField = textField(text : bind {model.itemsToKeep}, constraints:gbc(gridx :1, gridy:2, anchor : GridBagConstraints.LINE_END)) + label(text : "Feed refresh frequency in minutes", constraints : gbc(gridx: 0, gridy : 3, anchor : GridBagConstraints.LINE_START, weightx: 100)) + updateIntervalField = textField(text : bind {model.updateInterval}, constraints:gbc(gridx :1, gridy:3, anchor : GridBagConstraints.LINE_END)) + } + panel (constraints : BorderLayout.SOUTH) { + button(text : "Save", saveAction) + button(text : "Cancel", cancelAction) + } + } + } + + void mvcGroupInit(Map args) { + dialog.getContentPane().add(p) + dialog.pack() + dialog.setLocationRelativeTo(mainFrame) + dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE) + dialog.addWindowListener(new WindowAdapter() { + public void windowClosed(WindowEvent e) { + mvcGroup.destroy() + } + }) + dialog.show() + } +} \ No newline at end of file From f202fa34f3b51f2b6605eb4cc1ee1d204bfa8325 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 19:12:49 +0000 Subject: [PATCH 46/52] auto-publish shared files functionality --- .../com/muwire/core/files/PersisterFolderService.groovy | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/main/groovy/com/muwire/core/files/PersisterFolderService.groovy b/core/src/main/groovy/com/muwire/core/files/PersisterFolderService.groovy index 220f5361..ef2814a6 100644 --- a/core/src/main/groovy/com/muwire/core/files/PersisterFolderService.groovy +++ b/core/src/main/groovy/com/muwire/core/files/PersisterFolderService.groovy @@ -59,11 +59,15 @@ class PersisterFolderService extends BasePersisterService { } void onFileHashedEvent(FileHashedEvent hashedEvent) { + if (core.getMuOptions().getAutoPublishSharedFiles() && hashedEvent.sharedFile != null) + hashedEvent.sharedFile.publish(System.currentTimeMillis()) persistFile(hashedEvent.sharedFile, hashedEvent.infoHash) } void onFileDownloadedEvent(FileDownloadedEvent downloadedEvent) { if (core.getMuOptions().getShareDownloadedFiles()) { + if (core.getMuOptions().getAutoPublishSharedFiles()) + downloadedEvent.downloadedFile.publish(System.currentTimeMillis()) persistFile(downloadedEvent.downloadedFile, downloadedEvent.infoHash) } } From 69810d720338e0cfd1cc91ed05cc7147c90a4859 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 19:35:39 +0000 Subject: [PATCH 47/52] fix variable name --- gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy index 3908d7de..1a0b07b2 100644 --- a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy +++ b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy @@ -1344,7 +1344,7 @@ class MainFrameView { if (row < 0) return null if (lastFeedsSortEvent != null) - row = table.rowSorter.convertRowIndexToModel(row) + row = feedsTable.rowSorter.convertRowIndexToModel(row) model.feeds[row] } From 95cb7f32141ac3667a1d57f236c87fcce9bd62c8 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 19:48:36 +0000 Subject: [PATCH 48/52] auto-download feed items functionality --- .../com/muwire/gui/MainFrameModel.groovy | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy b/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy index 2259fc84..68057ab0 100644 --- a/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy +++ b/gui/griffon-app/models/com/muwire/gui/MainFrameModel.groovy @@ -28,8 +28,11 @@ import com.muwire.core.content.ContentControlEvent import com.muwire.core.download.DownloadStartedEvent import com.muwire.core.download.Downloader import com.muwire.core.filecert.CertificateCreatedEvent +import com.muwire.core.filefeeds.Feed import com.muwire.core.filefeeds.FeedFetchEvent +import com.muwire.core.filefeeds.FeedItemFetchedEvent import com.muwire.core.filefeeds.FeedLoadedEvent +import com.muwire.core.filefeeds.UIDownloadFeedItemEvent import com.muwire.core.filefeeds.UIFeedConfigurationEvent import com.muwire.core.files.AllFilesLoadedEvent import com.muwire.core.files.DirectoryUnsharedEvent @@ -64,6 +67,7 @@ import griffon.transform.FXObservable import griffon.transform.Observable import net.i2p.data.Base64 import net.i2p.data.Destination +import net.i2p.util.ConcurrentHashSet import griffon.metadata.ArtifactProviderFor @ArtifactProviderFor(GriffonModel) @@ -139,7 +143,7 @@ class MainFrameModel { @Observable Downloader downloader - private final Set downloadInfoHashes = new HashSet<>() + private final Set downloadInfoHashes = new ConcurrentHashSet<>() @Observable volatile Core core @@ -231,6 +235,7 @@ class MainFrameModel { core.eventBus.register(CertificateCreatedEvent.class, this) core.eventBus.register(FeedLoadedEvent.class, this) core.eventBus.register(FeedFetchEvent.class, this) + core.eventBus.register(FeedItemFetchedEvent.class, this) core.eventBus.register(UIFeedConfigurationEvent.class, this) core.muOptions.watchedKeywords.each { @@ -694,4 +699,17 @@ class MainFrameModel { view.refreshFeeds() } } + + void onFeedItemFetchedEvent(FeedItemFetchedEvent e) { + Feed feed = core.feedManager.getFeed(e.item.getPublisher()) + if (feed == null || !feed.isAutoDownload()) + return + if (!canDownload(e.item.getInfoHash())) + return + if (core.fileManager.isShared(e.item.getInfoHash())) + return + + File target = new File(core.getMuOptions().getDownloadLocation(), e.item.getName()) + core.eventBus.publish(new UIDownloadFeedItemEvent(item : e.item, target : target, sequential : feed.isSequential())) + } } \ No newline at end of file From 0408349c07ffcd505e5fcd5aeab095c7b06ee22c Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 20:04:28 +0000 Subject: [PATCH 49/52] size columns --- .../views/com/muwire/gui/MainFrameView.groovy | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy index 1a0b07b2..8fbcf4c6 100644 --- a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy +++ b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy @@ -444,10 +444,10 @@ class MainFrameView { scrollPane(constraints : BorderLayout.CENTER) { table(id : "feeds-table", autoCreateRowSorter : true, rowHeight : rowHeight) { tableModel(list : model.feeds) { - closureColumn(header : "Publisher", type : String, read : {it.getPublisher().getHumanReadableName()}) - closureColumn(header : "Files", type : Integer, read : {model.core.feedManager.getFeedItems(it.getPublisher()).size()}) + closureColumn(header : "Publisher", preferredWidth: 350, type : String, read : {it.getPublisher().getHumanReadableName()}) + closureColumn(header : "Files", preferredWidth: 10, type : Integer, read : {model.core.feedManager.getFeedItems(it.getPublisher()).size()}) closureColumn(header : "Last Updated", type : Long, read : {it.getLastUpdated()}) - closureColumn(header : "Status", type : String, read : {it.getStatus()}) + closureColumn(header : "Status", preferredWidth: 10, type : String, read : {it.getStatus()}) } } } @@ -465,11 +465,11 @@ class MainFrameView { scrollPane(constraints : BorderLayout.CENTER) { table(id : "feed-items-table", autoCreateRowSorter : true, rowHeight : rowHeight) { tableModel(list : model.feedItems) { - closureColumn(header : "Name", type : String, read : {it.getName()}) - closureColumn(header : "Size", type : Long, read : {it.getSize()}) - closureColumn(header : "Comment", type : Boolean, read : {it.getComment() != null}) - closureColumn(header : "Certificates", type : Integer, read : {it.getCertificates()}) - closureColumn(header : "Downloaded", type : Boolean, read : { + closureColumn(header : "Name", preferredWidth: 350, type : String, read : {it.getName()}) + closureColumn(header : "Size", preferredWidth: 10, type : Long, read : {it.getSize()}) + closureColumn(header : "Comment", preferredWidth: 10, type : Boolean, read : {it.getComment() != null}) + closureColumn(header : "Certificates", preferredWidth: 10, type : Integer, read : {it.getCertificates()}) + closureColumn(header : "Downloaded", preferredWidth: 10, type : Boolean, read : { InfoHash ih = it.getInfoHash() model.core.fileManager.isShared(ih) }) From 2ba81ccc84d7d589413aa200e027f870d275ada6 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 20:50:00 +0000 Subject: [PATCH 50/52] context menu for feeds table --- .../views/com/muwire/gui/MainFrameView.groovy | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy index 8fbcf4c6..c7343eca 100644 --- a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy +++ b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy @@ -851,6 +851,19 @@ class MainFrameView { if (selectedItemRow >= 0 && selectedItemRow < items.size()) feedItemsTable.selectionModel.setSelectionInterval(selectedItemRow, selectedItemRow) }) + feedsTable.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if(e.isPopupTrigger() || e.getButton() == MouseEvent.BUTTON3) + showFeedsPopupMenu(e) + } + @Override + public void mouseReleased(MouseEvent e) { + if(e.isPopupTrigger() || e.getButton() == MouseEvent.BUTTON3) + showFeedsPopupMenu(e) + } + }) + // feed items table def feedItemsTable = builder.getVariable("feed-items-table") @@ -1142,6 +1155,29 @@ class MainFrameView { showPopupMenu(menu, e) } + void showFeedsPopupMenu(MouseEvent e) { + Feed feed = selectedFeed() + if (feed == null) + return + // TODO: finish + JPopupMenu menu = new JPopupMenu() + if (model.updateFileFeedButtonEnabled) { + JMenuItem update = new JMenuItem("Update") + update.addActionListener({mvcGroup.controller.updateFileFeed()}) + menu.add(update) + } + + JMenuItem unsubscribe = new JMenuItem("Unsubscribe") + unsubscribe.addActionListener({mvcGroup.controller.unsubscribeFileFeed()}) + menu.add(unsubscribe) + + JMenuItem configure = new JMenuItem("Configure") + configure.addActionListener({mvcGroup.controller.configureFileFeed()}) + menu.add(configure) + + showPopupMenu(menu,e) + } + def selectedUploader() { def uploadsTable = builder.getVariable("uploads-table") int selectedRow = uploadsTable.getSelectedRow() From 38a027c308f5e8dd13588e68e6610a83488af0f5 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 21:15:59 +0000 Subject: [PATCH 51/52] context menu on the feed items table --- .../com/muwire/gui/MainFrameController.groovy | 2 + .../views/com/muwire/gui/MainFrameView.groovy | 44 ++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy index def93f26..b5a578ca 100644 --- a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy +++ b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy @@ -573,6 +573,8 @@ class MainFrameController { return Feed f = model.core.getFeedManager().getFeed(items.get(0).getPublisher()) items.each { + if (!model.canDownload(it.getInfoHash())) + return File target = new File(application.context.get("muwire-settings").downloadLocation, it.getName()) model.core.eventBus.publish(new UIDownloadFeedItemEvent(item : it, target : target, sequential : f.isSequential())) } diff --git a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy index c7343eca..adb891b8 100644 --- a/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy +++ b/gui/griffon-app/views/com/muwire/gui/MainFrameView.groovy @@ -892,6 +892,25 @@ class MainFrameView { model.viewFeedItemCertificatesButtonEnabled = item.getCertificates() > 0 } }) + feedItemsTable.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + List selectedItems = selectedFeedItems() + if (e.isPopupTrigger() || e.getButton() == MouseEvent.BUTTON3) + showFeedItemsPopupMenu(e) + else if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2 && + selectedItems != null && selectedItems.size() == 1 && + model.canDownload(selectedItems.get(0).getInfoHash())) { + mvcGroup.controller.downloadFeedItem() + } + } + + @Override + public void mouseReleased(MouseEvent e) { + if (e.isPopupTrigger() || e.getButton() == MouseEvent.BUTTON3) + showFeedItemsPopupMenu(e) + } + }) // subscription table def subscriptionTable = builder.getVariable("subscription-table") @@ -1159,7 +1178,6 @@ class MainFrameView { Feed feed = selectedFeed() if (feed == null) return - // TODO: finish JPopupMenu menu = new JPopupMenu() if (model.updateFileFeedButtonEnabled) { JMenuItem update = new JMenuItem("Update") @@ -1178,6 +1196,30 @@ class MainFrameView { showPopupMenu(menu,e) } + void showFeedItemsPopupMenu(MouseEvent e) { + List items = selectedFeedItems() + if (items == null || items.isEmpty()) + return + // TODO: finish + JPopupMenu menu = new JPopupMenu() + if (model.downloadFeedItemButtonEnabled) { + JMenuItem download = new JMenuItem("Download") + download.addActionListener({mvcGroup.controller.downloadFeedItem()}) + menu.add(download) + } + if (model.viewFeedItemCommentButtonEnabled) { + JMenuItem viewComment = new JMenuItem("View Comment") + viewComment.addActionListener({mvcGroup.controller.viewFeedItemComment()}) + menu.add(viewComment) + } + if (model.viewFeedItemCertificatesButtonEnabled) { + JMenuItem viewCertificates = new JMenuItem("View Certificates") + viewCertificates.addActionListener({mvcGroup.controller.viewFeedItemCertificates()}) + menu.add(viewCertificates) + } + showPopupMenu(menu, e) + } + def selectedUploader() { def uploadsTable = builder.getVariable("uploads-table") int selectedRow = uploadsTable.getSelectedRow() From 9a44603d2fa662f5275ab318bdd0929f1a81b7e7 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Tue, 10 Mar 2020 21:30:27 +0000 Subject: [PATCH 52/52] prevent duplicate feed subscriptions --- .../views/com/muwire/gui/SearchTabView.groovy | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/gui/griffon-app/views/com/muwire/gui/SearchTabView.groovy b/gui/griffon-app/views/com/muwire/gui/SearchTabView.groovy index 6dbdaf89..ee3d40f7 100644 --- a/gui/griffon-app/views/com/muwire/gui/SearchTabView.groovy +++ b/gui/griffon-app/views/com/muwire/gui/SearchTabView.groovy @@ -345,7 +345,8 @@ class SearchTabView { Persona sender = model.senders[row] model.browseActionEnabled = model.sendersBucket[sender].first().browse model.chatActionEnabled = model.sendersBucket[sender].first().chat - model.subscribeActionEnabled = model.sendersBucket[sender].first().feed + model.subscribeActionEnabled = model.sendersBucket[sender].first().feed && + model.core.feedManager.getFeed(sender) == null model.trustButtonsEnabled = true model.results.clear() model.results.addAll(model.sendersBucket[sender]) @@ -406,12 +407,13 @@ class SearchTabView { model.viewCommentActionEnabled = false return } - model.browseActionEnabled = model.senders2[row].browse - model.chatActionEnabled = model.senders2[row].chat - model.subscribeActionEnabled = model.senders2[row].feed + UIResultEvent e = model.senders2[row] + model.browseActionEnabled = e.browse + model.chatActionEnabled = e.chat + model.subscribeActionEnabled = e.feed && model.core.feedManager.getFeed(e.getSender()) == null model.trustButtonsEnabled = true - model.viewCommentActionEnabled = model.senders2[row].comment != null - model.viewCertificatesActionEnabled = model.senders2[row].certificates > 0 + model.viewCommentActionEnabled = e.comment != null + model.viewCertificatesActionEnabled = e.certificates > 0 }) if (settings.groupByFile)