packages feed

cloudyfs-0.0.1: cloudyfs.hs

module Main where

import Data.DateTime (DateTime, getCurrentTime)

import Data.Maybe
import Data.IORef

import qualified Data.Map as M

import qualified Data.ByteString.Char8 as B
import Foreign.C.Error
import System.Posix.Types
import System.Posix.Files
import System.Posix.IO

import System.Fuse hiding (RegularFile, Directory)
import qualified System.Fuse as Fuse

import System.CloudyFS.Expiring
import System.CloudyFS.FileSystem
import System.CloudyFS.Path
import System.CloudyFS.Weather

type FileType = Weather
type HT = FileType
type State = IORef (FileSystem FileType)

mkFileSystem :: IO State
mkFileSystem = newIORef emptyFS

main :: IO ()
main = do
  fs <- mkFileSystem
  fuseMain (cloudyFSOps fs) defaultExceptionHandler

cloudyFSOps :: State -> FuseOperations HT
cloudyFSOps fs = defaultFuseOps { fuseGetFileStat = cloudyGetFileStat fs
                            , fuseOpen = cloudyOpen fs
                            , fuseRead = cloudyRead
                            , fuseOpenDirectory = cloudyOpenDirectory fs
                            , fuseReadDirectory = cloudyReadDirectory fs
                            , fuseGetFileSystemStats = cloudyGetFileSystemStats
                            }

dirStat :: FuseContext -> FileStat
dirStat ctx = FileStat
  { statEntryType = Fuse.Directory
  , statFileMode = foldr1 unionFileModes
                     [ ownerReadMode
                     , ownerExecuteMode
                     , groupReadMode
                     , groupExecuteMode
                     , otherReadMode
                     , otherExecuteMode
                     ]
  , statLinkCount = 2
  , statFileOwner = fuseCtxUserID ctx
  , statFileGroup = fuseCtxGroupID ctx
  , statSpecialDeviceID = 0
  , statFileSize = 4096
  , statBlocks = 1
  , statAccessTime = 0
  , statModificationTime = 0
  , statStatusChangeTime = 0
  }

fileStat :: Weather -> FuseContext -> FileStat
fileStat w ctx = FileStat 
  { statEntryType = Fuse.RegularFile
  , statFileMode = foldr1 unionFileModes
                     [ ownerReadMode
                     , groupReadMode
                     , otherReadMode
                     ]
  , statLinkCount = 1
  , statFileOwner = fuseCtxUserID ctx
  , statFileGroup = fuseCtxGroupID ctx
  , statSpecialDeviceID = 0
  , statFileSize = fromIntegral $ B.length $ asByteString w
  , statBlocks = 1
  , statAccessTime = 0
  , statModificationTime = epochTime w 
  , statStatusChangeTime = 0
  }

stat :: FuseContext -> FileSystem FileType -> FileStat
stat c (SystemDirectory _) = dirStat c
stat c (SystemFile w) = fileStat w c

cloudyGetFileStat :: State -> FilePath -> IO (Either Errno FileStat)
cloudyGetFileStat stateRef p = do
  t <- getCurrentTime
  ctx <- getFuseContext
  state <- readIORef stateRef
  case getFile t state path of
    Just fs -> return $ Right $ stat ctx fs
    Nothing ->
      case mapMaybe (\ x -> x p) fileSpecifications of
        (_, RegularFile action):[] -> do
          result <- action path
          case result of
            Nothing -> return err
            Just r ->
              case mkfile state path r of
                Just f -> do
                  writeIORef stateRef f
                  cloudyGetFileStat stateRef p
                Nothing -> return err
        (_, DirectoryFile):[] -> do
          case mkdir state path of
            Just f -> do
              writeIORef stateRef f
              cloudyGetFileStat stateRef p
            Nothing -> return err
        _ -> return err
   where path = normalisePath p
         err = Left eNFILE

getDirContents ::
  DateTime ->
  FileSystem FileType ->
  [FilePart] ->
  FuseContext ->
  [(FilePath, FileStat)]
getDirContents t fs path ctx =
  case lsdir fs path of
    Nothing -> []
    Just m -> M.foldWithKey accumulator [] m
  where
    accumulator k f l =
      if isValid t f
        then (k, stat ctx f):l 
        else l

cloudyOpenDirectory :: State -> FilePath -> IO Errno
cloudyOpenDirectory stateRef path = do
  case mapMaybe (\ x -> x path) fileSpecifications of
    (fp, DirectoryFile):[] -> do
      st <- readIORef stateRef
      case mkdir st fp of
        Just f -> do
          writeIORef stateRef f
          return $ eOK
        Nothing -> return $ eOK
    _ -> return $ eEXIST
 
cloudyReadDirectory :: State -> FilePath -> IO (Either Errno [(FilePath, FileStat)])
cloudyReadDirectory stateRef path = do
    t <- getCurrentTime
    ctx <- getFuseContext
    case mapMaybe (\ x -> x path) fileSpecifications of
      (fp, DirectoryFile):_ -> do
        state <- readIORef stateRef
        return $ Right $ [(".", dirStat ctx)
                        ,("..", dirStat ctx)
                        ] ++ (getDirContents t state fp ctx)
      _ -> return $ Left eEXIST

cloudyOpen :: State -> FilePath -> OpenMode -> OpenFileFlags -> IO (Either Errno HT)
cloudyOpen stateRef path ReadOnly flags = do
   t <- getCurrentTime
   state <- readIORef stateRef
   case getFile t state (normalisePath path) of
     Nothing -> fetchAndPut state
     Just (SystemFile a) -> 
       if isValid t a
         then return $ Right a
         else fetchAndPut state
     _ -> return $ Left eNFILE
  where
    fetchAndPut state =
      case mapMaybe (\ x -> x path) fileSpecifications of
        (fp, RegularFile act):[]  -> do
          w <- act fp
          case w of
            Nothing -> return $ Left eNFILE
            Just r -> 
              case mkfile state fp r of
                Nothing -> return $ Left eACCES
                Just f -> do
                  writeIORef stateRef f
                  cloudyOpen stateRef path ReadOnly flags
        _ -> return $ Left eNFILE

cloudyOpen _ _ _ _ = return $ Left eACCES

cloudyRead :: FilePath -> HT -> ByteCount -> FileOffset -> IO (Either Errno B.ByteString)
cloudyRead _ ht byteCount offset =
  return $ Right $ B.take (fromIntegral byteCount) $ B.drop (fromIntegral offset) $ asByteString ht

cloudyGetFileSystemStats :: String -> IO (Either Errno FileSystemStats)
cloudyGetFileSystemStats _ =
  return $ Right $ FileSystemStats
    { fsStatBlockSize = 512
    , fsStatBlockCount = 1
    , fsStatBlocksFree = 1
    , fsStatBlocksAvailable = 1
    , fsStatFileCount = 5
    , fsStatFilesFree = 10
    , fsStatMaxNameLength = 255
    }