{-# LANGUAGE CPP, OverloadedStrings #-} {-# OPTIONS_GHC -fno-warn-orphans #-} module HsDev.Server.Base ( initLog, runServer, Server, startServer, inServer, withCache, writeCache, readCache, module HsDev.Server.Types, module HsDev.Server.Message ) where import Control.Applicative import Control.Concurrent import Control.Exception import Control.Monad import Control.Monad.Except import Control.Monad.Reader import Data.Default import qualified Data.Map as M import Data.Maybe import Data.String import Data.Text (Text) import qualified Data.Text as T (pack, unpack) import System.Log.Simple hiding (Level(..), Message(..), Command(..), (%=)) import qualified System.Log.Simple.Base as Log import qualified System.Log.Simple as Log import System.Directory (removeDirectoryRecursive, createDirectoryIfMissing) import System.FilePath import qualified Control.Concurrent.FiniteChan as F import System.Directory.Paths (canonicalize) import qualified System.Directory.Watcher as Watcher import Text.Format ((~~), FormatBuild(..), (%=)) import qualified HsDev.Cache as Cache import qualified HsDev.Cache.Structured as SC import qualified HsDev.Client.Commands as Client import HsDev.Database import qualified HsDev.Database.Async as DB import qualified HsDev.Database.Update as Update import HsDev.Inspect (getDefines) import HsDev.Tools.Ghc.Worker import HsDev.Server.Types import HsDev.Server.Message import HsDev.Util #if mingw32_HOST_OS import System.Win32.FileMapping.NamePool #endif -- | Inits log chan and returns functions (print message, wait channel) initLog :: ServerOpts -> IO SessionLog initLog sopts = do msgs <- F.newChan rulesVar <- newMVar [ruleStr] let getRules = do rs <- readMVar rulesVar return $ map (parseRule_ . fromString) rs l <- newLog (return getRules) $ concat [ [logger text console | not $ serverSilent sopts], [logger text (chaner msgs)], maybeToList $ (logger text . file) <$> serverLog sopts] Log.writeLog l Log.Info ("Log politics: low = {}, high = {}" ~~ logLow ~~ logHigh) let listenLog = F.dupChan msgs >>= F.readChan return $ SessionLog l rulesVar listenLog (stopLog l) where ruleStr :: String ruleStr = "/: {}" ~~ serverLogConfig sopts (Log.Politics logLow logHigh) = Log.rulePolitics (parseRule_ (fromString ruleStr)) Log.defaultPolitics instance FormatBuild Log.Level where -- | Run server runServer :: ServerOpts -> ServerM IO () -> IO () runServer sopts act = bracket (initLog sopts) sessionLogWait $ \slog -> Log.scopeLog (sessionLogger slog) (T.pack "hsdev") $ Watcher.withWatcher $ \watcher -> do waitSem <- newQSem 0 db <- DB.newAsync let outputStr = Log.writeLog (sessionLogger slog) withCache sopts () $ \cdir -> do outputStr Log.Trace $ "Checking cache version in {}" ~~ cdir ver <- Cache.readVersion $ cdir Cache.versionCache outputStr Log.Debug $ "Cache version: {}" ~~ strVersion ver unless (sameVersion (cutVersion version) (cutVersion ver)) $ ignoreIO $ do outputStr Log.Info $ "Cache version ({cache}) is incompatible with hsdev version ({hsdev}), removing cache ({dir})" ~~ ("cache" %= strVersion ver) ~~ ("hsdev" %= strVersion version) ~~ ("dir" %= cdir) -- drop cache removeDirectoryRecursive cdir outputStr Log.Debug $ "Writing new cache version: {}" ~~ strVersion version createDirectoryIfMissing True cdir Cache.writeVersion $ cdir Cache.versionCache when (serverLoad sopts) $ withCache sopts () $ \cdir -> do outputStr Log.Info $ "Loading cache from {}" ~~ cdir dbCache <- liftA merge <$> SC.load cdir case dbCache of Left err -> outputStr Log.Error $ "Failed to load cache: {}" ~~ err Right dbCache' -> DB.update db (return dbCache') #if mingw32_HOST_OS mmapPool <- Just <$> createPool "hsdev" #endif ghcw <- withLog (sessionLogger slog) $ ghcWorker defs <- getDefines let session = Session db (writeCache sopts) (readCache sopts) slog watcher #if mingw32_HOST_OS mmapPool #endif ghcw (do outputStr Log.Trace "stopping server" signalQSem waitSem) (waitQSem waitSem) defs _ <- forkIO $ Update.onEvent watcher $ \w e -> withSession session $ void $ Client.runClient def $ Update.processEvent def w e runReaderT (runServerM act) session type Server = Worker (ServerM IO) startServer :: ServerOpts -> IO Server startServer sopts = startWorker (runServer sopts) id id inServer :: Server -> CommandOptions -> Command -> IO Result inServer srv copts c = do c' <- canonicalize c inWorker srv (Client.runClient copts $ Client.runCommand c') chaner :: F.Chan String -> Consumer Text chaner ch = Consumer withChan where withChan f = f (F.putChan ch . T.unpack) -- | Perform action on cache withCache :: Monad m => ServerOpts -> a -> (FilePath -> m a) -> m a withCache sopts v onCache = case serverCache sopts of Nothing -> return v Just cdir -> onCache cdir writeCache :: SessionMonad m => ServerOpts -> Database -> m () writeCache sopts db = withCache sopts () $ \cdir -> do Log.log Log.Info $ "writing cache to {}" ~~ cdir logIO "cache writing exception: " (Log.log Log.Error . fromString) $ do let sd = structurize db liftIO $ SC.dump cdir sd forM_ (M.keys (structuredPackageDbs sd)) $ \c -> Log.log Log.Debug ("cache write: cabal {}" ~~ show c) forM_ (M.keys (structuredProjects sd)) $ \p -> Log.log Log.Debug ("cache write: project {}" ~~ p) case allModules (structuredFiles sd) of [] -> return () ms -> Log.log Log.Debug $ "cache write: {} files" ~~ length ms Log.log Log.Info $ "cache saved to {}" ~~ cdir readCache :: SessionMonad m => ServerOpts -> (FilePath -> ExceptT String IO Structured) -> m (Maybe Database) readCache sopts act = do s <- getSession liftIO $ withSession s $ withCache sopts Nothing $ \fpath -> do res <- liftIO $ runExceptT $ act fpath either cacheErr cacheOk res where cacheErr e = Log.log Log.Error ("Error reading cache: {}" ~~ e) >> return Nothing cacheOk s = do forM_ (M.keys (structuredPackageDbs s)) $ \c -> Log.log Log.Debug ("cache read: cabal {}" ~~ show c) forM_ (M.keys (structuredProjects s)) $ \p -> Log.log Log.Debug ("cache read: project {}" ~~ p) case allModules (structuredFiles s) of [] -> return () ms -> Log.log Log.Debug $ "cache read: {} files" ~~ length ms return $ Just $ merge s