2012-07-29 13:35:01 +00:00
|
|
|
{- git-annex assistant alerts
|
|
|
|
-
|
|
|
|
- Copyright 2012 Joey Hess <joey@kitenet.net>
|
|
|
|
-
|
|
|
|
- Licensed under the GNU GPL version 3 or higher.
|
|
|
|
-}
|
|
|
|
|
2012-08-06 19:00:46 +00:00
|
|
|
{-# LANGUAGE RankNTypes, BangPatterns, OverloadedStrings #-}
|
2012-07-29 13:35:01 +00:00
|
|
|
|
|
|
|
module Assistant.Alert where
|
|
|
|
|
2012-07-29 22:07:45 +00:00
|
|
|
import Common.Annex
|
|
|
|
import qualified Remote
|
2012-08-06 19:00:46 +00:00
|
|
|
import Utility.Tense
|
2012-08-06 21:09:23 +00:00
|
|
|
import Logs.Transfer
|
2012-07-29 22:07:45 +00:00
|
|
|
|
2012-08-06 19:00:46 +00:00
|
|
|
import qualified Data.Text as T
|
2012-09-08 23:57:15 +00:00
|
|
|
import Data.Text (Text)
|
2012-07-30 16:21:53 +00:00
|
|
|
import qualified Data.Map as M
|
2012-08-06 19:00:46 +00:00
|
|
|
import Data.String
|
2012-07-29 13:35:01 +00:00
|
|
|
|
|
|
|
{- Different classes of alerts are displayed differently. -}
|
2012-07-29 23:05:51 +00:00
|
|
|
data AlertClass = Success | Message | Activity | Warning | Error
|
|
|
|
deriving (Eq, Ord)
|
2012-07-29 13:35:01 +00:00
|
|
|
|
2012-07-30 16:21:53 +00:00
|
|
|
data AlertPriority = Filler | Low | Medium | High | Pinned
|
|
|
|
deriving (Eq, Ord)
|
|
|
|
|
2012-08-02 13:03:04 +00:00
|
|
|
{- An alert can have an name, which is used to combine it with other similar
|
|
|
|
- alerts. -}
|
2012-09-08 19:07:44 +00:00
|
|
|
data AlertName
|
|
|
|
= FileAlert TenseChunk
|
|
|
|
| SanityCheckFixAlert
|
|
|
|
| WarningAlert String
|
|
|
|
| PairRequestAlert String
|
2012-08-02 13:03:04 +00:00
|
|
|
deriving (Eq)
|
|
|
|
|
|
|
|
{- The first alert is the new alert, the second is an old alert.
|
|
|
|
- Should return a modified version of the old alert. -}
|
2012-08-06 19:41:42 +00:00
|
|
|
type AlertCombiner = Alert -> Alert -> Maybe Alert
|
2012-08-02 13:03:04 +00:00
|
|
|
|
2012-07-29 13:35:01 +00:00
|
|
|
data Alert = Alert
|
|
|
|
{ alertClass :: AlertClass
|
2012-08-06 19:00:46 +00:00
|
|
|
, alertHeader :: Maybe TenseText
|
2012-08-06 19:41:42 +00:00
|
|
|
, alertMessageRender :: [TenseChunk] -> TenseText
|
|
|
|
, alertData :: [TenseChunk]
|
2012-07-29 15:31:06 +00:00
|
|
|
, alertBlockDisplay :: Bool
|
2012-07-29 23:41:17 +00:00
|
|
|
, alertClosable :: Bool
|
2012-07-29 23:05:51 +00:00
|
|
|
, alertPriority :: AlertPriority
|
2012-09-09 19:09:22 +00:00
|
|
|
, alertIcon :: Maybe AlertIcon
|
2012-08-06 19:41:42 +00:00
|
|
|
, alertCombiner :: Maybe AlertCombiner
|
2012-08-02 13:03:04 +00:00
|
|
|
, alertName :: Maybe AlertName
|
2012-09-08 23:57:15 +00:00
|
|
|
, alertButton :: Maybe AlertButton
|
|
|
|
}
|
|
|
|
|
2012-09-09 19:09:22 +00:00
|
|
|
data AlertIcon = ActivityIcon | SuccessIcon | ErrorIcon | InfoIcon
|
|
|
|
|
|
|
|
bootstrapIcon :: AlertIcon -> String
|
|
|
|
bootstrapIcon ActivityIcon = "refresh"
|
|
|
|
bootstrapIcon InfoIcon = "info-sign"
|
|
|
|
bootstrapIcon SuccessIcon = "ok"
|
|
|
|
bootstrapIcon ErrorIcon = "exclamation-sign"
|
|
|
|
|
2012-09-09 05:02:44 +00:00
|
|
|
{- When clicked, a button always redirects to a URL
|
|
|
|
- It may also run an IO action in the background, which is useful
|
|
|
|
- to make the button close or otherwise change the alert. -}
|
2012-09-08 23:57:15 +00:00
|
|
|
data AlertButton = AlertButton
|
2012-09-09 05:02:44 +00:00
|
|
|
{ buttonLabel :: Text
|
|
|
|
, buttonUrl :: Text
|
|
|
|
, buttonAction :: Maybe (AlertId -> IO ())
|
2012-07-29 15:31:06 +00:00
|
|
|
}
|
|
|
|
|
2012-07-30 19:33:12 +00:00
|
|
|
type AlertPair = (AlertId, Alert)
|
|
|
|
|
|
|
|
type AlertMap = M.Map AlertId Alert
|
|
|
|
|
2012-07-29 23:05:51 +00:00
|
|
|
{- Higher AlertId indicates a more recent alert. -}
|
2012-07-30 18:08:22 +00:00
|
|
|
newtype AlertId = AlertId Integer
|
|
|
|
deriving (Read, Show, Eq, Ord)
|
|
|
|
|
|
|
|
firstAlertId :: AlertId
|
|
|
|
firstAlertId = AlertId 0
|
|
|
|
|
|
|
|
nextAlertId :: AlertId -> AlertId
|
|
|
|
nextAlertId (AlertId i) = AlertId $ succ i
|
2012-07-29 23:05:51 +00:00
|
|
|
|
2012-07-30 16:21:53 +00:00
|
|
|
{- This is as many alerts as it makes sense to display at a time.
|
2012-08-06 19:00:46 +00:00
|
|
|
- A display might be smaller, or larger, the point is to not overwhelm the
|
2012-07-30 16:21:53 +00:00
|
|
|
- user with a ton of alerts. -}
|
|
|
|
displayAlerts :: Int
|
2012-07-30 19:33:12 +00:00
|
|
|
displayAlerts = 6
|
2012-07-30 16:21:53 +00:00
|
|
|
|
|
|
|
{- This is not a hard maximum, but there's no point in keeping a great
|
|
|
|
- many filler alerts in an AlertMap, so when there's more than this many,
|
|
|
|
- they start being pruned, down toward displayAlerts. -}
|
|
|
|
maxAlerts :: Int
|
|
|
|
maxAlerts = displayAlerts * 2
|
2012-07-29 23:05:51 +00:00
|
|
|
|
|
|
|
{- The desired order is the reverse of:
|
|
|
|
-
|
2012-07-29 23:41:17 +00:00
|
|
|
- - Pinned alerts
|
2012-07-29 23:05:51 +00:00
|
|
|
- - High priority alerts, newest first
|
|
|
|
- - Medium priority Activity, newest first (mostly used for Activity)
|
2012-07-30 06:07:02 +00:00
|
|
|
- - Low priority alerts, newest first
|
|
|
|
- - Filler priorty alerts, newest first
|
2012-07-29 23:05:51 +00:00
|
|
|
- - Ties are broken by the AlertClass, with Errors etc coming first.
|
|
|
|
-}
|
|
|
|
compareAlertPairs :: AlertPair -> AlertPair -> Ordering
|
|
|
|
compareAlertPairs
|
2012-07-30 19:33:12 +00:00
|
|
|
(aid, Alert { alertClass = aclass, alertPriority = aprio })
|
|
|
|
(bid, Alert { alertClass = bclass, alertPriority = bprio })
|
2012-07-29 23:05:51 +00:00
|
|
|
= compare aprio bprio
|
|
|
|
`thenOrd` compare aid bid
|
|
|
|
`thenOrd` compare aclass bclass
|
|
|
|
|
2012-07-30 16:21:53 +00:00
|
|
|
sortAlertPairs :: [AlertPair] -> [AlertPair]
|
|
|
|
sortAlertPairs = sortBy compareAlertPairs
|
|
|
|
|
2012-08-06 19:00:46 +00:00
|
|
|
{- Renders an alert's header for display, if it has one. -}
|
2012-09-08 23:57:15 +00:00
|
|
|
renderAlertHeader :: Alert -> Maybe Text
|
2012-08-06 19:00:46 +00:00
|
|
|
renderAlertHeader alert = renderTense (alertTense alert) <$> alertHeader alert
|
|
|
|
|
|
|
|
{- Renders an alert's message for display. -}
|
2012-09-08 23:57:15 +00:00
|
|
|
renderAlertMessage :: Alert -> Text
|
2012-08-06 19:41:42 +00:00
|
|
|
renderAlertMessage alert = renderTense (alertTense alert) $
|
|
|
|
(alertMessageRender alert) (alertData alert)
|
2012-08-06 19:00:46 +00:00
|
|
|
|
|
|
|
alertTense :: Alert -> Tense
|
|
|
|
alertTense alert
|
|
|
|
| alertClass alert == Activity = Present
|
|
|
|
| otherwise = Past
|
|
|
|
|
|
|
|
{- Checks if two alerts display the same. -}
|
2012-07-30 19:33:12 +00:00
|
|
|
effectivelySameAlert :: Alert -> Alert -> Bool
|
2012-08-06 19:00:46 +00:00
|
|
|
effectivelySameAlert x y = all id
|
|
|
|
[ alertClass x == alertClass y
|
|
|
|
, alertHeader x == alertHeader y
|
2012-08-06 19:41:42 +00:00
|
|
|
, alertData x == alertData y
|
2012-08-06 19:00:46 +00:00
|
|
|
, alertBlockDisplay x == alertBlockDisplay y
|
|
|
|
, alertClosable x == alertClosable y
|
|
|
|
, alertPriority x == alertPriority y
|
|
|
|
]
|
2012-07-30 19:33:12 +00:00
|
|
|
|
2012-07-30 06:07:02 +00:00
|
|
|
makeAlertFiller :: Bool -> Alert -> Alert
|
|
|
|
makeAlertFiller success alert
|
2012-07-30 16:21:53 +00:00
|
|
|
| isFiller alert = alert
|
2012-07-30 06:07:02 +00:00
|
|
|
| otherwise = alert
|
|
|
|
{ alertClass = if c == Activity then c' else c
|
|
|
|
, alertPriority = Filler
|
2012-07-30 16:23:40 +00:00
|
|
|
, alertClosable = True
|
2012-09-09 19:09:22 +00:00
|
|
|
, alertIcon = Just $ if success then SuccessIcon else ErrorIcon
|
2012-07-30 06:07:02 +00:00
|
|
|
}
|
|
|
|
where
|
|
|
|
c = alertClass alert
|
|
|
|
c'
|
|
|
|
| success = Success
|
|
|
|
| otherwise = Error
|
|
|
|
|
2012-07-30 16:21:53 +00:00
|
|
|
isFiller :: Alert -> Bool
|
|
|
|
isFiller alert = alertPriority alert == Filler
|
|
|
|
|
2012-08-06 21:09:23 +00:00
|
|
|
{- Updates the Alertmap, adding or updating an alert.
|
2012-07-30 19:33:12 +00:00
|
|
|
-
|
2012-08-06 21:09:23 +00:00
|
|
|
- Any old filler that looks the same as the alert is removed.
|
|
|
|
-
|
|
|
|
- Or, if the alert has an alertCombiner that combines it with
|
|
|
|
- an old alert, the old alert is replaced with the result, and the
|
|
|
|
- alert is removed.
|
2012-07-30 16:21:53 +00:00
|
|
|
-
|
|
|
|
- Old filler alerts are pruned once maxAlerts is reached.
|
|
|
|
-}
|
2012-08-06 21:09:23 +00:00
|
|
|
mergeAlert :: AlertId -> Alert -> AlertMap -> AlertMap
|
|
|
|
mergeAlert i al m = maybe updatePrune updateCombine (alertCombiner al)
|
2012-07-30 16:21:53 +00:00
|
|
|
where
|
2012-08-06 21:09:23 +00:00
|
|
|
pruneSame k al' = k == i || not (effectivelySameAlert al al')
|
2012-07-30 19:33:12 +00:00
|
|
|
pruneBloat m'
|
|
|
|
| bloat > 0 = M.fromList $ pruneold $ M.toList m'
|
|
|
|
| otherwise = m'
|
|
|
|
where
|
|
|
|
bloat = M.size m' - maxAlerts
|
|
|
|
pruneold l =
|
2012-08-06 21:09:23 +00:00
|
|
|
let (f, rest) = partition (\(_, a) -> isFiller a) l
|
2012-07-30 19:33:12 +00:00
|
|
|
in drop bloat f ++ rest
|
2012-08-06 21:09:23 +00:00
|
|
|
updatePrune = pruneBloat $ M.filterWithKey pruneSame $
|
2012-08-02 13:03:04 +00:00
|
|
|
M.insertWith' const i al m
|
2012-08-06 21:09:23 +00:00
|
|
|
updateCombine combiner =
|
2012-08-02 13:03:04 +00:00
|
|
|
let combined = M.mapMaybe (combiner al) m
|
|
|
|
in if M.null combined
|
2012-08-06 21:09:23 +00:00
|
|
|
then updatePrune
|
2012-08-02 13:03:04 +00:00
|
|
|
else M.delete i $ M.union combined m
|
2012-07-29 23:05:51 +00:00
|
|
|
|
2012-07-29 23:41:17 +00:00
|
|
|
baseActivityAlert :: Alert
|
|
|
|
baseActivityAlert = Alert
|
2012-07-29 15:31:06 +00:00
|
|
|
{ alertClass = Activity
|
2012-07-29 23:41:17 +00:00
|
|
|
, alertHeader = Nothing
|
2012-08-06 19:41:42 +00:00
|
|
|
, alertMessageRender = tenseWords
|
|
|
|
, alertData = []
|
2012-07-29 15:31:06 +00:00
|
|
|
, alertBlockDisplay = False
|
2012-07-29 23:41:17 +00:00
|
|
|
, alertClosable = False
|
2012-07-29 23:05:51 +00:00
|
|
|
, alertPriority = Medium
|
2012-09-09 19:09:22 +00:00
|
|
|
, alertIcon = Just ActivityIcon
|
2012-08-02 13:03:04 +00:00
|
|
|
, alertCombiner = Nothing
|
|
|
|
, alertName = Nothing
|
2012-09-08 23:57:15 +00:00
|
|
|
, alertButton = Nothing
|
2012-07-29 13:35:01 +00:00
|
|
|
}
|
2012-07-29 22:07:45 +00:00
|
|
|
|
2012-09-06 17:56:23 +00:00
|
|
|
warningAlert :: String -> String -> Alert
|
|
|
|
warningAlert name msg = Alert
|
|
|
|
{ alertClass = Warning
|
|
|
|
, alertHeader = Just $ tenseWords ["warning"]
|
|
|
|
, alertMessageRender = tenseWords
|
|
|
|
, alertData = [UnTensed $ T.pack msg]
|
|
|
|
, alertBlockDisplay = True
|
|
|
|
, alertClosable = True
|
|
|
|
, alertPriority = High
|
2012-09-09 19:09:22 +00:00
|
|
|
, alertIcon = Just ErrorIcon
|
2012-09-06 17:56:23 +00:00
|
|
|
, alertCombiner = Just $ dataCombiner (++)
|
|
|
|
, alertName = Just $ WarningAlert name
|
2012-09-08 23:57:15 +00:00
|
|
|
, alertButton = Nothing
|
2012-09-06 17:56:23 +00:00
|
|
|
}
|
|
|
|
|
2012-08-06 19:41:42 +00:00
|
|
|
activityAlert :: Maybe TenseText -> [TenseChunk] -> Alert
|
|
|
|
activityAlert header dat = baseActivityAlert
|
2012-07-29 23:41:17 +00:00
|
|
|
{ alertHeader = header
|
2012-08-06 19:41:42 +00:00
|
|
|
, alertData = dat
|
2012-07-29 23:41:17 +00:00
|
|
|
}
|
|
|
|
|
2012-07-29 22:07:45 +00:00
|
|
|
startupScanAlert :: Alert
|
2012-08-06 19:00:46 +00:00
|
|
|
startupScanAlert = activityAlert Nothing $
|
2012-08-06 19:41:42 +00:00
|
|
|
[Tensed "Performing" "Performed", "startup scan"]
|
2012-07-29 22:07:45 +00:00
|
|
|
|
2012-08-02 18:02:35 +00:00
|
|
|
commitAlert :: Alert
|
2012-08-06 19:41:42 +00:00
|
|
|
commitAlert = activityAlert Nothing $
|
2012-08-06 19:00:46 +00:00
|
|
|
[Tensed "Committing" "Committed", "changes to git"]
|
|
|
|
|
|
|
|
showRemotes :: [Remote] -> TenseChunk
|
|
|
|
showRemotes = UnTensed . T.unwords . map (T.pack . Remote.name)
|
2012-08-02 18:02:35 +00:00
|
|
|
|
2012-07-29 22:07:45 +00:00
|
|
|
pushAlert :: [Remote] -> Alert
|
2012-08-06 19:41:42 +00:00
|
|
|
pushAlert rs = activityAlert Nothing $
|
2012-08-06 19:00:46 +00:00
|
|
|
[Tensed "Syncing" "Synced", "with", showRemotes rs]
|
2012-07-29 22:07:45 +00:00
|
|
|
|
|
|
|
pushRetryAlert :: [Remote] -> Alert
|
2012-08-06 19:00:46 +00:00
|
|
|
pushRetryAlert rs = activityAlert
|
|
|
|
(Just $ tenseWords [Tensed "Retrying" "Retried", "sync"])
|
2012-08-06 19:41:42 +00:00
|
|
|
(["with", showRemotes rs])
|
2012-07-29 22:07:45 +00:00
|
|
|
|
2012-08-06 21:09:23 +00:00
|
|
|
syncAlert :: [Remote] -> Alert
|
|
|
|
syncAlert rs = baseActivityAlert
|
2012-08-06 19:00:46 +00:00
|
|
|
{ alertHeader = Just $ tenseWords
|
2012-08-06 21:09:23 +00:00
|
|
|
[Tensed "Syncing" "Synced", "with", showRemotes rs]
|
|
|
|
, alertData = []
|
2012-07-29 23:05:51 +00:00
|
|
|
, alertPriority = Low
|
2012-07-29 22:07:45 +00:00
|
|
|
}
|
|
|
|
|
scan multiple remotes in one pass
The expensive transfer scan now scans a whole set of remotes in one pass.
So at startup, or when network comes up, it will run only once.
Note that this can result in transfers from/to higher cost remotes being
queued before other transfers of other content from/to lower cost remotes.
Before, low cost remotes were scanned first and all their transfers came
first. When multiple transfers are queued for a key, the lower cost ones
are still queued first. However, this could result in transfers from slow
remotes running for a long time while transfers of other data from faster
remotes waits.
I expect to make the transfer queue smarter about ordering
and/or make it allow multiple transfers at a time, which should eliminate
this annoyance. (Also, it was already possible to get into that situation,
for example if the network was up, lots of transfers from slow remotes
might be queued, and then a disk is mounted and its faster transfers have
to wait.)
Also note that this means I don't need to improve the code in
Assistant.Sync that currently checks if any of the reconnected remotes
have diverged, and if so, queues scans of all of them. That had been very
innefficient, but now doesn't matter.
2012-08-26 18:01:43 +00:00
|
|
|
scanAlert :: [Remote] -> Alert
|
|
|
|
scanAlert rs = baseActivityAlert
|
2012-08-06 19:00:46 +00:00
|
|
|
{ alertHeader = Just $ tenseWords
|
scan multiple remotes in one pass
The expensive transfer scan now scans a whole set of remotes in one pass.
So at startup, or when network comes up, it will run only once.
Note that this can result in transfers from/to higher cost remotes being
queued before other transfers of other content from/to lower cost remotes.
Before, low cost remotes were scanned first and all their transfers came
first. When multiple transfers are queued for a key, the lower cost ones
are still queued first. However, this could result in transfers from slow
remotes running for a long time while transfers of other data from faster
remotes waits.
I expect to make the transfer queue smarter about ordering
and/or make it allow multiple transfers at a time, which should eliminate
this annoyance. (Also, it was already possible to get into that situation,
for example if the network was up, lots of transfers from slow remotes
might be queued, and then a disk is mounted and its faster transfers have
to wait.)
Also note that this means I don't need to improve the code in
Assistant.Sync that currently checks if any of the reconnected remotes
have diverged, and if so, queues scans of all of them. That had been very
innefficient, but now doesn't matter.
2012-08-26 18:01:43 +00:00
|
|
|
[Tensed "Scanning" "Scanned", showRemotes rs]
|
2012-07-29 22:07:45 +00:00
|
|
|
, alertBlockDisplay = True
|
2012-07-29 23:05:51 +00:00
|
|
|
, alertPriority = Low
|
2012-07-29 22:07:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
sanityCheckAlert :: Alert
|
2012-08-06 19:00:46 +00:00
|
|
|
sanityCheckAlert = activityAlert
|
|
|
|
(Just $ tenseWords [Tensed "Running" "Ran", "daily sanity check"])
|
2012-08-06 19:41:42 +00:00
|
|
|
["to make sure everything is ok."]
|
2012-07-29 22:07:45 +00:00
|
|
|
|
|
|
|
sanityCheckFixAlert :: String -> Alert
|
|
|
|
sanityCheckFixAlert msg = Alert
|
|
|
|
{ alertClass = Warning
|
2012-08-06 19:00:46 +00:00
|
|
|
, alertHeader = Just $ tenseWords ["Fixed a problem"]
|
2012-08-06 19:41:42 +00:00
|
|
|
, alertMessageRender = render
|
|
|
|
, alertData = [UnTensed $ T.pack msg]
|
2012-07-29 22:07:45 +00:00
|
|
|
, alertBlockDisplay = True
|
2012-07-29 23:05:51 +00:00
|
|
|
, alertPriority = High
|
2012-07-29 23:41:17 +00:00
|
|
|
, alertClosable = True
|
2012-09-09 19:09:22 +00:00
|
|
|
, alertIcon = Just ErrorIcon
|
2012-08-02 13:03:04 +00:00
|
|
|
, alertName = Just SanityCheckFixAlert
|
2012-08-06 19:41:42 +00:00
|
|
|
, alertCombiner = Just $ dataCombiner (++)
|
2012-09-08 23:57:15 +00:00
|
|
|
, alertButton = Nothing
|
2012-08-02 13:03:04 +00:00
|
|
|
}
|
|
|
|
where
|
2012-08-06 19:41:42 +00:00
|
|
|
render dta = tenseWords $ alerthead : dta ++ [alertfoot]
|
2012-08-02 13:03:04 +00:00
|
|
|
alerthead = "The daily sanity check found and fixed a problem:"
|
|
|
|
alertfoot = "If these problems persist, consider filing a bug report."
|
|
|
|
|
2012-09-08 23:57:15 +00:00
|
|
|
pairRequestAlert :: String -> String -> AlertButton -> Alert
|
|
|
|
pairRequestAlert repo msg button = Alert
|
2012-09-08 19:07:44 +00:00
|
|
|
{ alertClass = Message
|
2012-09-09 19:16:17 +00:00
|
|
|
, alertHeader = Nothing
|
2012-09-08 19:07:44 +00:00
|
|
|
, alertMessageRender = tenseWords
|
|
|
|
, alertData = [UnTensed $ T.pack msg]
|
2012-09-09 19:16:17 +00:00
|
|
|
, alertBlockDisplay = False
|
2012-09-08 19:07:44 +00:00
|
|
|
, alertPriority = High
|
|
|
|
, alertClosable = True
|
2012-09-09 19:09:22 +00:00
|
|
|
, alertIcon = Just InfoIcon
|
2012-09-08 19:07:44 +00:00
|
|
|
, alertName = Just $ PairRequestAlert repo
|
|
|
|
, alertCombiner = Just $ dataCombiner $ const id
|
2012-09-08 23:57:15 +00:00
|
|
|
, alertButton = Just button
|
2012-09-08 19:07:44 +00:00
|
|
|
}
|
|
|
|
|
2012-08-06 21:09:23 +00:00
|
|
|
fileAlert :: TenseChunk -> FilePath -> Alert
|
|
|
|
fileAlert msg file = (activityAlert Nothing [f])
|
|
|
|
{ alertName = Just $ FileAlert msg
|
2012-08-06 19:41:42 +00:00
|
|
|
, alertMessageRender = render
|
|
|
|
, alertCombiner = Just $ dataCombiner combiner
|
2012-07-29 22:07:45 +00:00
|
|
|
}
|
2012-08-02 13:03:04 +00:00
|
|
|
where
|
2012-08-06 19:41:42 +00:00
|
|
|
f = fromString $ shortFile $ takeFileName file
|
2012-08-06 21:09:23 +00:00
|
|
|
render fs = tenseWords $ msg : fs
|
2012-08-06 19:41:42 +00:00
|
|
|
combiner new old = take 10 $ new ++ old
|
|
|
|
|
2012-08-06 21:09:23 +00:00
|
|
|
addFileAlert :: FilePath -> Alert
|
|
|
|
addFileAlert = fileAlert (Tensed "Adding" "Added")
|
|
|
|
|
|
|
|
{- This is only used as a success alert after a transfer, not during it. -}
|
2012-08-27 17:52:48 +00:00
|
|
|
transferFileAlert :: Direction -> Bool -> FilePath -> Alert
|
|
|
|
transferFileAlert direction True
|
2012-08-06 21:09:23 +00:00
|
|
|
| direction == Upload = fileAlert "Uploaded"
|
|
|
|
| otherwise = fileAlert "Downloaded"
|
2012-08-27 17:52:48 +00:00
|
|
|
transferFileAlert direction False
|
|
|
|
| direction == Upload = fileAlert "Upload failed"
|
|
|
|
| otherwise = fileAlert "Download failed"
|
2012-08-06 21:09:23 +00:00
|
|
|
|
2012-08-06 19:41:42 +00:00
|
|
|
dataCombiner :: ([TenseChunk] -> [TenseChunk] -> [TenseChunk]) -> AlertCombiner
|
|
|
|
dataCombiner combiner new old
|
|
|
|
| alertClass new /= alertClass old = Nothing
|
|
|
|
| alertName new == alertName old =
|
|
|
|
Just $! old { alertData = alertData new `combiner` alertData old }
|
|
|
|
| otherwise = Nothing
|
2012-08-02 17:47:26 +00:00
|
|
|
|
|
|
|
shortFile :: FilePath -> String
|
|
|
|
shortFile f
|
|
|
|
| len < maxlen = f
|
|
|
|
| otherwise = take half f ++ ".." ++ drop (len - half) f
|
|
|
|
where
|
|
|
|
len = length f
|
|
|
|
maxlen = 20
|
|
|
|
half = (maxlen - 2) `div` 2
|
|
|
|
|