025c7e74d554b0821afbdb3366a6527e6a26721c
[Sone.git] / src / main / kotlin / net / pterodactylus / sone / core / UpdateChecker.kt
1 /*
2  * Sone - UpdateChecker.java - Copyright © 2011–2019 David Roden
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 package net.pterodactylus.sone.core
19
20 import com.google.common.eventbus.*
21 import com.google.common.primitives.*
22 import com.google.inject.Inject
23 import freenet.keys.*
24 import net.pterodactylus.sone.core.event.*
25 import net.pterodactylus.sone.main.*
26 import net.pterodactylus.sone.utils.*
27 import net.pterodactylus.util.version.Version
28 import java.io.*
29 import java.util.*
30 import java.util.logging.*
31 import java.util.logging.Logger.*
32 import javax.inject.Singleton
33
34 /**
35  * Watches the official Sone homepage for new releases.
36  */
37 @Singleton
38 class UpdateChecker @Inject constructor(
39                 private val eventBus: EventBus,
40                 private val freenetInterface: FreenetInterface,
41                 private val currentRunningVersion: Version,
42                 pluginHomepage: PluginHomepage) {
43
44         private val logger: Logger = getLogger(UpdateChecker::class.java.name)
45
46         private val currentUri by lazy { FreenetURI(pluginHomepage.homepage) }
47
48         var latestEdition = SonePlugin.getLatestEdition()
49                 private set
50
51         var latestVersion: Version = currentRunningVersion
52                 private set
53
54         var latestVersionDate: Long = 0
55                 private set
56
57         fun hasLatestVersion() =
58                         latestVersion > currentRunningVersion
59
60         fun start() {
61                 freenetInterface.registerUsk(currentUri) { uri, edition, newKnownGood, newSlot ->
62                         logger.log(Level.FINEST, String.format("Found update for %s: %d, %s, %s", uri, edition, newKnownGood, newSlot))
63                         if (newKnownGood || newSlot) {
64                                 try {
65                                         freenetInterface.fetchUri(uri.setMetaString(arrayOf("sone.properties")))
66                                                         ?.onNull {
67                                                                 logger.log(Level.WARNING, String.format("Could not fetch properties of latest homepage: %s", uri))
68                                                         }?.fetchResult
69                                                         ?.asBucket()?.use { resultBucket ->
70                                                                 resultBucket.inputStream
71                                                                                 .let { parseProperties(it) }
72                                                                                 .let { extractCurrentVersion(it) }
73                                                                                 .onNull { logger.log(Level.INFO, "Invalid data parsed from properties.") }
74                                                                                 ?.takeIf { it.version > latestVersion }
75                                                                                 ?.also { updateVersionInformation(it, edition) }
76                                                                                 ?.also { logger.info { "Found new version: %s (%tc%s)".format(it.version, it.time, if (it.disruptive) ", disruptive" else "") } }
77                                                                                 ?.also { eventBus.post(UpdateFoundEvent(it.version, it.time, edition, it.disruptive)) }
78                                                         }
79                                 } catch (ioe1: IOException) {
80                                         logger.log(Level.WARNING, String.format("Could not parse sone.properties of %s!", uri), ioe1)
81                                 }
82                         }
83                 }
84         }
85
86         fun stop() {
87                 freenetInterface.unregisterUsk(currentUri)
88         }
89
90         private fun updateVersionInformation(versionInformation: VersionInformation, edition: Long) {
91                 latestVersion = versionInformation.version
92                 latestVersionDate = versionInformation.time
93                 latestEdition = edition
94         }
95
96         private fun parseProperties(propertiesInputStream: InputStream) =
97                         Properties().apply {
98                                 InputStreamReader(propertiesInputStream, "UTF-8").use { inputStreamReader ->
99                                         load(inputStreamReader)
100                                 }
101                         }
102
103         private fun extractCurrentVersion(properties: Properties) =
104                         properties.getProperty("CurrentVersion/Version")
105                                         ?.let { Version.parse(it) }
106                                         ?.let { version ->
107                                                 properties.getProperty("CurrentVersion/ReleaseTime")
108                                                                 ?.let { Longs.tryParse(it) }
109                                                                 ?.let { time ->
110                                                                         VersionInformation(version, time, disruptiveVersionBetweenCurrentAndFound(properties))
111                                                                 }
112                                         }
113
114         private fun disruptiveVersionBetweenCurrentAndFound(properties: Properties) =
115                         properties.stringPropertyNames()
116                                         .filter { it.startsWith("DisruptiveVersion/") }
117                                         .map { it.removePrefix("DisruptiveVersion/") }
118                                         .map { Version.parse(it) }
119                                         .any { it > currentRunningVersion }
120
121 }
122
123 private data class VersionInformation(val version: Version, val time: Long, val disruptive: Boolean)