mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-31 04:23:40 +00:00
95 lines
3.9 KiB
Haskell
95 lines
3.9 KiB
Haskell
{-# LANGUAGE ScopedTypeVariables #-}
|
|
{-# LANGUAGE QuasiQuotes #-}
|
|
{-# LANGUAGE TemplateHaskell #-}
|
|
module Lib.IconCache where
|
|
|
|
import Startlude hiding ( ask
|
|
, catch
|
|
, throwIO
|
|
, Reader
|
|
)
|
|
|
|
import Conduit
|
|
import Control.Concurrent.STM.TVar
|
|
import Control.Effect.Reader.Labelled
|
|
import Crypto.Hash
|
|
import qualified Data.Conduit.Binary as CB
|
|
import qualified Data.HashMap.Strict as HM
|
|
import Data.String.Interpolate.IsString
|
|
import Network.HTTP.Simple
|
|
import System.Directory
|
|
import System.FilePath
|
|
import System.IO.Error
|
|
import UnliftIO.Exception
|
|
|
|
import Lib.Error
|
|
import Lib.SystemPaths hiding ( (</>) )
|
|
import Lib.Types.Core
|
|
import Database.Persist.Sql ( runSqlPool
|
|
, repsert
|
|
, ConnectionPool
|
|
, delete
|
|
)
|
|
import Model
|
|
import Control.Effect.Error
|
|
import Crypto.Hash.Conduit ( hashFile )
|
|
import Util.File ( removeFileIfExists )
|
|
|
|
type HasIconTags sig m = HasLabelled "iconTagCache" (Reader (TVar (HM.HashMap AppId (Digest MD5)))) sig m
|
|
|
|
findIcon :: (HasFilesystemBase sig m, MonadIO m) => AppId -> m (Maybe FilePath)
|
|
findIcon appId = do
|
|
bp <- toS <$> getAbsoluteLocationFor iconBasePath
|
|
icons <- liftIO $ (listDirectory bp) `catch` \(e :: IOException) ->
|
|
if isDoesNotExistError e then createDirectoryIfMissing True bp *> pure [] else throwIO e
|
|
pure $ (bp </>) <$> find ((show appId ==) . takeBaseName) icons
|
|
|
|
saveIcon :: ( HasFilesystemBase sig m
|
|
, HasIconTags sig m
|
|
, HasLabelled "databaseConnection" (Reader ConnectionPool) sig m
|
|
, Has (Error S9Error) sig m
|
|
, MonadIO m
|
|
)
|
|
=> String
|
|
-> m ()
|
|
saveIcon url = do
|
|
bp <- toS <$> getAbsoluteLocationFor iconBasePath
|
|
req <- case parseRequest url of
|
|
Nothing -> throwError $ RegistryParseE (toS url) "invalid url"
|
|
Just x -> pure x
|
|
let saveAction = runConduit $ httpSource req getResponseBody .| CB.sinkFileCautious (bp </> takeFileName url)
|
|
liftIO $ runResourceT $ saveAction `catch` \(e :: IOException) -> if isDoesNotExistError e
|
|
then do
|
|
liftIO $ createDirectoryIfMissing True bp
|
|
saveAction
|
|
else throwIO e
|
|
tag <- hashFile (bp </> takeFileName url)
|
|
saveTag (AppId . toS $ takeFileName url) tag
|
|
|
|
saveTag :: (HasIconTags sig m, HasLabelled "databaseConnection" (Reader ConnectionPool) sig m, MonadIO m)
|
|
=> AppId
|
|
-> Digest MD5
|
|
-> m ()
|
|
saveTag appId tag = do
|
|
cache <- ask @"iconTagCache"
|
|
pool <- ask @"databaseConnection"
|
|
liftIO $ runSqlPool (repsert (IconDigestKey appId) (IconDigest tag)) pool `catch` \(e :: SomeException) ->
|
|
putStrLn @Text [i|Icon Cache Insertion Failed!: #{appId}, #{tag}, #{e}|]
|
|
liftIO $ atomically $ modifyTVar cache $ HM.insert appId tag
|
|
|
|
clearIcon :: ( MonadIO m
|
|
, HasLabelled "iconTagCache" (Reader (TVar (HM.HashMap AppId v0))) sig m
|
|
, HasLabelled "databaseConnection" (Reader ConnectionPool) sig m
|
|
, HasLabelled "filesystemBase" (Reader Text) sig m
|
|
)
|
|
=> AppId
|
|
-> m ()
|
|
clearIcon appId = do
|
|
db <- ask @"databaseConnection"
|
|
iconTags <- ask @"iconTagCache"
|
|
liftIO . atomically $ modifyTVar iconTags (HM.delete appId)
|
|
liftIO $ runSqlPool (delete (IconDigestKey appId)) db
|
|
findIcon appId >>= \case
|
|
Nothing -> pure ()
|
|
Just x -> removeFileIfExists x
|