From 986caf3a75fd58906bb88d7c7b886581bb39c4e9 Mon Sep 17 00:00:00 2001 From: Zlatin Balevsky Date: Mon, 3 Jun 2019 23:11:03 +0100 Subject: [PATCH] backend for checking updates --- .../main/groovy/com/muwire/core/Core.groovy | 10 +- .../com/muwire/core/MuWireSettings.groovy | 3 + .../muwire/core/hostcache/CacheClient.groovy | 2 +- .../core/update/UpdateAvailableEvent.groovy | 8 ++ .../muwire/core/update/UpdateClient.groovy | 132 ++++++++++++++++++ .../muwire/core/update/UpdateServers.groovy | 7 + gui/griffon-app/lifecycle/Ready.groovy | 7 +- .../com/muwire/update/UpdateServer.groovy | 2 +- 8 files changed, 166 insertions(+), 5 deletions(-) create mode 100644 core/src/main/groovy/com/muwire/core/update/UpdateAvailableEvent.groovy create mode 100644 core/src/main/groovy/com/muwire/core/update/UpdateClient.groovy create mode 100644 core/src/main/groovy/com/muwire/core/update/UpdateServers.groovy diff --git a/core/src/main/groovy/com/muwire/core/Core.groovy b/core/src/main/groovy/com/muwire/core/Core.groovy index 5abd5309..826ed9f1 100644 --- a/core/src/main/groovy/com/muwire/core/Core.groovy +++ b/core/src/main/groovy/com/muwire/core/Core.groovy @@ -32,6 +32,7 @@ import com.muwire.core.search.SearchEvent import com.muwire.core.search.SearchManager import com.muwire.core.trust.TrustEvent import com.muwire.core.trust.TrustService +import com.muwire.core.update.UpdateClient import com.muwire.core.upload.UploadManager import com.muwire.core.util.MuWireLogManager @@ -61,11 +62,12 @@ public class Core { private final HostCache hostCache private final ConnectionManager connectionManager private final CacheClient cacheClient + private final UpdateClient updateClient private final ConnectionAcceptor connectionAcceptor private final ConnectionEstablisher connectionEstablisher private final HasherService hasherService - public Core(MuWireSettings props, File home) { + public Core(MuWireSettings props, File home, String myVersion) { this.home = home log.info "Initializing I2P context" I2PAppContext.getGlobalContext().logManager() @@ -154,6 +156,9 @@ public class Core { log.info("initializing cache client") cacheClient = new CacheClient(eventBus,hostCache, connectionManager, i2pSession, props, 10000) + log.info("initializing update client") + updateClient = new UpdateClient(eventBus, i2pSession, myVersion, props) + log.info("initializing connector") I2PConnector i2pConnector = new I2PConnector(socketManager) @@ -197,6 +202,7 @@ public class Core { connectionAcceptor.start() connectionEstablisher.start() hostCache.waitForLoad() + updateClient.start() } public void shutdown() { @@ -227,7 +233,7 @@ public class Core { } } - Core core = new Core(props, home) + Core core = new Core(props, home, "0.0.6") core.startServices() // ... at the end, sleep or execute script diff --git a/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy b/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy index 90b46f3c..0636d5a1 100644 --- a/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy +++ b/core/src/main/groovy/com/muwire/core/MuWireSettings.groovy @@ -7,6 +7,7 @@ class MuWireSettings { final boolean isLeaf boolean allowUntrusted int downloadRetryInterval + int updateCheckInterval String nickname File downloadLocation String sharedFiles @@ -25,6 +26,7 @@ class MuWireSettings { System.getProperty("user.home"))) sharedFiles = props.getProperty("sharedFiles") downloadRetryInterval = Integer.parseInt(props.getProperty("downloadRetryInterval","15")) + updateCheckInterval = Integer.parseInt(props.getProperty("updateCheckInterval","36")) } void write(OutputStream out) throws IOException { @@ -35,6 +37,7 @@ class MuWireSettings { props.setProperty("nickname", nickname) props.setProperty("downloadLocation", downloadLocation.getAbsolutePath()) props.setProperty("downloadRetryInterval", String.valueOf(downloadRetryInterval)) + props.setProperty("updateCheckInterval", String.valueOf(updateCheckInterval)) if (sharedFiles != null) props.setProperty("sharedFiles", sharedFiles) props.store(out, "") diff --git a/core/src/main/groovy/com/muwire/core/hostcache/CacheClient.groovy b/core/src/main/groovy/com/muwire/core/hostcache/CacheClient.groovy index ba143280..64cb2ce1 100644 --- a/core/src/main/groovy/com/muwire/core/hostcache/CacheClient.groovy +++ b/core/src/main/groovy/com/muwire/core/hostcache/CacheClient.groovy @@ -65,7 +65,7 @@ class CacheClient { options.setSendLeaseSet(true) CacheServers.getCacheServers().each { log.info "Querying hostcache ${it.toBase32()}" - session.sendMessage(it, ping, 0, ping.length, I2PSession.PROTO_DATAGRAM, 0, 0, options) + session.sendMessage(it, ping, 0, ping.length, I2PSession.PROTO_DATAGRAM, 1, 0, options) } } diff --git a/core/src/main/groovy/com/muwire/core/update/UpdateAvailableEvent.groovy b/core/src/main/groovy/com/muwire/core/update/UpdateAvailableEvent.groovy new file mode 100644 index 00000000..a8a6aa04 --- /dev/null +++ b/core/src/main/groovy/com/muwire/core/update/UpdateAvailableEvent.groovy @@ -0,0 +1,8 @@ +package com.muwire.core.update + +import com.muwire.core.Event + +class UpdateAvailableEvent extends Event { + String version + String signer +} diff --git a/core/src/main/groovy/com/muwire/core/update/UpdateClient.groovy b/core/src/main/groovy/com/muwire/core/update/UpdateClient.groovy new file mode 100644 index 00000000..e6a66a29 --- /dev/null +++ b/core/src/main/groovy/com/muwire/core/update/UpdateClient.groovy @@ -0,0 +1,132 @@ +package com.muwire.core.update + +import java.util.logging.Level + +import com.muwire.core.EventBus +import com.muwire.core.MuWireSettings + +import groovy.json.JsonOutput +import groovy.json.JsonSlurper +import groovy.util.logging.Log +import net.i2p.client.I2PSession +import net.i2p.client.I2PSessionMuxedListener +import net.i2p.client.SendMessageOptions +import net.i2p.client.datagram.I2PDatagramDissector +import net.i2p.client.datagram.I2PDatagramMaker +import net.i2p.util.VersionComparator + +@Log +class UpdateClient { + final EventBus eventBus + final I2PSession session + final String myVersion + final MuWireSettings settings + + private final Timer timer + + private long lastUpdateCheckTime + + UpdateClient(EventBus eventBus, I2PSession session, String myVersion, MuWireSettings settings) { + this.eventBus = eventBus + this.session = session + this.myVersion = myVersion + this.settings = settings + timer = new Timer("update-client",true) + } + + void start() { + session.addMuxedSessionListener(new Listener(), I2PSession.PROTO_DATAGRAM, 2) + timer.schedule({checkUpdate()} as TimerTask, 30000, 60 * 60 * 1000) + } + + void stop() { + timer.cancel() + } + + private void checkUpdate() { + final long now = System.currentTimeMillis() + if (lastUpdateCheckTime > 0) { + if (now - lastUpdateCheckTime < settings.updateCheckInterval * 60 * 60 * 1000) + return + } + lastUpdateCheckTime = now + + log.info("checking for update") + + def ping = [version : 1, myVersion : myVersion] + ping = JsonOutput.toJson(ping) + def maker = new I2PDatagramMaker(session) + ping = maker.makeI2PDatagram(ping.bytes) + def options = new SendMessageOptions() + options.setSendLeaseSet(true) + session.sendMessage(UpdateServers.UPDATE_SERVER, ping, 0, ping.length, I2PSession.PROTO_DATAGRAM, 2, 0, options) + } + + class Listener implements I2PSessionMuxedListener { + + final JsonSlurper slurper = new JsonSlurper() + + @Override + public void messageAvailable(I2PSession session, int msgId, long size) { + } + + @Override + public void messageAvailable(I2PSession session, int msgId, long size, int proto, int fromport, int toport) { + if (proto != I2PSession.PROTO_DATAGRAM) { + log.warning "Received unexpected protocol $proto" + return + } + + def payload = session.receiveMessage(msgId) + def dissector = new I2PDatagramDissector() + try { + dissector.loadI2PDatagram(payload) + def sender = dissector.getSender() + if (sender != UpdateServers.UPDATE_SERVER) { + log.warning("received something not from update server " + sender.toBase32()) + return + } + + log.info("Received something from update server") + + payload = dissector.getPayload() + payload = slurper.parse(payload) + + if (payload.version == null) { + log.warning("version missing") + return + } + + if (payload.signer == null) { + log.warning("signer missing") + } + + if (VersionComparator.comp(myVersion, payload.version) >= 0) { + log.info("no new version available") + return + } + + log.info("new version $payload.version available, publishing event") + eventBus.publish(new UpdateAvailableEvent(version : payload.version, signer : payload.signer)) + + } catch (Exception e) { + log.log(Level.WARNING,"Invalid datagram",e) + } + } + + @Override + public void reportAbuse(I2PSession session, int severity) { + } + + @Override + public void disconnected(I2PSession session) { + log.severe("I2P session disconnected") + } + + @Override + public void errorOccurred(I2PSession session, String message, Throwable error) { + log.log(Level.SEVERE, message, error) + } + + } +} diff --git a/core/src/main/groovy/com/muwire/core/update/UpdateServers.groovy b/core/src/main/groovy/com/muwire/core/update/UpdateServers.groovy new file mode 100644 index 00000000..e71914b4 --- /dev/null +++ b/core/src/main/groovy/com/muwire/core/update/UpdateServers.groovy @@ -0,0 +1,7 @@ +package com.muwire.core.update + +import net.i2p.data.Destination + +class UpdateServers { + static final Destination UPDATE_SERVER = new Destination("pSWieSRB3czCl3Zz4WpKp4Z8tjv-05zbogRDS7SEnKcSdWOupVwjzQ92GsgQh1VqgoSRk1F8dpZOnHxxz5HFy9D7ri0uFdkMyXdSKoB7IgkkvCfTAyEmeaPwSYnurF3Zk7u286E7YG2rZkQZgJ77tow7ZS0mxFB7Z0Ti-VkZ9~GeGePW~howwNm4iSQACZA0DyTpI8iv5j4I0itPCQRgaGziob~Vfvjk49nd8N4jtaDGo9cEcafikVzQ2OgBgYWL6LRbrrItwuGqsDvITUHWaElUYIDhRQYUq8gYiUA6rwAJputfhFU0J7lIxFR9vVY7YzRvcFckfr0DNI4VQVVlPnRPkUxQa--BlldMaCIppWugjgKLwqiSiHywKpSMlBWgY2z1ry4ueEBo1WEP-mEf88wRk4cFQBCKtctCQnIG2GsnATqTl-VGUAsuzeNWZiFSwXiTy~gQ094yWx-K06fFZUDt4CMiLZVhGlixiInD~34FCRC9LVMtFcqiFB2M-Ql2AAAA") +} diff --git a/gui/griffon-app/lifecycle/Ready.groovy b/gui/griffon-app/lifecycle/Ready.groovy index cf37d440..2a21ec52 100644 --- a/gui/griffon-app/lifecycle/Ready.groovy +++ b/gui/griffon-app/lifecycle/Ready.groovy @@ -1,4 +1,5 @@ import griffon.core.GriffonApplication +import griffon.core.env.Metadata import groovy.util.logging.Log import org.codehaus.griffon.runtime.core.AbstractLifecycleHandler @@ -20,6 +21,9 @@ import java.util.logging.Level @Log class Ready extends AbstractLifecycleHandler { + + @Inject Metadata metadata + @Inject Ready(@Nonnull GriffonApplication application) { super(application) @@ -90,9 +94,10 @@ class Ready extends AbstractLifecycleHandler { props.write(it) } } + Core core try { - core = new Core(props, home) + core = new Core(props, home, metadata["application.version"]) } catch (Exception bad) { log.log(Level.SEVERE,"couldn't initialize core",bad) JOptionPane.showMessageDialog(null, "Couldn't connect to I2P router. Make sure I2P is running and restart MuWire", diff --git a/update-server/src/main/groovy/com/muwire/update/UpdateServer.groovy b/update-server/src/main/groovy/com/muwire/update/UpdateServer.groovy index b000c3ce..f1a40184 100644 --- a/update-server/src/main/groovy/com/muwire/update/UpdateServer.groovy +++ b/update-server/src/main/groovy/com/muwire/update/UpdateServer.groovy @@ -80,7 +80,7 @@ class UpdateServer { // I don't think we care about the payload at this point def maker = new I2PDatagramMaker(session) def response = maker.makeI2PDatagram(json.bytes) - session.sendMessage(sender, response, I2PSession.PROTO_DATAGRAM, 0, 0) + session.sendMessage(sender, response, I2PSession.PROTO_DATAGRAM, 0, 2) } catch (Exception e) { log.log(Level.WARNING, "exception responding to update request",e) }