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)
14 module Gargantext.Prelude.Utils
18 import Control.Exception
19 import Control.Lens (view)
20 import Control.Monad.Random.Class (MonadRandom)
21 import Control.Monad.Reader (MonadReader)
22 import Data.Aeson (ToJSON, toJSON)
23 import Data.Text (Text)
24 import Data.Tuple.Extra (both)
25 import GHC.IO (FilePath)
26 import Gargantext.Database.Admin.Types.Node (NodeId, NodeType)
27 import Gargantext.Database.Prelude (HasConfig(..))
28 import Gargantext.Prelude
29 import Gargantext.Prelude.Config
30 import Gargantext.Prelude.Crypto.Hash
31 import System.Directory (createDirectoryIfMissing)
32 import System.IO.Error
33 import System.Random (newStdGen)
34 import qualified Data.Text as Text
35 import qualified System.Directory as SD
36 import qualified System.Random.Shuffle as SRS
38 -------------------------------------------------------------------
39 -- | Main Class to use (just declare needed functions)
42 read :: FilePath -> IO a
44 rm :: (a, FilePath) -> IO ()
45 mv :: (a, FilePath) -> FilePath -> IO ()
48 -- | Why not this class too ?
49 class ToJSON parameters => GargDB' parameters gargdata where
50 write' :: parameters -> gargdata -> IO ()
51 read' :: parameters -> IO gargdata
53 rm' :: gargdata -> parameters -> IO ()
54 mv' :: gargdata -> parameters -> parameters -> IO ()
56 -------------------------------------------------------------------
57 -- | Deprecated Class, use GargDB instead
58 class SaveFile a where
59 saveFile' :: FilePath -> a -> IO ()
61 class ReadFile a where
62 readFile' :: FilePath -> IO a
64 -------------------------------------------------------------------
65 -------------------------------------------------------------------
66 type GargFilePath = (FolderPath, FileName)
68 type FolderPath = FilePath
69 type FileName = FilePath
71 --------------------------------
73 dataFilePath :: (ToJSON a) => a -> GargFilePath
74 dataFilePath = toPath . hash . show . toJSON
76 randomFilePath :: ( MonadReader env m
81 (foldPath, fileName) <- liftBase
86 pure (foldPath, fileName)
89 -- | toPath' : how to hash text to path
91 >>> toPath' (1,2) ("","helloword")
94 >>> toPath' (2,2) ("","helloword")
97 >>> toPath' (2,3) ("","helloword")
100 toPath :: Text -> (FolderPath, FileName)
101 toPath tx = both Text.unpack $ toPath' (2,3) ("", tx)
103 toPath' :: (Int,Int) -> (Text,Text) -> (Text,Text)
104 toPath' (n,m) (t,x) = foldl' (\tx _ -> toPath'' m tx) (t,x) [1..n]
106 toPath'' :: Int -> (Text, Text) -> (Text, Text)
107 toPath'' n (fp,fn) = (fp'',fn')
109 (fp',fn') = Text.splitAt n fn
110 fp'' = Text.intercalate "/" [fp,fp']
112 -------------------------------------------------------------------
113 type DataPath = FilePath
114 toFilePath :: FilePath -> FilePath -> FilePath
115 toFilePath fp1 fp2 = fp1 <> "/" <> fp2
117 -------------------------------------------------------------------
120 -- | For example, this write file with a random filepath
121 -- better use a hash of json of Type used to parameter as input
123 writeFile :: ( MonadReader env m
130 dataPath <- view $ hasConfig . gc_datafilepath
132 (foldPath, fileName) <- randomFilePath
134 let filePath = toFilePath foldPath fileName
135 dataFoldPath = toFilePath dataPath foldPath
136 dataFileName = toFilePath dataPath filePath
138 _ <- liftBase $ createDirectoryIfMissing True dataFoldPath
139 _ <- liftBase $ saveFile' dataFileName a
145 -- | Example to read a file with Type
146 readFile :: ( MonadReader env m
153 dataPath <- view $ hasConfig . gc_datafilepath
154 liftBase $ readFile' $ toFilePath dataPath fp
158 rmFile :: ( MonadReader env m
163 rmFile = onDisk_1 SD.removeFile
165 cpFile :: (MonadReader env m, MonadBase IO m, HasConfig env)
166 => FilePath -> FilePath -> m ()
167 cpFile = onDisk_2 SD.copyFile
171 mvFile :: (MonadReader env m, MonadBase IO m, HasConfig env)
172 => FilePath -> FilePath -> m ()
178 ------------------------------------------------------------------------
179 onDisk_1 :: ( MonadReader env m
183 => (FilePath -> IO ()) -> FilePath -> m ()
184 onDisk_1 action fp = do
185 dataPath <- view $ hasConfig . gc_datafilepath
186 liftBase $ action (toFilePath dataPath fp) `catch` handleExists
189 | isDoesNotExistError e = return ()
190 | otherwise = throwIO e
193 onDisk_2 :: ( MonadReader env m
197 => (FilePath -> FilePath -> IO ())
201 onDisk_2 action fp1 fp2 = do
202 dataPath <- view $ hasConfig . gc_datafilepath
203 let fp1' = toFilePath dataPath fp1
204 fp2' = toFilePath dataPath fp2
205 liftBase $ action fp1' fp2' `catch` handleExists
208 | isDoesNotExistError e = return ()
209 | otherwise = throwIO e
210 ------------------------------------------------------------------------
212 ------------------------------------------------------------------------
214 shuffle :: MonadRandom m => [a] -> m [a]
215 shuffle ns = SRS.shuffleM ns
216 --------------------------------------------------------------------------
218 -- TODO gargDB instance for NodeType
219 data NodeToHash = NodeToHash { nodeType :: NodeType