2 Module : Gargantext.Prelude.Utils
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)
16 module Gargantext.Prelude.Utils
19 import Control.Exception
20 import Control.Lens (view)
21 import Control.Monad.Random.Class (MonadRandom)
22 import Control.Monad.Reader (MonadReader)
23 import Data.Aeson (ToJSON, toJSON)
24 import Data.Text (Text)
25 import Data.Tuple.Extra (both)
26 import Formatting.Clock (timeSpecs)
27 import Formatting.Internal (Format(..))
28 import GHC.IO (FilePath)
29 import Gargantext.Database.Admin.Types.Node (NodeId, NodeType)
30 import Gargantext.Database.Prelude (HasConfig(..))
31 import Gargantext.Prelude
32 import Gargantext.Prelude.Config
33 import Gargantext.Prelude.Crypto.Hash
34 import System.Directory (createDirectoryIfMissing)
35 import System.IO.Error
36 import System.Random (newStdGen)
37 import qualified Data.Text as Text
38 import qualified System.Clock as Clock (getTime, TimeSpec, Clock(..))
39 import qualified System.Directory as SD
40 import qualified System.Random.Shuffle as SRS
42 -------------------------------------------------------------------
43 hasTime :: Formatting.Internal.Format r (Clock.TimeSpec -> Clock.TimeSpec -> r)
46 getTime :: MonadBase IO m => m Clock.TimeSpec
47 getTime = liftBase $ Clock.getTime Clock.ProcessCPUTime
48 -------------------------------------------------------------------
49 -- | Main Class to use (just declare needed functions)
52 read :: FilePath -> IO a
54 rm :: (a, FilePath) -> IO ()
55 mv :: (a, FilePath) -> FilePath -> IO ()
58 -- | Why not this class too ?
59 class ToJSON parameters => GargDB' parameters gargdata where
60 write' :: parameters -> gargdata -> IO ()
61 read' :: parameters -> IO gargdata
63 rm' :: gargdata -> parameters -> IO ()
64 mv' :: gargdata -> parameters -> parameters -> IO ()
66 -------------------------------------------------------------------
67 -- | Deprecated Class, use GargDB instead
68 class SaveFile a where
69 saveFile' :: FilePath -> a -> IO ()
71 class ReadFile a where
72 readFile' :: FilePath -> IO a
74 -------------------------------------------------------------------
75 -------------------------------------------------------------------
76 type GargFilePath = (FolderPath, FileName)
78 type FolderPath = FilePath
79 type FileName = FilePath
81 --------------------------------
83 dataFilePath :: (ToJSON a) => a -> GargFilePath
84 dataFilePath = toPath . hash . show . toJSON
86 randomFilePath :: ( MonadReader env m
91 (foldPath, fileName) <- liftBase
96 pure (foldPath, fileName)
99 -- | toPath' : how to hash text to path
101 >>> toPath' (1,2) ("","helloword")
104 >>> toPath' (2,2) ("","helloword")
107 >>> toPath' (2,3) ("","helloword")
110 toPath :: Text -> (FolderPath, FileName)
111 toPath tx = both Text.unpack $ toPath' (2,3) ("", tx)
113 toPath' :: (Int,Int) -> (Text,Text) -> (Text,Text)
114 toPath' (n,m) (t,x) = foldl' (\tx _ -> toPath'' m tx) (t,x) [1..n]
116 toPath'' :: Int -> (Text, Text) -> (Text, Text)
117 toPath'' n (fp,fn) = (fp'',fn')
119 (fp',fn') = Text.splitAt n fn
120 fp'' = Text.intercalate "/" [fp,fp']
122 -------------------------------------------------------------------
123 type DataPath = FilePath
124 toFilePath :: FilePath -> FilePath -> FilePath
125 toFilePath fp1 fp2 = fp1 <> "/" <> fp2
127 -------------------------------------------------------------------
130 -- | For example, this write file with a random filepath
131 -- better use a hash of json of Type used to parameter as input
133 writeFile :: ( MonadReader env m
140 dataPath <- view $ hasConfig . gc_datafilepath
142 (foldPath, fileName) <- randomFilePath
144 let filePath = toFilePath foldPath fileName
145 dataFoldPath = toFilePath dataPath foldPath
146 dataFileName = toFilePath dataPath filePath
148 _ <- liftBase $ createDirectoryIfMissing True dataFoldPath
149 _ <- liftBase $ saveFile' dataFileName a
155 -- | Example to read a file with Type
156 readFile :: ( MonadReader env m
163 dataPath <- view $ hasConfig . gc_datafilepath
164 liftBase $ readFile' $ toFilePath dataPath fp
168 rmFile :: ( MonadReader env m
173 rmFile = onDisk_1 SD.removeFile
175 cpFile :: (MonadReader env m, MonadBase IO m, HasConfig env)
176 => FilePath -> FilePath -> m ()
177 cpFile = onDisk_2 SD.copyFile
181 mvFile :: (MonadReader env m, MonadBase IO m, HasConfig env)
182 => FilePath -> FilePath -> m ()
188 ------------------------------------------------------------------------
189 onDisk_1 :: ( MonadReader env m
193 => (FilePath -> IO ()) -> FilePath -> m ()
194 onDisk_1 action fp = do
195 dataPath <- view $ hasConfig . gc_datafilepath
196 liftBase $ action (toFilePath dataPath fp) `catch` handleExists
199 | isDoesNotExistError e = return ()
200 | otherwise = throwIO e
203 onDisk_2 :: ( MonadReader env m
207 => (FilePath -> FilePath -> IO ())
211 onDisk_2 action fp1 fp2 = do
212 dataPath <- view $ hasConfig . gc_datafilepath
213 let fp1' = toFilePath dataPath fp1
214 fp2' = toFilePath dataPath fp2
215 liftBase $ action fp1' fp2' `catch` handleExists
218 | isDoesNotExistError e = return ()
219 | otherwise = throwIO e
220 ------------------------------------------------------------------------
222 ------------------------------------------------------------------------
224 shuffle :: MonadRandom m => [a] -> m [a]
225 shuffle ns = SRS.shuffleM ns
226 --------------------------------------------------------------------------
228 -- TODO gargDB instance for NodeType
229 data NodeToHash = NodeToHash { nodeType :: NodeType