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)
import qualified System.Log.Simple.Base 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 ((~~), (~%))
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
initLog :: ServerOpts -> IO SessionLog
initLog sopts = do
msgs <- F.newChan
l <- newLog (logCfg [("", Log.level_ . T.pack . serverLogLevel $ sopts)]) $ concat [
[handler text console | not $ serverSilent sopts],
[handler text (chaner msgs)],
[handler text (file f) | f <- maybeToList (serverLog sopts)]]
let
listenLog = F.dupChan msgs >>= F.readChan
return $ SessionLog l listenLog (stopLog l)
runServer :: ServerOpts -> ServerM IO () -> IO ()
runServer sopts act = bracket (initLog sopts) sessionLogWait $ \slog -> Watcher.withWatcher $ \watcher -> withLog (sessionLogger slog) $ do
waitSem <- liftIO $ newQSem 0
db <- liftIO $ DB.newAsync
withCache sopts () $ \cdir -> do
sendLog Log.Trace $ "Checking cache version in {}" ~~ cdir
ver <- liftIO $ Cache.readVersion $ cdir </> Cache.versionCache
sendLog Log.Debug $ "Cache version: {}" ~~ strVersion ver
unless (sameVersion (cutVersion version) (cutVersion ver)) $ ignoreIO $ do
sendLog Log.Info $ "Cache version ({cache}) is incompatible with hsdev version ({hsdev}), removing cache ({dir})" ~~
("cache" ~% strVersion ver) ~~
("hsdev" ~% strVersion version) ~~
("dir" ~% cdir)
liftIO $ removeDirectoryRecursive cdir
sendLog Log.Debug $ "Writing new cache version: {}" ~~ strVersion version
liftIO $ createDirectoryIfMissing True cdir
liftIO $ Cache.writeVersion $ cdir </> Cache.versionCache
when (serverLoad sopts) $ withCache sopts () $ \cdir -> do
sendLog Log.Info $ "Loading cache from {}" ~~ cdir
dbCache <- liftA merge <$> liftIO (SC.load cdir)
case dbCache of
Left err -> sendLog Log.Error $ "Failed to load cache: {}" ~~ err
Right dbCache' -> DB.update db (return dbCache')
#if mingw32_HOST_OS
mmapPool <- Just <$> liftIO (createPool "hsdev")
#endif
ghcw <- ghcWorker
defs <- liftIO getDefines
let
session = Session
db
(writeCache sopts)
(readCache sopts)
slog
watcher
#if mingw32_HOST_OS
mmapPool
#endif
ghcw
(do
withLog (sessionLogger slog) $ sendLog Log.Trace "stopping server"
signalQSem waitSem)
(waitQSem waitSem)
defs
_ <- liftIO $ forkIO $ Update.onEvent watcher $ \w e -> withSession session $
void $ Client.runClient def $ Update.processEvent def w e
liftIO $ 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 = return $ F.putChan ch . T.unpack
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
sendLog Log.Info $ "writing cache to {}" ~~ cdir
logIO "cache writing exception: " (sendLog Log.Error . fromString) $ do
let
sd = structurize db
liftIO $ SC.dump cdir sd
forM_ (M.keys (structuredPackageDbs sd)) $ \c -> sendLog Log.Debug ("cache write: cabal {}" ~~ show c)
forM_ (M.keys (structuredProjects sd)) $ \p -> sendLog Log.Debug ("cache write: project {}" ~~ p)
case allModules (structuredFiles sd) of
[] -> return ()
ms -> sendLog Log.Debug $ "cache write: {} files" ~~ length ms
sendLog 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 = sendLog Log.Error ("Error reading cache: {}" ~~ e) >> return Nothing
cacheOk s = do
forM_ (M.keys (structuredPackageDbs s)) $ \c -> sendLog Log.Debug ("cache read: cabal {}" ~~ show c)
forM_ (M.keys (structuredProjects s)) $ \p -> sendLog Log.Debug ("cache read: project {}" ~~ p)
case allModules (structuredFiles s) of
[] -> return ()
ms -> sendLog Log.Debug $ "cache read: {} files" ~~ length ms
return $ Just $ merge s