packages feed

AndroidViewHierarchyImporter-0.1.0.0: ViewServer.hs

{-# LANGUAGE OverloadedStrings #-}
{- | Utility functions for interacting with the view server
  
-}
module ViewServer(
      listWindows
    , listViews
    , rawViews
    , mkTree
    , test
    ) where

import Network.Socket 
import qualified Network.Socket.ByteString as SB
import Control.Monad.IO.Class
import qualified Data.ByteString.Char8 as B
import Control.Monad.Writer 
import Data.List.Split 
import Types
import Data.List(foldl')
import Data.Tree

-- | Stack used for reconstructing a tree from the flattened trace generated by the view server
newtype Stack a = Stack [a] deriving(Show)

isEmpty :: Stack a -> Bool 
isEmpty (Stack s) = null s 

push :: a -> Stack a -> Stack a 
push a (Stack s) = Stack $ (a:s)

pop :: Stack a -> (Stack a,a)
pop (Stack (s:l)) = (Stack l,s)
pop _ = error "Can't pop and empty stack"

emptyStack :: Stack a
emptyStack = Stack []

stackLength :: Stack a -> Int 
stackLength (Stack l) = length l


-- Low level command
-- Do we list the windows of dump the views
data Cmd = ListCmd 
         | Dump B.ByteString 

-- Result of a command
data Result = WList [B.ByteString]
            | WProperties B.ByteString 
            deriving(Eq,Show,Read)

-- | Get result from the view server
recvAll :: Socket -> IO B.ByteString 
recvAll sock = do
    f <- r_ sock id
    return (f B.empty) 
  where 
    r_ s current = do 
        msg <- SB.recv s 1024
        if (B.null msg) 
            then return current 
            else do 
                r_ s (current . B.append msg)

-- | Send a command to the view server and read the full result.
processCmd :: Socket 
           -> B.ByteString 
           -> IO B.ByteString
processCmd sock s = do 
    SB.sendAll sock $ B.append s (B.pack "\n")
    recvAll sock

-- | Connect to the view server and send a command
genericCmd c s = do
       let hints = defaultHints { addrFlags = [AI_ADDRCONFIG, AI_CANONNAME] } 
       addrinfos <- getAddrInfo Nothing (Just (hostname c)) (Just $ show (port c))
       let serveraddr = head addrinfos
       sock <- socket (addrFamily serveraddr) Stream defaultProtocol
       connect sock (addrAddress serveraddr)
       r <- processCmd sock s
       sClose sock
       return r

-- | Some of the commands recognized by the view server
command :: Config -> Cmd -> IO Result
command p ListCmd = genericCmd p "LIST" >>= return . WList . B.lines
command p (Dump a) = genericCmd p (B.append (B.pack "DUMP ")  a) >>= return . WProperties

-- | List of windows
listWindows :: Config -> IO [Window]
listWindows p = do 
    WList l <- command p ListCmd 
    return . map toWindow . filter (/= "DONE.") $ l

-- | List of view for a window
-- The int is used to reconstruct the tree. In the view server trace it is encoded
-- as the number of spaces before the view name.
listViews :: Config -> WindowHash -> IO [(Int,(String,View))]
listViews p wh = do
    WProperties l <- command p (Dump (B.pack $ wh))
    let views = filter (\(_,(vn,_)) -> vn /= "DONE." && vn /= "DONE") . map toProperties $ lines . B.unpack $ l
    return views

-- | Raw result from the view server
rawViews :: Config -> WindowHash -> IO String
rawViews p wh = do
    WProperties l <- command p (Dump (B.pack $ wh))
    let views = B.unpack $ l
    return views 

test :: Monad m => B.ByteString -> m [(Int,(String,View))]
test l = do 
    let theViews = map toProperties $ lines . B.unpack $ l
    return theViews

-- | Parse the window description from the view server
toWindow :: B.ByteString -> Window 
toWindow l = 
    let [hash,name] = splitOn " " . B.unpack $ l
    in 
    mkWindow hash name

-- | Parse a view description from the view server
toProperties :: String -> (Int,(String,View))
toProperties l = 
    let (nb,viewName,propertyTags) = getNbFrontSpace 0 l
    in 
    (nb, (viewName, foldl' setViewProperty emptyView propertyTags))

-- | Get the number of leading spaces before a view name
getNbFrontSpace nb [] = (nb,"",[])
getNbFrontSpace nb (' ':l) = getNbFrontSpace (nb+1) l 
getNbFrontSpace nb l = let (name,otherLines) = break (== ' ') $ l
                           r = getValues "" otherLines
                       in 
                       (nb,name,r)

-- | Get the values for the view fields
getValues current [] = [] 
getValues current ('=':r) = 
    let (nbS,remaining) = break (== ',') r 
        nb = read nbS 
        value = take nb . drop 1 $ remaining 
    in 
    (dropWhile (== ' ') . reverse $ current,value):getValues "" (drop (nb+1) remaining)
getValues current (a:r) = getValues (a:current) r

reverseStack nb (Stack s) = 
    let addChild (nb,((Node a l):r)) (_,c) = (nb,(Node a (c ++ l)):r)
        nodeNb = fst
        connectAll (a:b:l) | nodeNb a >= nb && nodeNb b < nb = connectAll ((addChild b a):l)
        connectAll l = l
        connected = reverse $ connectAll (reverse s)
    in 
    Stack connected

testb = 
    let node (x,y) = (x,Node (show y) [])
        bb = [(0,0),(1,1),(2,2),(1,4)]
    in 
    map node bb 

{-

Algorithm for reconstructing a tree from the trace
(not the most elegant - but not enough time to do differently)
-}
data StackElem a = SE { nodeNb :: Int
                      , valueNb :: Tree a
                      } deriving(Show)

addChild :: Tree a -> Tree a -> Tree a 
addChild aChild (Node root children) = Node root (aChild:children)

concatSE :: StackElem a -> StackElem a -> StackElem a 
concatSE first@(SE _ f) second@(SE nb s) = SE nb (addChild f s)


concatStack :: Int -> StackElem a -> [StackElem a] -> [StackElem a]
concatStack nb val (first:second:r) | nb <= nodeNb first = concatStack nb val (concatSE first second:r)
                                    | otherwise = val:first:second:r
concatStack nb val (a:l) = val:a:l 
concatStack nb val [] = [val]

toTree :: StackElem a -> Tree a 
toTree = valueNb

--0,1,2,3,1,2

mkTree :: [(Int,a)] -> Tree a
mkTree [] = error "Can't make tree from empty list"
mkTree l = toTree $ _mkTree [] l
 where 
    node (nb,v) = SE nb (Node v [])
    _mkTree :: [StackElem a] -> [(Int,a)] -> StackElem a
    _mkTree [] (first:r) = _mkTree [node first] r
    _mkTree stack [] = foldl1 concatSE stack
    _mkTree stack@(first:second:stack') (a:b) | fst a == nodeNb first = _mkTree (node a:concatSE first second:stack') b 
                                              | fst a > nodeNb first = _mkTree (node a:stack) b 
                                              | fst a < nodeNb first = _mkTree (concatStack (fst a) (node a) stack) b
    _mkTree stack@(first:l) (a:b) | fst a == nodeNb first = error "can't add sibling"
                                  | fst a > nodeNb first = _mkTree (node a:stack) b 
                                  | fst a < nodeNb first = _mkTree (concatStack (fst a) (node a) stack) b