From 77c82de4ea09685f6ab400c5fc676b32cbc9a6f1 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Fri, 15 Mar 2013 17:52:41 -0400 Subject: [PATCH] webapp: Display an alert when there are XMPP remotes, and a cloud transfer repository needs to be configured. --- Assistant/Alert.hs | 19 ++++++ Assistant/DaemonStatus.hs | 8 ++- Assistant/Threads/XMPPClient.hs | 2 +- Assistant/Types/DaemonStatus.hs | 3 + Assistant/WebApp/Configurators/Pairing.hs | 6 -- Assistant/WebApp/Configurators/XMPP.hs | 58 +++++++++++++++++- Assistant/WebApp/RepoList.hs | 9 +++ Assistant/WebApp/routes | 1 + Assistant/XMPP/Git.hs | 46 +++++++++----- debian/changelog | 2 + doc/assistant/cloudnudge.png | Bin 0 -> 7332 bytes .../configurators/pairing/xmpp/end.hamlet | 2 +- .../configurators/xmpp/needcloudrepo.hamlet | 17 +++++ 13 files changed, 146 insertions(+), 27 deletions(-) create mode 100644 doc/assistant/cloudnudge.png create mode 100644 templates/configurators/xmpp/needcloudrepo.hamlet diff --git a/Assistant/Alert.hs b/Assistant/Alert.hs index 65f8c3e674..40c37a94a8 100644 --- a/Assistant/Alert.hs +++ b/Assistant/Alert.hs @@ -34,6 +34,7 @@ data AlertName | WarningAlert String | PairAlert String | XMPPNeededAlert + | CloudRepoNeededAlert deriving (Eq) {- The first alert is the new alert, the second is an old alert. @@ -333,6 +334,24 @@ xmppNeededAlert button = Alert , alertData = [] } +cloudRepoNeededAlert :: Maybe String -> AlertButton -> Alert +cloudRepoNeededAlert friendname button = Alert + { alertHeader = Just $ fromString $ unwords + [ "Unable to download files from" + , (fromMaybe "your other devices" friendname) ++ "." + ] + , alertIcon = Just ErrorIcon + , alertPriority = High + , alertButton = Just button + , alertClosable = True + , alertClass = Message + , alertMessageRender = tenseWords + , alertBlockDisplay = True + , alertName = Just $ CloudRepoNeededAlert + , alertCombiner = Just $ dataCombiner $ \_old new -> new + , alertData = [] + } + fileAlert :: TenseChunk -> FilePath -> Alert fileAlert msg file = (activityAlert Nothing [f]) { alertName = Just $ FileAlert msg diff --git a/Assistant/DaemonStatus.hs b/Assistant/DaemonStatus.hs index ea57176b54..fcfb1a4f3f 100644 --- a/Assistant/DaemonStatus.hs +++ b/Assistant/DaemonStatus.hs @@ -51,10 +51,14 @@ calcSyncRemotes = do alive <- trustExclude DeadTrusted (map Remote.uuid rs) let good r = Remote.uuid r `elem` alive let syncable = filter good rs + let nonxmpp = filter (not . isXMPPRemote) syncable return $ \dstatus -> dstatus { syncRemotes = syncable - , syncGitRemotes = filter (not . Remote.specialRemote) syncable - , syncDataRemotes = filter (not . isXMPPRemote) syncable + , syncGitRemotes = + filter (not . Remote.specialRemote) syncable + , syncDataRemotes = nonxmpp + , syncingToCloudRemote = + any (Git.repoIsUrl . Remote.repo) nonxmpp } {- Updates the sycRemotes list from the list of all remotes in Annex state. -} diff --git a/Assistant/Threads/XMPPClient.hs b/Assistant/Threads/XMPPClient.hs index fb7da10c7b..69a886c4af 100644 --- a/Assistant/Threads/XMPPClient.hs +++ b/Assistant/Threads/XMPPClient.hs @@ -111,7 +111,7 @@ xmppClient urlrenderer d creds = handle _ (GotNetMessage m@(Pushing _ pushstage)) | isPushInitiation pushstage = inAssistant $ unlessM (queueNetPushMessage m) $ - void $ forkIO <~> handlePushInitiation m + void $ forkIO <~> handlePushInitiation urlrenderer m | otherwise = void $ inAssistant $ queueNetPushMessage m handle _ (Ignorable _) = noop handle _ (Unknown _) = noop diff --git a/Assistant/Types/DaemonStatus.hs b/Assistant/Types/DaemonStatus.hs index df95b23c01..0fc800a37e 100644 --- a/Assistant/Types/DaemonStatus.hs +++ b/Assistant/Types/DaemonStatus.hs @@ -46,6 +46,8 @@ data DaemonStatus = DaemonStatus , syncGitRemotes :: [Remote] -- Ordered list of remotes to sync data with , syncDataRemotes :: [Remote] + -- Are we syncing to any cloud remotes? + , syncingToCloudRemote :: Bool -- List of uuids of remotes that we may have gotten out of sync with. , desynced :: S.Set UUID -- Pairing request that is in progress. @@ -81,6 +83,7 @@ newDaemonStatus = DaemonStatus <*> pure [] <*> pure [] <*> pure [] + <*> pure False <*> pure S.empty <*> pure Nothing <*> newNotificationBroadcaster diff --git a/Assistant/WebApp/Configurators/Pairing.hs b/Assistant/WebApp/Configurators/Pairing.hs index 100c122ab6..d9aacab8a5 100644 --- a/Assistant/WebApp/Configurators/Pairing.hs +++ b/Assistant/WebApp/Configurators/Pairing.hs @@ -186,12 +186,6 @@ getFinishXMPPPairR _ = noXMPPPairing xmppPairStatus :: Bool -> Maybe JID -> Handler RepHtml xmppPairStatus inprogress theirjid = pairPage $ do let friend = buddyName <$> theirjid - let cloudrepolist = repoListDisplay $ RepoSelector - { onlyCloud = True - , onlyConfigured = False - , includeHere = False - , nudgeAddMore = False - } $(widgetFile "configurators/pairing/xmpp/end") #endif diff --git a/Assistant/WebApp/Configurators/XMPP.hs b/Assistant/WebApp/Configurators/XMPP.hs index ebe0754f0a..0a136a2e4a 100644 --- a/Assistant/WebApp/Configurators/XMPP.hs +++ b/Assistant/WebApp/Configurators/XMPP.hs @@ -13,6 +13,7 @@ module Assistant.WebApp.Configurators.XMPP where import Assistant.WebApp.Common import Assistant.WebApp.Notifications import Utility.NotificationBroadcaster +import qualified Remote #ifdef WITH_XMPP import Assistant.XMPP.Client import Assistant.XMPP.Buddies @@ -21,6 +22,9 @@ import Assistant.NetMessager import Assistant.Alert import Assistant.DaemonStatus import Utility.SRV +import Assistant.WebApp.RepoList +import Assistant.WebApp.Configurators +import Assistant.XMPP #endif #ifdef WITH_XMPP @@ -30,7 +34,7 @@ import qualified Data.Text as T import Control.Exception (SomeException) #endif -{- Displays an alert suggesting to configure XMPP, with a button. -} +{- Displays an alert suggesting to configure XMPP. -} xmppNeeded :: Handler () #ifdef WITH_XMPP xmppNeeded = whenM (isNothing <$> liftAnnex getXMPPCreds) $ do @@ -46,6 +50,48 @@ xmppNeeded = whenM (isNothing <$> liftAnnex getXMPPCreds) $ do xmppNeeded = return () #endif +{- Displays an alert suggesting to configure a cloud repo + - to suppliment an XMPP remote. -} +cloudRepoNeeded :: UrlRenderer -> UUID -> Assistant () +#ifdef WITH_XMPP +cloudRepoNeeded urlrenderer for = do + buddyname <- getBuddyName for + url <- liftIO $ renderUrl urlrenderer (NeedCloudRepoR for) [] + close <- asIO1 removeAlert + void $ addAlert $ cloudRepoNeededAlert buddyname $ AlertButton + { buttonLabel = "Add a cloud repository" + , buttonUrl = url + , buttonAction = Just close + } +#else +cloudRepoNeeded = return () +#endif + +{- Returns the name of the friend corresponding to a + - repository's UUID, but not if it's our name. -} +getBuddyName :: UUID -> Assistant (Maybe String) +getBuddyName u = go =<< getclientjid + where + go Nothing = return Nothing + go (Just myjid) = (T.unpack . buddyName <$>) + . headMaybe + . filter (\j -> baseJID j /= baseJID myjid) + . map fst + . filter (\(_, r) -> Remote.uuid r == u) + <$> getXMPPRemotes + getclientjid = maybe Nothing parseJID . xmppClientID + <$> getDaemonStatus + +getNeedCloudRepoR :: UUID -> Handler RepHtml +#ifdef WITH_XMPP +getNeedCloudRepoR for = page "Cloud repository needed" (Just Configuration) $ do + buddyname <- lift $ liftAssistant $ getBuddyName for + $(widgetFile "configurators/xmpp/needcloudrepo") +#else +needCloudRepoR = xmppPage $ + $(widgetFile "configurators/xmpp/disabled") +#endif + getXMPPR :: Handler RepHtml #ifdef WITH_XMPP getXMPPR = xmppPage $ do @@ -86,8 +132,7 @@ buddyListDisplay = do myjid <- lift $ liftAssistant $ xmppClientID <$> getDaemonStatus let isself (BuddyKey b) = Just b == myjid buddies <- lift $ liftAssistant $ do - rs <- filter isXMPPRemote . syncGitRemotes <$> getDaemonStatus - let pairedwith = catMaybes $ map (parseJID . getXMPPClientID) rs + pairedwith <- map fst <$> getXMPPRemotes catMaybes . map (buddySummary pairedwith) <$> (getBuddyList <<~ buddyList) $(widgetFile "configurators/xmpp/buddylist") @@ -97,6 +142,13 @@ buddyListDisplay = do #ifdef WITH_XMPP +getXMPPRemotes :: Assistant [(JID, Remote)] +getXMPPRemotes = catMaybes . map pair . filter isXMPPRemote . syncGitRemotes + <$> getDaemonStatus + where + pair r = maybe Nothing (\jid -> Just (jid, r)) $ + parseJID $ getXMPPClientID r + data XMPPForm = XMPPForm { formJID :: Text , formPassword :: Text } diff --git a/Assistant/WebApp/RepoList.hs b/Assistant/WebApp/RepoList.hs index a09c9475fc..fa24055a1d 100644 --- a/Assistant/WebApp/RepoList.hs +++ b/Assistant/WebApp/RepoList.hs @@ -81,6 +81,15 @@ mainRepoSelector = RepoSelector , nudgeAddMore = False } +{- List of cloud repositories, configured and not. -} +cloudRepoList :: Widget +cloudRepoList = repoListDisplay $ RepoSelector + { onlyCloud = True + , onlyConfigured = False + , includeHere = False + , nudgeAddMore = False + } + repoListDisplay :: RepoSelector -> Widget repoListDisplay reposelector = do autoUpdate ident (NotifierRepoListR reposelector) (10 :: Int) (10 :: Int) diff --git a/Assistant/WebApp/routes b/Assistant/WebApp/routes index 258959bd3b..c6af3fa44c 100644 --- a/Assistant/WebApp/routes +++ b/Assistant/WebApp/routes @@ -16,6 +16,7 @@ /config ConfigurationR GET /config/preferences PreferencesR GET /config/xmpp XMPPR GET +/config/xmpp/needcloudrepo/#UUID NeedCloudRepoR GET /config/addrepository AddRepositoryR GET /config/repository/new/first FirstRepositoryR GET diff --git a/Assistant/XMPP/Git.hs b/Assistant/XMPP/Git.hs index a088f459ee..74ce4b7259 100644 --- a/Assistant/XMPP/Git.hs +++ b/Assistant/XMPP/Git.hs @@ -5,6 +5,8 @@ - Licensed under the GNU GPL version 3 or higher. -} +{-# LANGUAGE CPP #-} + module Assistant.XMPP.Git where import Assistant.Common @@ -29,6 +31,10 @@ import qualified Remote as Remote import Remote.List import Utility.FileMode import Utility.Shell +#ifdef WITH_WEBAPP +import Assistant.WebApp (UrlRenderer) +import Assistant.WebApp.Configurators.XMPP +#endif import Network.Protocol.XMPP import qualified Data.Text as T @@ -80,8 +86,8 @@ makeXMPPGitRemote buddyname jid u = do - - We listen at the other end of the pipe and relay to and from XMPP. -} -xmppPush :: ClientID -> (Git.Repo -> IO Bool) -> Assistant Bool -xmppPush cid gitpush = runPush SendPack cid handleDeferred $ do +xmppPush :: ClientID -> (Git.Repo -> IO Bool) -> (NetMessage -> Assistant ()) -> Assistant Bool +xmppPush cid gitpush handledeferred = runPush SendPack cid handledeferred $ do sendNetMessage $ Pushing cid StartingPush (Fd inf, writepush) <- liftIO createPipe @@ -201,8 +207,8 @@ xmppGitRelay = do {- Relays git receive-pack stdin and stdout via XMPP, as well as propigating - its exit status to XMPP. -} -xmppReceivePack :: ClientID -> Assistant Bool -xmppReceivePack cid = runPush ReceivePack cid handleDeferred $ do +xmppReceivePack :: ClientID -> (NetMessage -> Assistant ()) -> Assistant Bool +xmppReceivePack cid handledeferred = runPush ReceivePack cid handledeferred $ do repodir <- liftAnnex $ fromRepo repoPath let p = (proc "git" ["receive-pack", repodir]) { std_in = CreatePipe @@ -250,11 +256,11 @@ xmppRemotes cid = case baseJID <$> parseJID cid of where matching loc r = repoIsUrl r && repoLocation r == loc -handlePushInitiation :: NetMessage -> Assistant () -handlePushInitiation (Pushing cid CanPush) = +handlePushInitiation :: UrlRenderer -> NetMessage -> Assistant () +handlePushInitiation _ (Pushing cid CanPush) = unlessM (null <$> xmppRemotes cid) $ sendNetMessage $ Pushing cid PushRequest -handlePushInitiation (Pushing cid PushRequest) = +handlePushInitiation urlrenderer (Pushing cid PushRequest) = go =<< liftAnnex (inRepo Git.Branch.current) where go Nothing = noop @@ -266,18 +272,30 @@ handlePushInitiation (Pushing cid PushRequest) = <*> getUUID liftIO $ Command.Sync.updateBranch (Command.Sync.syncBranch branch) g selfjid <- ((T.unpack <$>) . xmppClientID) <$> getDaemonStatus - forM_ rs $ \r -> alertWhile (syncAlert [r]) $ - xmppPush cid $ taggedPush u selfjid branch r -handlePushInitiation (Pushing cid StartingPush) = do + forM_ rs $ \r -> do + void $ alertWhile (syncAlert [r]) $ + xmppPush cid + (taggedPush u selfjid branch r) + (handleDeferred urlrenderer) + checkCloudRepos urlrenderer r +handlePushInitiation urlrenderer (Pushing cid StartingPush) = do rs <- xmppRemotes cid - unless (null rs) $ + unless (null rs) $ do void $ alertWhile (syncAlert rs) $ - xmppReceivePack cid -handlePushInitiation _ = noop + xmppReceivePack cid (handleDeferred urlrenderer) + mapM_ (checkCloudRepos urlrenderer) rs +handlePushInitiation _ _ = noop -handleDeferred :: NetMessage -> Assistant () +handleDeferred :: UrlRenderer -> NetMessage -> Assistant () handleDeferred = handlePushInitiation +checkCloudRepos :: UrlRenderer -> Remote -> Assistant () +-- TODO only display if needed +checkCloudRepos urlrenderer r = +#ifdef WITH_WEBAPP + cloudRepoNeeded urlrenderer (Remote.uuid r) +#endif + writeChunk :: Handle -> B.ByteString -> IO () writeChunk h b = do B.hPut h b diff --git a/debian/changelog b/debian/changelog index 176234e86d..2300de0d64 100644 --- a/debian/changelog +++ b/debian/changelog @@ -3,6 +3,8 @@ git-annex (4.20130315) UNRELEASED; urgency=low * webapp: Repository list is now included in the dashboard, and other UI tweaks. * webapp: Improved UI for pairing your own devices together using XMPP. + * webapp: Display an alert when there are XMPP remotes, and a cloud + transfer repository needs to be configured. -- Joey Hess Fri, 15 Mar 2013 00:10:07 -0400 diff --git a/doc/assistant/cloudnudge.png b/doc/assistant/cloudnudge.png new file mode 100644 index 0000000000000000000000000000000000000000..b6f9a657e027b0556628d5fa1e44e9853b3b2bae GIT binary patch literal 7332 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 Add a cloud repository ^{makeCloudRepositories True} diff --git a/templates/configurators/xmpp/needcloudrepo.hamlet b/templates/configurators/xmpp/needcloudrepo.hamlet new file mode 100644 index 0000000000..afae858c30 --- /dev/null +++ b/templates/configurators/xmpp/needcloudrepo.hamlet @@ -0,0 +1,17 @@ +
+

+ ☂ Configure a shared cloud repository + $maybe name <- buddyname +

+ You and #{name} have combined your repositores. But you can't open # + each other's files yet. To start sharing files with #{name}, # + you need a repository in the cloud, that you both can access. + $nothing +

+ You've combined the repositories on two or more of your devices. # + But files are not flowing yet. To start sharing files # + between your devices, you should set up a repository in the cloud. + ^{cloudRepoList} +

+ Add a cloud repository + ^{makeCloudRepositories True}