Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(bindings/haskell): support is_exist create_dir copy rename delete #2475

Merged
merged 2 commits into from
Jun 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions bindings/haskell/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,14 @@ Test suite logged to:
If you don't want to specify `LIBRARY_PATH` and `LD_LIBRARY_PATH` every time, you can use [`direnv`](https://direnv.net/) to set the environment variable automatically. Add the following to your `.envrc`:

```shell
export LIBRARY_PATH=../../target/debug:LIBRARY_PATH
export LD_LIBRARY_PATH=../../target/debug:LD_LIBRARY_PATH
export LIBRARY_PATH=../../target/debug:$LIBRARY_PATH
export LD_LIBRARY_PATH=../../target/debug:$LD_LIBRARY_PATH
```

If you are using [`Haskell`](https://marketplace.visualstudio.com/items?itemName=haskell.haskell) in VSCode, you may need to add the following configuration to your `settings.json`:

```json
"haskell.serverEnvironment": {
"LIBRARY_PATH": "../../target/debug:LIBRARY_PATH"
"LIBRARY_PATH": "../../target/debug:$LIBRARY_PATH"
},
```
143 changes: 128 additions & 15 deletions bindings/haskell/haskell-src/OpenDAL.hs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,16 @@

module OpenDAL (
Operator,
createOp,
OpenDALError,
ErrorCode (..),
newOp,
readOp,
writeOp,
isExistOp,
createDirOp,
copyOp,
renameOp,
deleteOp,
) where

import Data.ByteString (ByteString)
Expand All @@ -28,16 +35,48 @@ import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HashMap
import Foreign
import Foreign.C.String
import Foreign.C.Types (CChar)
import OpenDAL.FFI

newtype Operator = Operator (Ptr RawOperator)
newtype Operator = Operator (ForeignPtr RawOperator)

data ErrorCode
= FFIError
| Unexpected
| Unsupported
| ConfigInvalid
| NotFound
| PermissionDenied
| IsADirectory
| NotADirectory
| AlreadyExists
| RateLimited
| IsSameFile
deriving (Eq, Show)

data OpenDALError = OpenDALError {errorCode :: ErrorCode, message :: String}
Xuanwo marked this conversation as resolved.
Show resolved Hide resolved
deriving (Eq, Show)

byteSliceToByteString :: ByteSlice -> IO ByteString
byteSliceToByteString (ByteSlice bsDataPtr len) = BS.packCStringLen (bsDataPtr, fromIntegral len)

parseErrorCode :: Int -> ErrorCode
parseErrorCode 1 = FFIError
parseErrorCode 2 = Unexpected
parseErrorCode 3 = Unsupported
parseErrorCode 4 = ConfigInvalid
parseErrorCode 5 = NotFound
parseErrorCode 6 = PermissionDenied
parseErrorCode 7 = IsADirectory
parseErrorCode 8 = NotADirectory
parseErrorCode 9 = AlreadyExists
parseErrorCode 10 = RateLimited
parseErrorCode 11 = IsSameFile
parseErrorCode _ = FFIError

-- | Create a new Operator.
createOp :: String -> HashMap String String -> IO (Either String Operator)
createOp scheme hashMap = do
newOp :: String -> HashMap String String -> IO (Either OpenDALError Operator)
newOp scheme hashMap = do
let keysAndValues = HashMap.toList hashMap
withCString scheme $ \cScheme ->
withMany withCString (map fst keysAndValues) $ \cKeys ->
Expand All @@ -49,39 +88,113 @@ createOp scheme hashMap = do
pokeArray cValuesPtr cValues
c_via_map_ffi cScheme cKeysPtr cValuesPtr (fromIntegral $ length keysAndValues) ffiResultPtr
ffiResult <- peek ffiResultPtr
if success ffiResult
if ffiCode ffiResult == 0
then do
let op = Operator (castPtr $ dataPtr ffiResult)
op <- Operator <$> (newForeignPtr c_free_operator $ castPtr $ dataPtr ffiResult)
return $ Right op
else do
let code = parseErrorCode $ fromIntegral $ ffiCode ffiResult
errMsg <- peekCString (errorMessage ffiResult)
return $ Left errMsg
return $ Left $ OpenDALError code errMsg

readOp :: Operator -> String -> IO (Either String ByteString)
readOp (Operator op) path = (flip ($)) op $ \opptr ->
readOp :: Operator -> String -> IO (Either OpenDALError ByteString)
readOp (Operator op) path = withForeignPtr op $ \opptr ->
withCString path $ \cPath ->
alloca $ \ffiResultPtr -> do
c_blocking_read opptr cPath ffiResultPtr
ffiResult <- peek ffiResultPtr
if success ffiResult
if ffiCode ffiResult == 0
then do
byteslice <- peek (castPtr $ dataPtr ffiResult)
byte <- byteSliceToByteString byteslice
c_free_byteslice (bsData byteslice) (bsLen byteslice)
return $ Right byte
else do
let code = parseErrorCode $ fromIntegral $ ffiCode ffiResult
errMsg <- peekCString (errorMessage ffiResult)
return $ Left errMsg
return $ Left $ OpenDALError code errMsg

writeOp :: Operator -> String -> ByteString -> IO (Either String ())
writeOp (Operator op) path byte = (flip ($)) op $ \opptr ->
writeOp :: Operator -> String -> ByteString -> IO (Either OpenDALError ())
writeOp (Operator op) path byte = withForeignPtr op $ \opptr ->
withCString path $ \cPath ->
BS.useAsCStringLen byte $ \(cByte, len) ->
alloca $ \ffiResultPtr -> do
c_blocking_write opptr cPath cByte (fromIntegral len) ffiResultPtr
ffiResult <- peek ffiResultPtr
if success ffiResult
if ffiCode ffiResult == 0
then return $ Right ()
else do
let code = parseErrorCode $ fromIntegral $ ffiCode ffiResult
errMsg <- peekCString (errorMessage ffiResult)
return $ Left $ OpenDALError code errMsg

isExistOp :: Operator -> String -> IO (Either OpenDALError Bool)
isExistOp (Operator op) path = withForeignPtr op $ \opptr ->
withCString path $ \cPath ->
alloca $ \ffiResultPtr -> do
c_blocking_is_exist opptr cPath ffiResultPtr
ffiResult <- peek ffiResultPtr
if ffiCode ffiResult == 0
then do
-- For Bool type, the memory layout is different between C and Haskell.
val <- peek ((castPtr $ dataPtr ffiResult) :: Ptr CChar)
let isExist = val /= 0
return $ Right isExist
else do
let code = parseErrorCode $ fromIntegral $ ffiCode ffiResult
errMsg <- peekCString (errorMessage ffiResult)
return $ Left $ OpenDALError code errMsg

createDirOp :: Operator -> String -> IO (Either OpenDALError ())
createDirOp (Operator op) path = withForeignPtr op $ \opptr ->
withCString path $ \cPath ->
alloca $ \ffiResultPtr -> do
c_blocking_create_dir opptr cPath ffiResultPtr
ffiResult <- peek ffiResultPtr
if ffiCode ffiResult == 0
then return $ Right ()
else do
let code = parseErrorCode $ fromIntegral $ ffiCode ffiResult
errMsg <- peekCString (errorMessage ffiResult)
return $ Left $ OpenDALError code errMsg

copyOp :: Operator -> String -> String -> IO (Either OpenDALError ())
copyOp (Operator op) srcPath dstPath = withForeignPtr op $ \opptr ->
withCString srcPath $ \cSrcPath ->
withCString dstPath $ \cDstPath ->
alloca $ \ffiResultPtr -> do
c_blocking_copy opptr cSrcPath cDstPath ffiResultPtr
ffiResult <- peek ffiResultPtr
if ffiCode ffiResult == 0
then return $ Right ()
else do
let code = parseErrorCode $ fromIntegral $ ffiCode ffiResult
errMsg <- peekCString (errorMessage ffiResult)
return $ Left errMsg
return $ Left $ OpenDALError code errMsg

renameOp :: Operator -> String -> String -> IO (Either OpenDALError ())
renameOp (Operator op) srcPath dstPath = withForeignPtr op $ \opptr ->
withCString srcPath $ \cSrcPath ->
withCString dstPath $ \cDstPath ->
alloca $ \ffiResultPtr -> do
c_blocking_rename opptr cSrcPath cDstPath ffiResultPtr
ffiResult <- peek ffiResultPtr
if ffiCode ffiResult == 0
then return $ Right ()
else do
let code = parseErrorCode $ fromIntegral $ ffiCode ffiResult
errMsg <- peekCString (errorMessage ffiResult)
return $ Left $ OpenDALError code errMsg

deleteOp :: Operator -> String -> IO (Either OpenDALError ())
deleteOp (Operator op) path = withForeignPtr op $ \opptr ->
withCString path $ \cPath ->
alloca $ \ffiResultPtr -> do
c_blocking_delete opptr cPath ffiResultPtr
ffiResult <- peek ffiResultPtr
if ffiCode ffiResult == 0
then return $ Right ()
else do
let code = parseErrorCode $ fromIntegral $ ffiCode ffiResult
errMsg <- peekCString (errorMessage ffiResult)
return $ Left $ OpenDALError code errMsg
21 changes: 13 additions & 8 deletions bindings/haskell/haskell-src/OpenDAL/FFI.hs
Original file line number Diff line number Diff line change
Expand Up @@ -25,30 +25,30 @@ import Foreign.C.Types
data RawOperator

data FFIResult = FFIResult
{ success :: Bool
{ ffiCode :: CUInt
, dataPtr :: Ptr ()
, errorMessage :: CString
}
deriving (Show)

instance Storable FFIResult where
sizeOf _ = sizeOf (undefined :: CSize) + sizeOf (undefined :: Ptr ()) + sizeOf (undefined :: CString)
alignment _ = alignment (undefined :: CIntPtr)
alignment _ = alignment (undefined :: CSize)
peek ptr = do
s <- ((/= (0 :: CSize)) <$> peekByteOff ptr successOffset)
s <- peekByteOff ptr codeOffset
d <- peekByteOff ptr dataPtrOffset
errMsg <- peekByteOff ptr errorMessageOffset
return $ FFIResult s d errMsg
where
successOffset = 0
codeOffset = 0
dataPtrOffset = sizeOf (undefined :: CSize)
errorMessageOffset = dataPtrOffset + sizeOf (undefined :: Ptr ())
poke ptr (FFIResult s d errMsg) = do
pokeByteOff ptr successOffset (fromBool s :: CSize)
pokeByteOff ptr codeOffset s
pokeByteOff ptr dataPtrOffset d
pokeByteOff ptr errorMessageOffset errMsg
where
successOffset = 0
codeOffset = 0
dataPtrOffset = sizeOf (undefined :: CSize)
errorMessageOffset = dataPtrOffset + sizeOf (undefined :: Ptr ())

Expand All @@ -59,7 +59,7 @@ data ByteSlice = ByteSlice

instance Storable ByteSlice where
sizeOf _ = sizeOf (undefined :: Ptr CChar) + sizeOf (undefined :: CSize)
alignment _ = alignment (undefined :: Ptr CChar)
alignment _ = alignment (undefined :: CSize)
peek ptr = do
bsDataPtr <- peekByteOff ptr dataOffset
len <- peekByteOff ptr lenOffset
Expand All @@ -80,4 +80,9 @@ foreign import ccall "via_map_ffi"
foreign import ccall "&free_operator" c_free_operator :: FunPtr (Ptr RawOperator -> IO ())
foreign import ccall "free_byteslice" c_free_byteslice :: Ptr CChar -> CSize -> IO ()
foreign import ccall "blocking_read" c_blocking_read :: Ptr RawOperator -> CString -> Ptr FFIResult -> IO ()
foreign import ccall "blocking_write" c_blocking_write :: Ptr RawOperator -> CString -> Ptr CChar -> CSize -> Ptr FFIResult -> IO ()
foreign import ccall "blocking_write" c_blocking_write :: Ptr RawOperator -> CString -> Ptr CChar -> CSize -> Ptr FFIResult -> IO ()
foreign import ccall "blocking_is_exist" c_blocking_is_exist :: Ptr RawOperator -> CString -> Ptr FFIResult -> IO ()
foreign import ccall "blocking_create_dir" c_blocking_create_dir :: Ptr RawOperator -> CString -> Ptr FFIResult -> IO ()
foreign import ccall "blocking_copy" c_blocking_copy :: Ptr RawOperator -> CString -> CString -> Ptr FFIResult -> IO ()
foreign import ccall "blocking_rename" c_blocking_rename :: Ptr RawOperator -> CString -> CString -> Ptr FFIResult -> IO ()
foreign import ccall "blocking_delete" c_blocking_delete :: Ptr RawOperator -> CString -> Ptr FFIResult -> IO ()
2 changes: 1 addition & 1 deletion bindings/haskell/opendal-hs.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ library
default-language: Haskell2010
extra-libraries: opendal_hs
ghc-options: -Wall
build-depends:
build-depends:
base >=4.10.0.0 && <5,
unordered-containers >=0.2.0.0,
bytestring >=0.11.0.0
Expand Down
Loading