Skip to content

Commit

Permalink
Check for compatible updates in plugin (rojo-rbx#832)
Browse files Browse the repository at this point in the history
  • Loading branch information
boatbomber authored Aug 5, 2024
1 parent 3fa1d6b commit 4b5db4e
Show file tree
Hide file tree
Showing 8 changed files with 273 additions and 51 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
* Added popout diff visualizer for table properties like Attributes and Tags ([#834])
* Updated Theme to use Studio colors ([#838])
* Improved patch visualizer UX ([#883])
* Added update notifications for newer compatible versions in the Studio plugin. ([#832])
* Added experimental setting for Auto Connect in playtests ([#840])
* Improved settings UI ([#886])
* `Open Scripts Externally` option can now be changed while syncing ([#911])
Expand Down Expand Up @@ -75,6 +76,7 @@
**All** sync rules are reset between project files, so they must be specified in each one when nesting them. This is to ensure that nothing can break other projects by changing how files are synced!

[#813]: https://github.com/rojo-rbx/rojo/pull/813
[#832]: https://github.com/rojo-rbx/rojo/pull/832
[#834]: https://github.com/rojo-rbx/rojo/pull/834
[#838]: https://github.com/rojo-rbx/rojo/pull/838
[#840]: https://github.com/rojo-rbx/rojo/pull/840
Expand Down
27 changes: 3 additions & 24 deletions plugin/src/App/StatusPages/Connected.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ local Packages = Rojo.Packages

local Roact = require(Packages.Roact)

local timeUtil = require(Plugin.timeUtil)
local Theme = require(Plugin.App.Theme)
local Assets = require(Plugin.Assets)
local PatchSet = require(Plugin.PatchSet)
Expand All @@ -20,28 +21,6 @@ local TableDiffVisualizer = require(Plugin.App.Components.TableDiffVisualizer)

local e = Roact.createElement

local AGE_UNITS = {
{ 31556909, "y" },
{ 2629743, "mon" },
{ 604800, "w" },
{ 86400, "d" },
{ 3600, "h" },
{ 60, "m" },
}
function timeSinceText(elapsed: number): string
local ageText = string.format("%ds", elapsed)

for _, UnitData in ipairs(AGE_UNITS) do
local UnitSeconds, UnitName = UnitData[1], UnitData[2]
if elapsed > UnitSeconds then
ageText = elapsed // UnitSeconds .. UnitName
break
end
end

return ageText
end

local ChangesViewer = Roact.Component:extend("ChangesViewer")

function ChangesViewer:init()
Expand Down Expand Up @@ -287,7 +266,7 @@ function ConnectedPage:getChangeInfoText()
if patchData == nil then
return ""
end
return timeSinceText(DateTime.now().UnixTimestamp - patchData.timestamp)
return timeUtil.elapsedToText(DateTime.now().UnixTimestamp - patchData.timestamp)
end

function ConnectedPage:startChangeInfoTextUpdater()
Expand All @@ -303,7 +282,7 @@ function ConnectedPage:startChangeInfoTextUpdater()
local updateInterval = 1

-- Update timestamp text as frequently as currently needed
for _, UnitData in ipairs(AGE_UNITS) do
for _, UnitData in ipairs(timeUtil.AGE_UNITS) do
local UnitSeconds = UnitData[1]
if elapsed > UnitSeconds then
updateInterval = UnitSeconds
Expand Down
19 changes: 19 additions & 0 deletions plugin/src/App/StatusPages/Settings/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,25 @@ function SettingsPage:render()
layoutOrder = layoutIncrement(),
}),

CheckForUpdates = e(Setting, {
id = "checkForUpdates",
name = "Check For Updates",
description = "Notify about newer compatible Rojo releases",
transparency = self.props.transparency,
layoutOrder = layoutIncrement(),
}),

CheckForPreleases = e(Setting, {
id = "checkForPrereleases",
name = "Include Prerelease Updates",
description = "Include prereleases when checking for updates",
transparency = self.props.transparency,
layoutOrder = layoutIncrement(),
visible = if string.find(debug.traceback(), "\n[^\n]-user_.-$") == nil
then false -- Must be a local install to allow prerelease checks
else Settings:getBinding("checkForUpdates"),
}),

AutoConnectPlaytestServer = e(Setting, {
id = "autoConnectPlaytestServer",
name = "Auto Connect Playtest Server",
Expand Down
101 changes: 75 additions & 26 deletions plugin/src/App/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ local PatchTree = require(Plugin.PatchTree)
local preloadAssets = require(Plugin.preloadAssets)
local soundPlayer = require(Plugin.soundPlayer)
local ignorePlaceIds = require(Plugin.ignorePlaceIds)
local timeUtil = require(Plugin.timeUtil)
local Theme = require(script.Theme)

local Page = require(script.Page)
Expand Down Expand Up @@ -118,6 +119,13 @@ function App:init()
end)
end)

self.disconnectUpdatesCheckChanged = Settings:onChanged("checkForUpdates", function()
self:checkForUpdates()
end)
self.disconnectPrereleasesCheckChanged = Settings:onChanged("checkForPrereleases", function()
self:checkForUpdates()
end)

self:setState({
appStatus = AppStatus.NotConnected,
guiEnabled = false,
Expand All @@ -131,32 +139,35 @@ function App:init()
toolbarIcon = Assets.Images.PluginButton,
})

if
RunService:IsEdit()
and self.serveSession == nil
and Settings:get("syncReminder")
and self:getLastSyncTimestamp()
and (self:isSyncLockAvailable())
then
self:addNotification("You've previously synced this place. Would you like to reconnect?", 300, {
Connect = {
text = "Connect",
style = "Solid",
layoutOrder = 1,
onClick = function(notification)
notification:dismiss()
self:startSession()
end,
},
Dismiss = {
text = "Dismiss",
style = "Bordered",
layoutOrder = 2,
onClick = function(notification)
notification:dismiss()
end,
},
})
if RunService:IsEdit() then
self:checkForUpdates()

if
Settings:get("syncReminder")
and self.serveSession == nil
and self:getLastSyncTimestamp()
and (self:isSyncLockAvailable())
then
self:addNotification("You've previously synced this place. Would you like to reconnect?", 300, {
Connect = {
text = "Connect",
style = "Solid",
layoutOrder = 1,
onClick = function(notification)
notification:dismiss()
self:startSession()
end,
},
Dismiss = {
text = "Dismiss",
style = "Bordered",
layoutOrder = 2,
onClick = function(notification)
notification:dismiss()
end,
},
})
end
end

if self:isAutoConnectPlaytestServerAvailable() then
Expand All @@ -179,6 +190,10 @@ end
function App:willUnmount()
self.waypointConnection:Disconnect()
self.confirmationBindable:Destroy()

self.disconnectUpdatesCheckChanged()
self.disconnectPrereleasesCheckChanged()

self.autoConnectPlaytestServerListener()
self:clearRunningConnectionInfo()
end
Expand Down Expand Up @@ -225,6 +240,40 @@ function App:closeNotification(id: number)
})
end

function App:checkForUpdates()
if not Settings:get("checkForUpdates") then
return
end

local isLocalInstall = string.find(debug.traceback(), "\n[^\n]-user_.-$") ~= nil
local latestCompatibleVersion = Version.retrieveLatestCompatible({
version = Config.version,
includePrereleases = isLocalInstall and Settings:get("checkForPrereleases"),
})
if not latestCompatibleVersion then
return
end

self:addNotification(
string.format(
"A newer compatible version of Rojo, %s, was published %s! Go to the Rojo releases page to learn more.",
Version.display(latestCompatibleVersion.version),
timeUtil.elapsedToText(DateTime.now().UnixTimestamp - latestCompatibleVersion.publishedUnixTimestamp)
),
500,
{
Dismiss = {
text = "Dismiss",
style = "Bordered",
layoutOrder = 2,
onClick = function(notification)
notification:dismiss()
end,
},
}
)
end

function App:getPriorEndpoint()
local priorEndpoints = Settings:get("priorEndpoints")
if not priorEndpoints then
Expand Down
2 changes: 2 additions & 0 deletions plugin/src/Settings.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ local defaultSettings = {
twoWaySync = false,
showNotifications = true,
syncReminder = true,
checkForUpdates = true,
checkForPrereleases = false,
autoConnectPlaytestServer = false,
confirmationBehavior = "Initial",
largeChangesConfirmationThreshold = 5,
Expand Down
107 changes: 106 additions & 1 deletion plugin/src/Version.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
local Packages = script.Parent.Parent.Packages
local Http = require(Packages.Http)
local Promise = require(Packages.Promise)

local function compare(a, b)
if a > b then
return 1
Expand Down Expand Up @@ -30,7 +34,48 @@ function Version.compare(a, b)
return minor
end

return revision
if revision ~= 0 then
return revision
end

local aPrerelease = if a[4] == "" then nil else a[4]
local bPrerelease = if b[4] == "" then nil else b[4]

-- If neither are prerelease, they are the same
if aPrerelease == nil and bPrerelease == nil then
return 0
end

-- If one is prerelease it is older
if aPrerelease ~= nil and bPrerelease == nil then
return -1
end
if aPrerelease == nil and bPrerelease ~= nil then
return 1
end

-- If they are both prereleases, compare those based on number
local aPrereleaseNumeric = string.match(aPrerelease, "(%d+).*$")
local bPrereleaseNumeric = string.match(bPrerelease, "(%d+).*$")

if aPrereleaseNumeric == nil or bPrereleaseNumeric == nil then
-- If one or both lack a number, comparing isn't meaningful
return 0
end
return compare(tonumber(aPrereleaseNumeric) or 0, tonumber(bPrereleaseNumeric) or 0)
end

function Version.parse(versionString: string)
local version = { string.match(versionString, "^v?(%d+)%.(%d+)%.(%d+)(.*)$") }
for i, v in version do
version[i] = tonumber(v) or v
end

if version[4] == "" then
version[4] = nil
end

return version
end

function Version.display(version)
Expand All @@ -43,4 +88,64 @@ function Version.display(version)
return output
end

function Version.retrieveLatestCompatible(options: {
version: { number },
includePrereleases: boolean?,
}): {
version: { number },
prerelease: boolean,
publishedUnixTimestamp: number,
}?
local success, releases = Http.get("https://api.github.com/repos/rojo-rbx/rojo/releases?per_page=10")
:andThen(function(response)
if response.code >= 400 then
local message = string.format("HTTP %s:\n%s", tostring(response.code), response.body)

return Promise.reject(message)
end

return response
end)
:andThen(Http.Response.json)
:await()

if success == false or type(releases) ~= "table" or next(releases) ~= 1 then
return nil
end

-- Iterate through releases, looking for the latest compatible version
local latestCompatible = nil
for _, release in releases do
-- Skip prereleases if they are not requested
if (not options.includePrereleases) and release.prerelease then
continue
end

local releaseVersion = Version.parse(release.tag_name)

-- Skip releases that are potentially incompatible
if releaseVersion[1] > options.version[1] then
continue
end

-- Skip releases that are older than the latest compatible version
if latestCompatible ~= nil and Version.compare(releaseVersion, latestCompatible.version) <= 0 then
continue
end

latestCompatible = {
version = releaseVersion,
prerelease = release.prerelease,
publishedUnixTimestamp = DateTime.fromIsoDate(release.published_at).UnixTimestamp,
}
end

-- Don't return anything if the latest found is not newer than the current version
if latestCompatible == nil or Version.compare(latestCompatible.version, options.version) <= 0 then
return nil
end

return latestCompatible
end

return Version
Loading

0 comments on commit 4b5db4e

Please sign in to comment.