upgrade alerts

The webapp will check twice a day, when the network is connected, to see if
it can download a distributon upgrade file. If a newer version is found,
display an upgrade alert.

This will need the autobuilders to set UPGRADE_LOCATION to the url
it can be downloaded from when building git-annex. Only builds with that
set need automatic upgrade alerts.

Currently, the upgrade page just requests the user manually download
and upgrade it. But, all the info is provided to do automated upgrades
in the future.

Note that urls used will need to all be https.

This commit was sponsored by Dirk Kraft.
This commit is contained in:
Joey Hess 2013-11-21 17:49:56 -04:00
parent ff2b0a9df6
commit e2f17e9da3
14 changed files with 191 additions and 3 deletions

View file

@ -28,6 +28,7 @@ import Assistant.Threads.ProblemFixer
import Assistant.Threads.MountWatcher import Assistant.Threads.MountWatcher
#endif #endif
import Assistant.Threads.NetWatcher import Assistant.Threads.NetWatcher
import Assistant.Threads.Upgrader
import Assistant.Threads.TransferScanner import Assistant.Threads.TransferScanner
import Assistant.Threads.TransferPoller import Assistant.Threads.TransferPoller
import Assistant.Threads.ConfigMonitor import Assistant.Threads.ConfigMonitor
@ -150,6 +151,7 @@ startDaemon assistant foreground startdelay cannotrun listenhost startbrowser =
, assist $ mountWatcherThread urlrenderer , assist $ mountWatcherThread urlrenderer
#endif #endif
, assist $ netWatcherThread , assist $ netWatcherThread
, assist $ upgraderThread urlrenderer
, assist $ netWatcherFallbackThread , assist $ netWatcherFallbackThread
, assist $ transferScannerThread urlrenderer , assist $ transferScannerThread urlrenderer
, assist $ cronnerThread urlrenderer , assist $ cronnerThread urlrenderer

View file

@ -215,6 +215,26 @@ notFsckedAlert mr button = Alert
, alertData = [] , alertData = []
} }
canUpgradeAlert :: AlertPriority -> AlertButton -> Alert
canUpgradeAlert priority button = Alert
{ alertHeader = Just $ fromString $ concat
[ if priority >= High
then "An important upgrade of git-annex is available!"
else "An upgrade of git-annex is available."
]
, alertIcon = Just UpgradeIcon
, alertPriority = priority
, alertButton = Just button
, alertClosable = True
, alertClass = Message
, alertMessageRender = renderData
, alertCounter = 0
, alertBlockDisplay = True
, alertName = Just UpgradeAlert
, alertCombiner = Just $ dataCombiner $ \_old new -> new
, alertData = []
}
brokenRepositoryAlert :: AlertButton -> Alert brokenRepositoryAlert :: AlertButton -> Alert
brokenRepositoryAlert = errorAlert "Serious problems have been detected with your repository. This needs your immediate attention!" brokenRepositoryAlert = errorAlert "Serious problems have been detected with your repository. This needs your immediate attention!"

View file

@ -15,6 +15,7 @@ import Assistant.Sync
import Utility.ThreadScheduler import Utility.ThreadScheduler
import qualified Types.Remote as Remote import qualified Types.Remote as Remote
import Assistant.DaemonStatus import Assistant.DaemonStatus
import Utility.NotificationBroadcaster
#if WITH_DBUS #if WITH_DBUS
import Utility.DBus import Utility.DBus
@ -127,7 +128,9 @@ listenWicdConnections client callback =
#endif #endif
handleConnection :: Assistant () handleConnection :: Assistant ()
handleConnection = reconnectRemotes True =<< networkRemotes handleConnection = do
liftIO . sendNotification . networkConnectedNotifier =<< getDaemonStatus
reconnectRemotes True =<< networkRemotes
{- Network remotes to sync with. -} {- Network remotes to sync with. -}
networkRemotes :: Assistant [Remote] networkRemotes :: Assistant [Remote]

View file

@ -0,0 +1,87 @@
{- git-annex assistant thread to detect when upgrade is needed
-
- Copyright 2013 Joey Hess <joey@kitenet.net>
-
- Licensed under the GNU GPL version 3 or higher.
-}
{-# LANGUAGE CPP #-}
module Assistant.Threads.Upgrader (
upgraderThread
) where
import Assistant.Common
import Assistant.Types.UrlRenderer
import Assistant.DaemonStatus
import Assistant.Alert
import Utility.NotificationBroadcaster
import Utility.Tmp
import qualified Build.SysConfig
import qualified Utility.Url as Url
import qualified Annex.Url as Url
import qualified Git.Version
import Types.Distribution
#ifdef WITH_WEBAPP
import Assistant.WebApp.Types
#endif
import Data.Time.Clock
import qualified Data.Text as T
upgraderThread :: UrlRenderer -> NamedThread
upgraderThread urlrenderer = namedThread "Upgrader" $ do
checkUpgrade urlrenderer -- TODO: remove
when (isJust Build.SysConfig.upgradelocation) $ do
h <- liftIO . newNotificationHandle False . networkConnectedNotifier =<< getDaemonStatus
go h Nothing
where
{- Wait for a network connection event. Then see if it's been
- half a day since the last upgrade check. If so, proceed with
- check. -}
go h lastchecked = do
liftIO $ waitNotification h
now <- liftIO getCurrentTime
if maybe True (\t -> diffUTCTime now t > halfday) lastchecked
then do
checkUpgrade urlrenderer
go h =<< Just <$> liftIO getCurrentTime
else go h lastchecked
halfday = 12 * 60 * 60
checkUpgrade :: UrlRenderer -> Assistant ()
checkUpgrade urlrenderer = do
debug [ "Checking if an upgrade is available." ]
go =<< getDistributionInfo
where
go Nothing = debug [ "Failed to check if upgrade is available." ]
go (Just d) = do
let installed = Git.Version.normalize Build.SysConfig.packageversion
let avail = Git.Version.normalize $ distributionVersion d
let old = Git.Version.normalize <$> distributionUrgentUpgrade d
if Just installed <= old
then canUpgrade Low urlrenderer d
else when (installed < avail) $
canUpgrade High urlrenderer d
canUpgrade :: AlertPriority -> UrlRenderer -> GitAnnexDistribution -> Assistant ()
canUpgrade urgency urlrenderer d = do
#ifdef WITH_WEBAPP
button <- mkAlertButton False (T.pack "Upgrade") urlrenderer (ConfigUpgradeR d)
void $ addAlert (canUpgradeAlert urgency button)
#else
noop
#endif
getDistributionInfo :: Assistant (Maybe GitAnnexDistribution)
getDistributionInfo = do
ua <- liftAnnex Url.getUserAgent
liftIO $ withTmpFile "git-annex.tmp" $ \tmpfile h -> do
hClose h
ifM (Url.downloadQuiet distributionInfoUrl [] [] tmpfile ua)
( readish <$> readFileStrict tmpfile
, return Nothing
)
distributionInfoUrl :: String
distributionInfoUrl = fromJust Build.SysConfig.upgradelocation ++ "/info"

View file

@ -30,6 +30,7 @@ import Assistant.WebApp.Configurators.Preferences
import Assistant.WebApp.Configurators.Edit import Assistant.WebApp.Configurators.Edit
import Assistant.WebApp.Configurators.Delete import Assistant.WebApp.Configurators.Delete
import Assistant.WebApp.Configurators.Fsck import Assistant.WebApp.Configurators.Fsck
import Assistant.WebApp.Configurators.Upgrade
import Assistant.WebApp.Documentation import Assistant.WebApp.Documentation
import Assistant.WebApp.Control import Assistant.WebApp.Control
import Assistant.WebApp.OtherRepos import Assistant.WebApp.OtherRepos

View file

@ -31,6 +31,7 @@ data AlertName
| CloudRepoNeededAlert | CloudRepoNeededAlert
| SyncAlert | SyncAlert
| NotFsckedAlert | NotFsckedAlert
| UpgradeAlert
deriving (Eq) deriving (Eq)
{- The first alert is the new alert, the second is an old alert. {- The first alert is the new alert, the second is an old alert.
@ -52,7 +53,7 @@ data Alert = Alert
, alertButton :: Maybe AlertButton , alertButton :: Maybe AlertButton
} }
data AlertIcon = ActivityIcon | SyncIcon | SuccessIcon | ErrorIcon | InfoIcon | TheCloud data AlertIcon = ActivityIcon | SyncIcon | SuccessIcon | ErrorIcon | InfoIcon | UpgradeIcon | TheCloud
type AlertMap = M.Map AlertId Alert type AlertMap = M.Map AlertId Alert

View file

@ -67,6 +67,8 @@ data DaemonStatus = DaemonStatus
, scheduleLogNotifier :: NotificationBroadcaster , scheduleLogNotifier :: NotificationBroadcaster
-- Broadcasts a notification once the startup sanity check has run. -- Broadcasts a notification once the startup sanity check has run.
, startupSanityCheckNotifier :: NotificationBroadcaster , startupSanityCheckNotifier :: NotificationBroadcaster
-- Broadcasts notifications when the network is connected
, networkConnectedNotifier :: NotificationBroadcaster
-- When the XMPP client is connected, this will contain the XMPP -- When the XMPP client is connected, this will contain the XMPP
-- address. -- address.
, xmppClientID :: Maybe ClientID , xmppClientID :: Maybe ClientID
@ -103,5 +105,6 @@ newDaemonStatus = DaemonStatus
<*> newNotificationBroadcaster <*> newNotificationBroadcaster
<*> newNotificationBroadcaster <*> newNotificationBroadcaster
<*> newNotificationBroadcaster <*> newNotificationBroadcaster
<*> newNotificationBroadcaster
<*> pure Nothing <*> pure Nothing
<*> pure M.empty <*> pure M.empty

View file

@ -0,0 +1,24 @@
{- git-annex assistant webapp upgrade UI
-
- Copyright 2013 Joey Hess <joey@kitenet.net>
-
- Licensed under the GNU AGPL version 3 or higher.
-}
{-# LANGUAGE QuasiQuotes, TemplateHaskell, OverloadedStrings #-}
module Assistant.WebApp.Configurators.Upgrade where
import Assistant.WebApp.Common
import Types.Distribution
import Utility.HumanTime
import Data.Time.Clock
getConfigUpgradeR :: GitAnnexDistribution -> Handler Html
getConfigUpgradeR d = page "Upgrade git-annex" (Just Configuration) $ do
now <- liftIO getCurrentTime
let upgradeage = Duration $ floor $
now `diffUTCTime` distributionReleasedate d
$(widgetFile "configurators/upgrade")

View file

@ -97,6 +97,7 @@ htmlIcon SyncIcon = [whamlet|<img src="@{StaticR syncicon_gif}" alt="">|]
htmlIcon InfoIcon = bootstrapIcon "info-sign" htmlIcon InfoIcon = bootstrapIcon "info-sign"
htmlIcon SuccessIcon = bootstrapIcon "ok" htmlIcon SuccessIcon = bootstrapIcon "ok"
htmlIcon ErrorIcon = bootstrapIcon "exclamation-sign" htmlIcon ErrorIcon = bootstrapIcon "exclamation-sign"
htmlIcon UpgradeIcon = bootstrapIcon "arrow-up"
-- utf-8 umbrella (utf-8 cloud looks too stormy) -- utf-8 umbrella (utf-8 cloud looks too stormy)
htmlIcon TheCloud = [whamlet|&#9730;|] htmlIcon TheCloud = [whamlet|&#9730;|]

View file

@ -25,6 +25,7 @@ import Utility.Gpg (KeyId)
import Build.SysConfig (packageversion) import Build.SysConfig (packageversion)
import Types.ScheduledActivity import Types.ScheduledActivity
import Assistant.WebApp.RepoId import Assistant.WebApp.RepoId
import Types.Distribution
import Yesod.Static import Yesod.Static
import Text.Hamlet import Text.Hamlet
@ -222,3 +223,7 @@ instance PathPiece ScheduledActivity where
instance PathPiece RepoId where instance PathPiece RepoId where
toPathPiece = pack . show toPathPiece = pack . show
fromPathPiece = readish . unpack fromPathPiece = readish . unpack
instance PathPiece GitAnnexDistribution where
toPathPiece = pack . show
fromPathPiece = readish . unpack

View file

@ -21,6 +21,7 @@
/config/xmpp/needcloudrepo/#UUID NeedCloudRepoR GET /config/xmpp/needcloudrepo/#UUID NeedCloudRepoR GET
/config/fsck ConfigFsckR GET POST /config/fsck ConfigFsckR GET POST
/config/fsck/preferences ConfigFsckPreferencesR POST /config/fsck/preferences ConfigFsckPreferencesR POST
/config/upgrade/#GitAnnexDistribution ConfigUpgradeR GET
/config/addrepository AddRepositoryR GET /config/addrepository AddRepositoryR GET
/config/repository/new NewRepositoryR GET POST /config/repository/new NewRepositoryR GET POST

View file

@ -7,7 +7,7 @@ import Data.List
import System.Process import System.Process
import Control.Applicative import Control.Applicative
import System.FilePath import System.FilePath
import System.Environment import System.Environment (getArgs)
import Data.Maybe import Data.Maybe
import Control.Monad.IfElse import Control.Monad.IfElse
import Data.Char import Data.Char
@ -17,11 +17,13 @@ import Build.Version
import Utility.SafeCommand import Utility.SafeCommand
import Utility.Monad import Utility.Monad
import Utility.ExternalSHA import Utility.ExternalSHA
import Utility.Env
import qualified Git.Version import qualified Git.Version
tests :: [TestCase] tests :: [TestCase]
tests = tests =
[ TestCase "version" getVersion [ TestCase "version" getVersion
, TestCase "UPGRADE_LOCATION" getUpgradeLocation
, TestCase "git" $ requireCmd "git" "git --version >/dev/null" , TestCase "git" $ requireCmd "git" "git --version >/dev/null"
, TestCase "git version" getGitVersion , TestCase "git version" getGitVersion
, testCp "cp_a" "-a" , testCp "cp_a" "-a"
@ -90,6 +92,11 @@ testCp k option = TestCase cmd $ testCmd k cmdline
cmd = "cp " ++ option cmd = "cp " ++ option
cmdline = cmd ++ " " ++ testFile ++ " " ++ testFile ++ ".new" cmdline = cmd ++ " " ++ testFile ++ " " ++ testFile ++ ".new"
getUpgradeLocation :: Test
getUpgradeLocation = do
e <- getEnv "UPGRADE_LOCATION"
return $ Config "upgradelocation" $ MaybeStringConfig e
getGitVersion :: Test getGitVersion :: Test
getGitVersion = Config "gitversion" . StringConfig . show getGitVersion = Config "gitversion" . StringConfig . show
<$> Git.Version.installed <$> Git.Version.installed

21
Types/Distribution.hs Normal file
View file

@ -0,0 +1,21 @@
{- Data type for a distribution of git-annex
-
- Copyright 2013 Joey Hess <joey@kitenet.net>
-
- Licensed under the GNU GPL version 3 or higher.
-}
module Types.Distribution where
import Data.Time.Clock
data GitAnnexDistribution = GitAnnexDistribution
{ distributionUrl :: String
, distributionSha256 :: String
, distributionVersion :: GitAnnexVersion
, distributionReleasedate :: UTCTime
, distributionUrgentUpgrade :: Maybe GitAnnexVersion
}
deriving (Read, Show, Eq)
type GitAnnexVersion = String

View file

@ -0,0 +1,12 @@
<div .span9 .hero-unit>
<h2>
Upgrade git-annex
<p>
Version #{distributionVersion d} was released #
#{fromDuration upgradeage} ago.
<p>
To upgrade to this version, you will need to manually download and #
install it. (Sorry, upgrades are not automated yet..)
<p>
<a .btn .btn-primary href="#{distributionUrl d}">
Download