packages feed

notifications-tray-icon-0.2.0.0: src/StatusNotifier/Item/Notifications/Gitea.hs

module StatusNotifier.Item.Notifications.Gitea where

import           Control.Concurrent
import           Control.Concurrent.Async
import           Control.Concurrent.MVar as MV
import           Control.Monad
import           Data.Either
import           Data.List
import qualified Data.Text as T
import           GI.Dbusmenu
import           Gitea.API
import           StatusNotifier.Item.Notifications.Util
import           System.Log.Logger
import           Text.Printf

giteaLog :: Priority -> String -> IO ()
giteaLog = logM "StatusNotifier.Item.Notifications.Gitea"

data GiteaUpdaterConfig = GiteaUpdaterConfig
  { giteaConfig :: GiteaConfig
  , giteaRefreshSeconds :: Rational
  }

giteaUpdaterNew :: GiteaUpdaterConfig -> (Int -> Menuitem -> IO ()) -> IO ()
giteaUpdaterNew GiteaUpdaterConfig
                  { giteaConfig = config
                  , giteaRefreshSeconds = refreshSeconds
                  } update = do

  notificationsVar <- MV.newMVar []
  errorVar <- MV.newMVar Nothing
  forceRefreshVar <- MV.newEmptyMVar

  let forceRefresh = void $ MV.tryPutMVar forceRefreshVar ()
      delayedRefresh = void $ forkIO $ threadDelay 1000000 >> forceRefresh
      openNotificationsHTML =
        openURL $ giteaBaseUrl config <> "/notifications"
      markAllRead = void $ markAllNotificationsRead config
      getCurrentNotifications = MV.readMVar notificationsVar
      buildMenu = do
        notifications <- getCurrentNotifications
        root <- menuitemNew
        mapM_ ((>>= menuitemChildAppend root) . makeGiteaNotificationItem config)
            notifications

        separatorItem <- menuitemNew
        menuitemPropertySet separatorItem MENUITEM_PROP_TYPE CLIENT_TYPES_SEPARATOR
        menuitemChildAppend root separatorItem

        markAllReadItem <- makeMenuItemWithLabel "Mark all as read"
        onMenuitemItemActivated markAllReadItem $ const $ markAllRead >> delayedRefresh
        menuitemChildAppend root markAllReadItem

        viewItem <- makeMenuItemWithLabel "View notifications"
        onMenuitemItemActivated viewItem $ const $ void openNotificationsHTML
        menuitemChildAppend root viewItem

        refreshItem <- makeMenuItemWithLabel "Refresh"
        onMenuitemItemActivated refreshItem $ const forceRefresh
        menuitemChildAppend root refreshItem

        return root
      updateNotifications newNotifications currentNotifications =
        let newSortedIds = sort $ map giteaNotificationId newNotifications
            oldSortedIds = sort $ map giteaNotificationId currentNotifications
        in return ( newNotifications
                  , (newSortedIds /= oldSortedIds
                    , newSortedIds \\ oldSortedIds
                    )
                  )
      updateError err = do
        MV.modifyMVar_ errorVar (const $ return $ Just err)
        giteaLog ERROR $ printf "Error retrieving notifications %s" err
        return (False, [])
      updateVariables =
        getNotifications config >>=
        either updateError (MV.modifyMVar notificationsVar . updateNotifications)
      doUpdate = do
        newRoot <- buildMenu
        notificationsCount <- length <$> getCurrentNotifications
        update notificationsCount newRoot
      sendNotifications newIds = do
        notifications <- getCurrentNotifications
        let getById nid = find ((== nid) . giteaNotificationId) notifications
        mapM_ (traverse sendNotification . getById) newIds
      sendNotification notification =
        runCommandFromPath [ "notify-send"
                           , "--icon=gitea"
                           , getGiteaNotificationSummary notification
                           ]

  void updateVariables
  doUpdate
  void $ forkIO $ forever $ do
    forced <-
      isRight <$> race (threadDelay (floor $ refreshSeconds * 1000000))
                       (takeMVar forceRefreshVar)
    giteaLog DEBUG "Refreshing notifications"
    (menuNeedsRebuild, newIds) <- updateVariables
    sendNotifications newIds
    giteaLog DEBUG $ printf "Rebuild needed: %s, force: %s"
                         (show menuNeedsRebuild) (show forced)
    when (forced || menuNeedsRebuild) doUpdate

makeGiteaNotificationItem :: GiteaConfig -> GiteaNotification -> IO Menuitem
makeGiteaNotificationItem config notification = do
  let notifId = giteaNotificationId notification
      notificationText = T.pack $ getGiteaNotificationSummary notification
      subject = giteaNotificationSubject notification
      openHTML = case giteaSubjectHtmlUrl subject of
        Just url -> void $ openURL $ T.unpack url
        Nothing -> case giteaSubjectLatestCommentHtmlUrl subject of
          Just url -> void $ openURL $ T.unpack url
          Nothing -> giteaLog WARNING "Notification has no HTML URL"
      markAsRead = void $ markNotificationRead config notifId

  menuItem <- menuitemNewWithId $ fromIntegral notifId
  textVariant <- toGVariant notificationText
  menuitemPropertySetVariant menuItem "label" textVariant

  markAsReadItem <- makeMenuItemWithLabel "Mark as read"
  onMenuitemItemActivated markAsReadItem $ const markAsRead
  menuitemChildAppend menuItem markAsReadItem

  viewItem <- makeMenuItemWithLabel "View on Gitea"
  onMenuitemItemActivated viewItem $ const openHTML
  menuitemChildAppend menuItem viewItem

  return menuItem

getGiteaNotificationSummary :: GiteaNotification -> String
getGiteaNotificationSummary notification =
  printf "%s - %s"
    (T.unpack $ giteaRepoFullName $ giteaNotificationRepo notification)
    (T.unpack $ giteaSubjectTitle $ giteaNotificationSubject notification)