google-drive-0.3.0: src/Network/Google/Drive/Search.hs
{-# LANGUAGE OverloadedStrings #-}
-- |
--
-- Searching for files on your drive.
--
-- https://developers.google.com/drive/web/search-parameters
--
module Network.Google.Drive.Search
( listFiles
, listVisibleContents
-- * Building Queries
, Query
, Field(..)
, QueryValue(..)
, (?=)
, (?!=)
, (?<)
, (?<=)
, (?>)
, (?>=)
, qIn
, qHas
, qContains
, qAnd
, qOr
, qNot
) where
import Network.Google.Api
import Network.Google.Drive.File
import Control.Applicative ((<$>), (<$>))
import Control.Monad (mzero)
import Data.Aeson
import Data.Char (toLower)
import Data.Monoid ((<>))
import Data.Text (Text)
import Data.Text.Encoding (encodeUtf8)
import qualified Data.Text as T
type Query = Text
-- | Queriable fields
data Field
= Title
| FullText
| MimeType
| ModifiedDate
| LastViewedByMeDate
| Trashed
| Starred
| Parents
| Owners
| Writers
| Readers
| SharedWithMe
| Properties
deriving Show
-- | Type class for values which can be used in queries
class QueryValue a where
escapeValue :: a -> Text
instance QueryValue Text where
escapeValue t = "'" <> t <> "'"
instance QueryValue Bool where
escapeValue True = "true"
escapeValue False = "false"
-- TODO
-- instance QueryValue UTCTime where
-- RFC 3339 format, default timezone is UTC, e.g., 2012-06-04T12:00:00-08:00.
newtype Items = Items [File]
instance FromJSON Items where
parseJSON (Object o) = Items <$> (mapM parseJSON =<< o .: "items")
parseJSON _ = mzero
-- | Perform a search as specified by the @Query@
listFiles :: Query -> Api [File]
listFiles query = do
Items items <- getJSON (baseUrl <> "/files")
[ ("q", Just $ encodeUtf8 query)
, ("maxResults", Just "1000")
]
return items
-- | List all not-trashed files within the given folder
listVisibleContents :: File -> Api [File]
listVisibleContents folder =
listFiles $ (fileId folder) `qIn` Parents `qAnd` Trashed ?= False
-- | The content of a string or boolean is equal to the other
(?=) :: QueryValue a => Field -> a -> Query
(?=) = qOp "="
-- | The content of a string or boolean is not equal to the other
(?!=) :: QueryValue a => Field -> a -> Query
(?!=) = qOp "!="
-- | A date is earlier than another
(?<) :: QueryValue a => Field -> a -> Query
(?<) = qOp "<"
-- | A date is earlier than or equal to another
(?<=) :: QueryValue a => Field -> a -> Query
(?<=) = qOp "<="
-- | A date is later than another
(?>) :: QueryValue a => Field -> a -> Query
(?>) = qOp ">"
-- | A date is later than or equal to another
(?>=) :: QueryValue a => Field -> a -> Query
(?>=) = qOp ">="
-- | An element is contained within a collection
--
-- Used for @Parents@, @Owners@, @Writers@, and @Readers@.
--
-- Note the reversed arguments such that infix usage makes sense.
--
qIn :: QueryValue a => a -> Field -> Query
qIn v f = T.intercalate " " [escapeValue v, "in", escapeField f]
-- | A collection contains an element matching the parameters.
--
-- Used for @Properties@.
--
qHas :: QueryValue a => Field -> a -> Query
qHas = qOp "has"
-- | The content of one string is present in the other
--
-- Used for @Title@, @FullText@, and @MimeType@.
--
qContains :: QueryValue a => Field -> a -> Query
qContains = qOp "contains"
infixr 3 `qAnd`
-- | Return files that match both clauses
qAnd :: Query -> Query -> Query
q `qAnd` p = "(" <> p <> ") and (" <> q <> ")"
infixr 2 `qOr`
-- | Return files that match either clause
qOr :: Query -> Query -> Query
q `qOr` p = "(" <> p <> ") or (" <> q <> ")"
-- | Negates a search clause
qNot :: Query -> Query
qNot q = "not " <> "(" <> q <> ")"
qOp :: QueryValue a => Text -> Field -> a -> Query
qOp x f v = T.intercalate " " [escapeField f, x, escapeValue v]
escapeField :: Field -> Text
escapeField = T.pack . lowerCase . show
where
lowerCase [] = []
lowerCase (x:xs) = toLower x : xs
baseUrl :: URL
baseUrl = "https://www.googleapis.com/drive/v2"