2 Module : Gargantext.Prelude.GargDB
3 Description : Useful Tools near Prelude of the project
4 Copyright : (c) CNRS, 2017-Present
5 License : AGPL + CECILL v3
6 Maintainer : team@gargantext.org
7 Stability : experimental
10 TODO_1: qualitative tests (human)
11 TODO_2: quantitative tests (coded)
15 {-# LANGUAGE QuasiQuotes #-}
17 module Gargantext.Database.GargDB
20 import Control.Exception
21 import Control.Lens (view)
22 import Control.Monad (void)
23 import Control.Monad.Reader (MonadReader)
24 import Database.PostgreSQL.Simple.SqlQQ (sql)
25 import Data.Aeson (ToJSON, toJSON)
26 import Data.Text (Text)
27 import Data.Tuple.Extra (both)
28 import GHC.IO (FilePath)
29 import Gargantext.Database.Prelude (HasConfig(..), Cmd, execPGSQuery)
30 import Gargantext.Prelude
31 import Gargantext.Prelude.Config
32 import Gargantext.Prelude.Crypto.Hash
33 import System.Directory (createDirectoryIfMissing)
34 import System.IO.Error
35 import System.Random (newStdGen)
36 import qualified Data.Text as Text
37 import qualified System.Directory as SD
39 -------------------------------------------------------------------
40 -- | Main Class to use (just declare needed functions)
43 read :: FilePath -> IO a
45 rm :: (a, FilePath) -> IO ()
46 mv :: (a, FilePath) -> FilePath -> IO ()
49 -- | Why not this class too ?
50 class ToJSON parameters => GargDB' parameters gargdata where
51 write' :: parameters -> gargdata -> IO ()
52 read' :: parameters -> IO gargdata
54 rm' :: gargdata -> parameters -> IO ()
55 mv' :: gargdata -> parameters -> parameters -> IO ()
57 -------------------------------------------------------------------
58 -- | Deprecated Class, use GargDB instead
59 class SaveFile a where
60 saveFile' :: FilePath -> a -> IO ()
62 class ReadFile a where
63 readFile' :: FilePath -> IO a
65 -------------------------------------------------------------------
66 -------------------------------------------------------------------
67 type GargFilePath = (FolderPath, FileName)
69 type FolderPath = FilePath
70 type FileName = FilePath
72 --------------------------------
74 dataFilePath :: (ToJSON a) => a -> GargFilePath
75 dataFilePath = toPath . hash . show . toJSON
77 randomFilePath :: ( MonadReader env m
82 (foldPath, fileName) <- liftBase
87 pure (foldPath, fileName)
90 -- | toPath' : how to hash text to path
92 >>> toPath' (1,2) ("","helloword")
95 >>> toPath' (2,2) ("","helloword")
98 >>> toPath' (2,3) ("","helloword")
101 toPath :: Text -> (FolderPath, FileName)
102 toPath tx = both Text.unpack $ toPath' (2,3) ("", tx)
104 toPath' :: (Int,Int) -> (Text,Text) -> (Text,Text)
105 toPath' (n,m) (t,x) = foldl' (\tx _ -> toPath'' m tx) (t,x) [1..n]
107 toPath'' :: Int -> (Text, Text) -> (Text, Text)
108 toPath'' n (fp,fn) = (fp'',fn')
110 (fp',fn') = Text.splitAt n fn
111 fp'' = Text.intercalate "/" [fp,fp']
113 -------------------------------------------------------------------
114 type DataPath = FilePath
115 toFilePath :: FilePath -> FilePath -> FilePath
116 toFilePath fp1 fp2 = fp1 <> "/" <> fp2
118 -------------------------------------------------------------------
121 -- | For example, this write file with a random filepath
122 -- better use a hash of json of Type used to parameter as input
124 writeFile :: ( MonadReader env m
131 dataPath <- view $ hasConfig . gc_datafilepath
133 (foldPath, fileName) <- randomFilePath
135 let filePath = toFilePath foldPath fileName
136 dataFoldPath = toFilePath dataPath foldPath
137 dataFileName = toFilePath dataPath filePath
139 _ <- liftBase $ createDirectoryIfMissing True dataFoldPath
140 _ <- liftBase $ saveFile' dataFileName a
146 -- | Example to read a file with Type
147 readGargFile :: ( MonadReader env m
154 dataPath <- view $ hasConfig . gc_datafilepath
155 liftBase $ readFile' $ toFilePath dataPath fp
159 rmFile :: ( MonadReader env m
164 rmFile = onDisk_1 SD.removeFile
166 cpFile :: (MonadReader env m, MonadBase IO m, HasConfig env)
167 => FilePath -> FilePath -> m ()
168 cpFile = onDisk_2 SD.copyFile
172 mvFile :: (MonadReader env m, MonadBase IO m, HasConfig env)
173 => FilePath -> FilePath -> m ()
179 ------------------------------------------------------------------------
180 onDisk_1 :: ( MonadReader env m
184 => (FilePath -> IO ()) -> FilePath -> m ()
185 onDisk_1 action fp = do
186 dataPath <- view $ hasConfig . gc_datafilepath
187 liftBase $ action (toFilePath dataPath fp) `catch` handleExists
190 | isDoesNotExistError e = return ()
191 | otherwise = throwIO e
194 onDisk_2 :: ( MonadReader env m
198 => (FilePath -> FilePath -> IO ())
202 onDisk_2 action fp1 fp2 = do
203 dataPath <- view $ hasConfig . gc_datafilepath
204 let fp1' = toFilePath dataPath fp1
205 fp2' = toFilePath dataPath fp2
206 liftBase $ action fp1' fp2' `catch` handleExists
209 | isDoesNotExistError e = return ()
210 | otherwise = throwIO e
211 ------------------------------------------------------------------------
213 -- | Refreshes the \"context_node_ngrams_view\" materialized view. This
214 -- function will be run periodically.
215 refreshNgramsMaterializedView :: Cmd IOException ()
216 refreshNgramsMaterializedView =
217 void $ execPGSQuery [sql| refresh materialized view context_node_ngrams_view; |] ()