packages feed

concrete-haskell-0.1.0.16: src/Data/Concrete/Services/Fetch.hs

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE PackageImports #-}
{-|
Description: Implementations of FetchCommunicationService for various backends
-}

module Data.Concrete.Services.Fetch ( HandleFetch(..)
                                    , makeHandleFetch
                                    , ZipFetch(..)
                                    , makeZipFetch
                                    , TarFetch(..)
                                    , makeTarFetch                                    
                                    , process
                                    , fetchDirect
                                    ) where

import System.IO (Handle)
import qualified Data.Vector as V
import qualified Codec.Archive.Tar       as Tar
import qualified Codec.Archive.Tar.Entry as Tar
import qualified Codec.Archive.Tar.Index as Tar
import qualified Data.ByteString as SBS
import qualified Data.ByteString.Lazy as LBS
import qualified Codec.Compression.GZip as GZip
import qualified Codec.Compression.BZip as BZip
import qualified "zip-conduit" Codec.Archive.Zip       as ZipC
import qualified "zip" Codec.Archive.Zip       as Zip
import qualified Codec.Archive.Tar       as Tar
import qualified Codec.Archive.Tar.Entry as Tar
import qualified Codec.Archive.Tar.Index as TarIndex
import Codec.Archive.Tar.Index (TarIndex)
--import Data.Either (fromRight)
import System.IO (openFile, IOMode(..), hTell)
import System.FilePath (takeExtension)
import Data.Either (rights)
import qualified Data.Map.Strict as Map
import Data.Map.Strict (Map)
import Data.Concrete.Utils (stringToComm)
import Data.Concrete.Services (Compression(..))
import Data.Concrete.Autogen.Communication_Types (Communication(..), default_Communication, read_Communication, write_Communication)
import Data.Concrete.Autogen.Service_Iface (Service_Iface(about, alive))
import Data.Concrete.Autogen.Services_Types (ServiceInfo(..))
import Data.Concrete.Autogen.Access_Types (FetchResult(..), default_FetchResult, FetchRequest(..))
import Data.Concrete.Autogen.FetchCommunicationService_Iface (FetchCommunicationService_Iface(fetch, getCommunicationIDs, getCommunicationCount))
import Data.Concrete.Autogen.FetchCommunicationService (process)
import Data.List (genericDrop, genericTake, genericLength)
import Data.Text.Lazy (Text, pack, unpack)
import Path.IO (resolveFile')
import Control.Monad.IO.Class (liftIO)
import qualified Data.Text.Lazy as T
import Control.Monad (liftM, when)
import Conduit
import Data.Conduit
import Data.Conduit.Zlib (ungzip)
import Data.Conduit.BZlib (bunzip2)
import qualified Crypto.Hash.Conduit as CH
import qualified Data.Conduit.Tar    as CT
import Control.DeepSeq

fetchDirect :: FetchCommunicationService_Iface a => a -> IO [Communication]
fetchDirect f = do
  n <- getCommunicationCount f
  is <- getCommunicationIDs f 0 n
  FetchResult cs <- fetch f (FetchRequest is Nothing)
  return $ V.toList cs

-- | Handle-based Fetch backend
newtype HandleFetch = HandleFetch Handle

instance Service_Iface HandleFetch where
  about _ = return $ ServiceInfo "Handle-backed FetchCommunicationService" "0.0.1" (Just "Haskell implementation")
  alive _ = return True

instance FetchCommunicationService_Iface HandleFetch where
  fetch _ (FetchRequest ii _) = error "unimplemented"
  getCommunicationIDs _ offset count = error "unimplemented"
  getCommunicationCount _ = error "unimplemented"

makeHandleFetch :: String -> IO HandleFetch
makeHandleFetch f = error "unimplemented"

-- | Zip-based Fetch backend
newtype ZipFetch = ZipFetch (String, Map Text String)

instance Service_Iface ZipFetch where
  about _ = return $ ServiceInfo "Zip-backed FetchCommunicationService" "0.0.1" (Just "Haskell implementation")
  alive _ = return True

instance FetchCommunicationService_Iface ZipFetch where
  fetch (ZipFetch (f, ms)) (FetchRequest ii _) = error "" --do
    -- f' <- resolveFile' f
    -- let ids = map T.unpack (V.toList ii)
    --     es = map (\i -> ms Map.! i) ids
    -- ss <- liftIO $ Zip.withArchive f' (sequence $ map (liftM LBS.fromStrict . Zip.getEntry) es)
    -- cs <- sequence $ map stringToComm ss
    -- return $ default_FetchResult { fetchResult_communications=V.fromList cs }
  getCommunicationIDs (ZipFetch (f, ms)) offset count = error "" -- return $ V.fromList $ ((map (pack . fst)) . genericTake count . genericDrop offset . Map.toList) ms
  getCommunicationCount (ZipFetch (f, ms)) = error "" --return ((genericLength . Map.toList) ms)

-- | Create a Zip-based Fetch handler based on the given file
makeZipFetch :: String -> IO ZipFetch
makeZipFetch f = do
  f' <- resolveFile' f  
  es <- liftIO $ ZipC.withArchive f ZipC.entryNames -- Zip.getEntries
  -- ms <- Map.fromList <$> mapM (\e -> do
  --                                 s <- liftIO $ Zip.withArchive f' (Zip.getEntry e)
  --                                 c <- stringToComm (LBS.fromStrict s)
  --                                 return (T.unpack $ communication_id c, e)) (Map.keys es)
  return $ ZipFetch (f, Map.fromList [])

-- | Tar-based Fetch backend
newtype TarFetch = TarFetch (Handle, Map Text TarIndex.TarEntryOffset) -- [(String, Int, Int)])
  --(String, Int, Int)]
  deriving Show
--(Handle, (LBS.ByteString -> LBS.ByteString), Tar.TarIndex, Map String FilePath)

instance Service_Iface TarFetch where
  about _ = return $ ServiceInfo "Tar-backed FetchCommunicationService" "0.0.1" (Just "Haskell implementation")
  alive _ = return True

instance FetchCommunicationService_Iface TarFetch where
  fetch (TarFetch (h, m)) ii = do
    let i = [m Map.! n | n <- ((V.toList . fetchRequest_communicationIds) ii)]
    i' <- mapM (\ o -> do
                   e <- TarIndex.hReadEntry h o
                   case Tar.entryContent e of
                     Tar.NormalFile bs _ -> do
                       stringToComm bs
                       --return $!! (communication_id c, o)
               ) i
    return $ default_FetchResult { fetchResult_communications=V.fromList i' }

  --fetch (TarFetch (h, c, i, l)) ii = error "te"
    -- do
    -- cc <- sequence $ map fetchOne ((map unpack . V.toList . fetchRequest_communicationIds) ii)
    -- return $ default_FetchResult { fetchResult_communications=V.fromList cc }
    -- where
    --   fetchOne :: String -> IO Communication
    --   fetchOne p = do
    --     let (Just (Tar.TarFileEntry o)) = Tar.lookup i (l Map.! p)
    --     e <- Tar.hReadEntry h o
    --     stringToComm ((((\ (Tar.NormalFile bs _) -> bs) . Tar.entryContent) ) e)
  --getCommunicationIDs (TarFetch (_, _, _, l)) offset count = error "dd"
  getCommunicationIDs (TarFetch (_, m))  offset count = return $ (V.fromList . take count' . drop offset' . Map.keys) m
    where
      count' = fromIntegral count
      offset' = fromIntegral offset
    -- return $ V.fromList $ ((map (pack . fst)) . genericTake count . genericDrop offset . Map.toList) l
  --getCommunicationCount (TarFetch (_, _, _, l)) = error "as"
  getCommunicationCount (TarFetch (_, m)) = return $ fromIntegral $ Map.size m
  -- return ((genericLength . Map.toList) l)

-- | Create a Tar-based Fetch handler based on the given file
makeTarFetch :: String -> IO TarFetch
makeTarFetch f = do
  let c = case takeExtension f of
            ".tgz" -> GZip.decompress
            ".tbz2" -> BZip.decompress
            ".tar" -> id
  h <- openFile f ReadMode
  t <- LBS.hGetContents h
  h' <- openFile f ReadMode

  let es = Tar.read t
      cs = []
      Right i = (liftM TarIndex.toList . TarIndex.build) es

  --print i
  --print $ length i
  i' <- mapM (\(_, o) -> do
                 e <- TarIndex.hReadEntry h' o
                 case Tar.entryContent e of
                   Tar.NormalFile bs _ -> do
                     c <- stringToComm bs
                     return $!! (communication_id c, o)
             ) i
  --let cs = Tar.foldEntries (\e l -> ((T.pack . Tar.entryPath) e):l) [] (\e -> []) (Tar.read t)
  -- cs <- Tar.foldEntries (\e !l -> do
  --                           l' <- l
  --                           let con = Tar.entryContent e
  --                           case con of
  --                             Tar.NormalFile bs _ -> do
  --                               comm <- stringToComm bs
  --                               return $!! (communication_id comm):l'
  --                             _ -> return $!! l'
  --                       ) (return []) (\e -> return []) (Tar.read t)
  
  --is <- runConduitRes $ sourceFileBS f .| c .| CT.untar .| CT.withEntries commIdFromEntry .| sinkList
  --bs <- c <$> LBS.hGetContents h
  -- let e = Tar.read bs
  -- (l, i) <- build e
  -- h' <- openFile f ReadMode
  return $ TarFetch (h', Map.fromList i') --(fromRight (TarIndex.finalise Tar.empty). TarIndex.build . Tar.read) bs) --Tar.finalise Tar.empty)
  --is -- (h, c, i, is)

--entryName :: Monad m => CT.Header -> Conduit SBS.ByteString m (String, Int, Int)
--entryName c = yield $ (CT.headerFilePath c, CT.headerPayloadOffset c, CT.headerPayloadSize c)

--commFromEntry :: Tar.Entry -> IO Communication
commIdFromEntry e = when (CT.headerFileType e == CT.FTNormal) $ do
  --yield (communication_id default_Communication)
  c <- await
  case c of
    Just t -> do
      c' <- liftIO $ stringToComm (LBS.fromStrict t) -- default_Communication
      yield (communication_id c')
    _ -> yield ""
  --liftIO $ print c
  
  --let c' = default_Communication
  --
  --yield "" -- (communication_id c')
  --stringToComm (((\ (Tar.NormalFile bs _) -> bs) . Tar.entryContent) e)

-- build :: Tar.Entries e -> IO ([(String, FilePath)], Tar.TarIndex)
-- build = go ([], Tar.empty)
--   where
--     go :: ([(String, FilePath)], Tar.IndexBuilder) -> Tar.Entries e -> IO ([(String, FilePath)], Tar.TarIndex)
--     go (l, !builder) (Tar.Next e es) = do
--       c <- commFromEntry e
--       go (((unpack . communication_id) c, Tar.entryPath e):l, Tar.addNextEntry e builder) es
--     go (l, !builder) (Tar.Done) = do
--       return (l, Tar.finalise builder)