{-# LANGUAGE OverloadedStrings #-} module Much.API (module Much.API) where import Control.Concurrent import Control.Exception (catch, finally, throwIO) import Control.Monad.IO.Class import Data.Function ((&)) import Data.Proxy (Proxy) import Data.Tree.Zipper qualified as Z import Much.API.Config as Much.API import Much.Event import Much.State import Much.TreeView import Network.Socket import Network.Wai import Network.Wai.Handler.Warp import Notmuch.Class import Notmuch.Message import Servant import System.IO.Error (isDoesNotExistError) import System.Posix.Files (removeLink) type API = "current" :> ( "part" :> Get '[PlainText] String :<|> "query" :> Get '[PlainText] String ) api :: Proxy API api = Proxy main :: Config -> (Event -> IO ()) -> IO () main Config{socketPath} putEvent = do sock <- socket AF_UNIX Stream defaultProtocol removeIfExists socketPath bind sock $ SockAddrUnix socketPath listen sock maxListenQueue let settings = defaultSettings & setPort 0 runSettingsSocket settings sock app `finally` closeSocket sock where app :: Application app = serve api server server :: Server API server = servePart :<|> serveQuery servePart :: Handler String servePart = do q <- liftIO getState case searchPart (Z.label (cursor q)) of Just i -> return (show i <> "\n") Nothing -> throwError err404 serveQuery :: Handler String serveQuery = do q <- liftIO getState return $ (searchQuery $ Z.label $ cursor q) <> "\n" getState :: IO State getState = do v <- newEmptyMVar putEvent $ EStateGet $ putMVar v takeMVar v searchPart :: TreeView -> Maybe Int searchPart = \case TVMessagePart _ i -> Just (partID i) _ -> Nothing searchQuery :: TreeView -> String searchQuery = \case TVMessage m -> notmuchId m TVMessageHeaderField m _ -> notmuchId m TVMessagePart m _ -> notmuchId m TVMessageQuoteLine m _ _ _ -> notmuchId m TVMessageRawLine m _ _ _ -> notmuchId m TVMessageLine m _ _ _ -> notmuchId m TVSearch s -> s TVSearchResult r -> notmuchId r removeIfExists :: FilePath -> IO () removeIfExists fileName = removeLink fileName `catch` handleExists where handleExists e | isDoesNotExistError e = return () | otherwise = throwIO e closeSocket :: Socket -> IO () closeSocket sock = do name <- getSocketName sock close sock case name of SockAddrUnix path -> removeIfExists path _ -> return ()