<commit_before>module Main where

import Util.Options
import Util.Sanity
import Core.Datatypes
import Core.Constants

import qualified Data.ByteString.Char8 as B
import System.Posix.Types
import System.Posix.Files
import System.FilePath.Posix
import System.Posix.IO
import System.Directory
import System.Console.GetOpt
import System.Exit
import System.Environment
import System.IO
import System.Fuse
import System(getArgs)
import Control.Monad
import Data.Maybe
import Data.List (nub)
import Data.ByteString.Char8 (pack,unpack)

{-
 - TODO: we can fill in actualPath now. do we want to though?
 -}
{-
OPEN QUESTIONS:
how to parse a file, then return as bytestring in which one can seek etc?

How should I present SymLinks?
-}

{- TODO(nathan)
* There's currently no real error checking whatsoever.
* Add funionWrite
* Thread in logging
* Can I delete "funionVirtualPath"?
* need to add unit tests
-}


dirContents :: FilePath -> IO [FilePath]
dirContents = fmap (filter (`notElem` [".",".."])) . getDirectoryContents

-- TODO: nicer logging, to a variable filename (State / Reader monad?)
debug :: String -> IO ()
debug str = appendFile "/tmp/foo" (str ++ "\n")

fileExists, dirExists :: FilePath -> FilePath -> IO Bool
fileExists path name = doesFileExist $ path </> name
dirExists  path name = doesDirectoryExist $ path </> name


getFileStats, getDirStats :: FilePath-> FilePath -> IO FunionFS
getFileStats path name = do p <- canonicalizePath $ path </> name
                            getStats RegularFile p
getDirStats  path name = do p <- canonicalizePath $ path </> name
                            getStats Directory p


getStats :: EntryType -> FilePath -> IO FunionFS
getStats entrytype uri = do
  status <- getFileStatus uri
  children <- case entrytype of
    Directory -> do contents <- dirContents uri
                    -- list of files
                    files    <- filterM (fileExists uri) contents
                    fileList <- mapM (getFileStats uri) files
                    -- list of directories
                    dirs     <- filterM (dirExists uri) contents
                    dirList  <- mapM (getDirStats uri) dirs
                    return $ dirList ++ fileList
    RegularFile -> return []
  return FunionFS {
      funionEntryName   = takeFileName uri
    , funionActualPath  = uri
    , funionVirtualPath = ""
    , funionFileStat    = FileStat
        { statEntryType = entrytype
        , statFileMode  = fileMode status
        , statLinkCount = linkCount status
        , statFileOwner = fileOwner status
        , statFileGroup = fileGroup status
        , statSpecialDeviceID = specialDeviceID status
        , statFileSize  = fileSize status
        , statBlocks    = fromIntegral $ fileSize status `div` 1024
        , statAccessTime= accessTime status
        , statModificationTime = modificationTime status
        , statStatusChangeTime = statusChangeTime status
        }
     , funionContents = children
  }

{-
 - this is a rather important function for the system.
 - it finds a file by name. the idea is, if a file or directory
 - exists in the conf tree, return that.
 -}
funionLookUp :: Conf -> FilePath -> IO (Maybe FunionFS)
funionLookUp (C confdir) path = do
    confVersion <- statIfExists confdir path
    case confVersion of
        Just stats ->  do let oldFileStat = funionFileStat stats
                              -- TODO set owner to current user
                              newFileStat = oldFileStat {statFileMode = 0o400} -- read only for the owner
                              stats'      = stats {funionFileStat = newFileStat}
                          return $ Just stats'
        Nothing -> return Nothing


statIfExists :: FilePath -> FilePath -> IO (Maybe FunionFS)
statIfExists dir file = do
                          existsAsDir <- dir `dirExists` file
                          if existsAsDir then
                                      do debug $ file ++ " is a dir in "++dir
                                         stats <- dir `getDirStats` file
                                         return $ Just stats -- this already contains the children
                             else
                                      do existsAsFile <- dir `fileExists` file
                                         if existsAsFile then do
                                                       debug $ file ++ " is a file in " ++ dir
                                                       stats <- dir `getFileStats` file
                                                       return $ Just stats
                                            else return Nothing

funionFSOps :: Conf -> FuseOperations Fd
funionFSOps dir =
  defaultFuseOps {
                   fuseGetFileStat        = funionGetFileStat dir
                 , fuseGetFileSystemStats = funionGetFileSystemStats dir
                 , fuseOpenDirectory      = funionOpenDirectory dir
                 , fuseReadDirectory      = funionReadDirectory dir
                 , fuseRead               = funionRead dir
                 , fuseFlush              = funionFlush dir
                 , fuseOpen               = funionOpen dir
                 }


funionGetFileStat :: Conf -> FilePath -> IO (Either Errno FileStat)
funionGetFileStat dp (_:dir) = do
  lookup <- funionLookUp dp dir
  case lookup of
    Just file -> return $ Right $ funionFileStat file
    Nothing   -> return $ Left eNOENT



funionOpen :: Conf -> FilePath -> OpenMode -> OpenFileFlags -> IO (Either Errno Fd)
funionOpen dirs (_:path) ReadOnly flags = do
  file <- funionLookUp dirs path
  case file of
    Just f -> do
      fd <- openFd (funionActualPath f) ReadOnly Nothing defaultFileFlags
      return (Right fd)
    Nothing -> return (Left eNOENT)
funionOpen dirs (_:path) mode flags = return (Left eNOENT)

-- What if 'fd' is no good?  What will happen?
funionFlush :: Conf -> FilePath -> Fd -> IO Errno
funionFlush _ _ fd = do closeFd fd; return eOK


funionOpenDirectory :: Conf -> FilePath -> IO Errno
funionOpenDirectory (C confdir) (_:path) = do
  extantDirs <- confdir `dirExists` path
  return $ if extantDirs then eOK else eNOENT



funionReadDirectory :: Conf -> FilePath -> IO (Either Errno [(FilePath, FileStat)])
funionReadDirectory dirs@(C confdir) (_:dir) = do
  entry <- funionLookUp dirs dir
  case entry of
    Nothing -> return $ Left eNOENT
    Just e -> do
        let contents = funionContents e
        let dirContents = map (\x -> (funionEntryName x :: String , funionFileStat x)) contents
        dotstats <- confdir `getDirStats` dir
        return $ Right $ [ (".", funionFileStat dotstats), ("..", dirStat)] ++ dirContents

funionRead  :: Conf -> FilePath -> Fd -> ByteCount -> FileOffset -> IO (Either Errno B.ByteString)
funionRead dirsToUnion (_:path) fd byteCount offset = do<commit_msg>added list of TODO functions<commit_after>module Main where

import Util.Options
import Util.Sanity
import Core.Datatypes
import Core.Constants

import qualified Data.ByteString.Char8 as B
import System.Posix.Types
import System.Posix.Files
import System.FilePath.Posix
import System.Posix.IO
import System.Directory
import System.Console.GetOpt
import System.Exit
import System.Environment
import System.IO
import System.Fuse
import System(getArgs)
import Control.Monad
import Data.Maybe
import Data.List (nub)
import Data.ByteString.Char8 (pack,unpack)

{-
 - TODO: we can fill in actualPath now. do we want to though?
 -}
{-
OPEN QUESTIONS:
how to parse a file, then return as bytestring in which one can seek etc?

How should I present SymLinks?
-}

{- TODO(nathan)
* There's currently no real error checking whatsoever.
* Add funionWrite
* Thread in logging
* Can I delete "funionVirtualPath"?
* need to add unit tests
-}


dirContents :: FilePath -> IO [FilePath]
dirContents = fmap (filter (`notElem` [".",".."])) . getDirectoryContents

-- TODO: nicer logging, to a variable filename (State / Reader monad?)
debug :: String -> IO ()
debug str = appendFile "/tmp/foo" (str ++ "\n")

fileExists, dirExists :: FilePath -> FilePath -> IO Bool
fileExists path name = doesFileExist $ path </> name
dirExists  path name = doesDirectoryExist $ path </> name


getFileStats, getDirStats :: FilePath-> FilePath -> IO FunionFS
getFileStats path name = do p <- canonicalizePath $ path </> name
                            getStats RegularFile p
getDirStats  path name = do p <- canonicalizePath $ path </> name
                            getStats Directory p


getStats :: EntryType -> FilePath -> IO FunionFS
getStats entrytype uri = do
  status <- getFileStatus uri
  children <- case entrytype of
    Directory -> do contents <- dirContents uri
                    -- list of files
                    files    <- filterM (fileExists uri) contents
                    fileList <- mapM (getFileStats uri) files
                    -- list of directories
                    dirs     <- filterM (dirExists uri) contents
                    dirList  <- mapM (getDirStats uri) dirs
                    return $ dirList ++ fileList
    RegularFile -> return []
  return FunionFS {
      funionEntryName   = takeFileName uri
    , funionActualPath  = uri
    , funionVirtualPath = ""
    , funionFileStat    = FileStat
        { statEntryType = entrytype
        , statFileMode  = fileMode status
        , statLinkCount = linkCount status
        , statFileOwner = fileOwner status
        , statFileGroup = fileGroup status
        , statSpecialDeviceID = specialDeviceID status
        , statFileSize  = fileSize status
        , statBlocks    = fromIntegral $ fileSize status `div` 1024
        , statAccessTime= accessTime status
        , statModificationTime = modificationTime status
        , statStatusChangeTime = statusChangeTime status
        }
     , funionContents = children
  }

{-
 - this is a rather important function for the system.
 - it finds a file by name. the idea is, if a file or directory
 - exists in the conf tree, return that.
 -}
funionLookUp :: Conf -> FilePath -> IO (Maybe FunionFS)
funionLookUp (C confdir) path = do
    confVersion <- statIfExists confdir path
    case confVersion of
        Just stats ->  do let oldFileStat = funionFileStat stats
                              -- TODO set owner to current user
                              newFileStat = oldFileStat {statFileMode = 0o400} -- read only for the owner
                              stats'      = stats {funionFileStat = newFileStat}
                          return $ Just stats'
        Nothing -> return Nothing


statIfExists :: FilePath -> FilePath -> IO (Maybe FunionFS)
statIfExists dir file = do
                          existsAsDir <- dir `dirExists` file
                          if existsAsDir then
                                      do debug $ file ++ " is a dir in "++dir
                                         stats <- dir `getDirStats` file
                                         return $ Just stats -- this already contains the children
                             else
                                      do existsAsFile <- dir `fileExists` file
                                         if existsAsFile then do
                                                       debug $ file ++ " is a file in " ++ dir
                                                       stats <- dir `getFileStats` file
                                                       return $ Just stats
                                            else return Nothing

funionFSOps :: Conf -> FuseOperations Fd
funionFSOps dir =
  defaultFuseOps {
                   fuseGetFileStat        = funionGetFileStat dir
                 , fuseGetFileSystemStats = funionGetFileSystemStats dir
                 , fuseOpenDirectory      = funionOpenDirectory dir
                 , fuseReadDirectory      = funionReadDirectory dir
                 , fuseRead               = funionRead dir
                 , fuseOpen               = funionOpen dir
                 , fuseRelease            = funionRelease dir
                 --, fuseSynchronizeFile    = funionSynchronizeFile dir -- this should reload/reparse the file
                 --, fuseReadSymbolicLink   = funionReadSymbolicLink dir -- symlinks already work out the box, but don't present nicely in `ls -la`
                 }

{- possible FUSE operations:
 -- | Implements Unix98 @pread(2)@. It differs from
 --   'System.Posix.Files.fdRead' by the explicit 'FileOffset' argument.
 --   The @fuse.h@ documentation stipulates that this \"should return
 --   exactly the number of bytes requested except on EOF or error,
 --   otherwise the rest of the data will be substituted with zeroes.\"
 fuseRead :: FilePath -> fh -> ByteCount -> FileOffset
          -> IO (Either Errno B.ByteString),
 --
 -- | Check file access permissions; this will be called for the
 --   access() system call.  If the @default_permissions@ mount option
 --   is given, this method is not called.  This method is also not
 --   called under Linux kernel versions 2.4.x
 fuseAccess :: FilePath -> Int -> IO Errno, -- FIXME present a nicer type to Haskell
 -}


funionGetFileStat :: Conf -> FilePath -> IO (Either Errno FileStat)
funionGetFileStat dp (_:dir) = do
  lookup <- funionLookUp dp dir
  case lookup of
    Just file -> return $ Right $ funionFileStat file
    Nothing   -> return $ Left eNOENT



funionOpen :: Conf -> FilePath -> OpenMode -> OpenFileFlags -> IO (Either Errno Fd)
funionOpen dirs (_:path) ReadOnly flags = do
  file <- funionLookUp dirs path
  case file of
    Just f -> do
      fd <- openFd (funionActualPath f) ReadOnly Nothing defaultFileFlags
      return (Right fd)
    Nothing -> return (Left eNOENT)
funionOpen dirs (_:path) mode flags = return (Left eACCES)

funionRelease :: Conf -> FilePath -> Fd -> IO ()
funionRelease _ _ fd = closeFd fd

-- TODO: check permissions with `getPermissions`
funionOpenDirectory :: Conf -> FilePath -> IO Errno
funionOpenDirectory (C confdir) (_:path) = do
  extantDirs <- confdir `dirExists` path
  return $ if extantDirs then eOK else eNOENT



funionReadDirectory :: Conf -> FilePath -> IO (Either Errno [(FilePath, FileStat)])
funionReadDirectory dirs@(C confdir) (_:dir) = do
  entry <- funionLookUp dirs dir
  case entry of
    Nothing -> return $ Left eNOENT
    Just e -> do
        let contents = funionContents e
        let dirContents = map (\x -> (funionEntryName x :: String , funionFileStat x)) contents
        dotstats <- confdir `getDirStats` dir
        return $ Right $ [ (".", funionFileStat dotstats), ("..", dirStat)] ++ dirContents

funionRead  :: Conf -> FilePath -> Fd -> ByteCount -> FileOffset -> IO (Either Errno B.ByteString)
funionRead dirsToUnion (_:path) fd byteCount offset = do
{'fraction_non_alphanumeric': 0.07694395440358258, 'fraction_numerical': 0.001696295291084272, 'mean_word_length': 2.0277321281840592, 'pattern_counts': {'":': 0, '<': 51, '<?xml version=': 0, '>': 101, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 38, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2571571', 'n_tokens_mistral': 3907, 'n_tokens_neox': 3750, 'n_words': 1391}