packages feed

mahoro-0.1.1: DB.hs

{-# LANGUAGE DeriveDataTypeable #-}

-- | DB.hs
-- A module for work with database.

module DB (
    URL(..),
    JID(..),
    DB,
    DBProcess,
    newDB,
    addThread,
    delThread,
    delThreadAll,
    bye,
    getDescription,
    setDescription,
    getList,
    getUserThreads,
    getThread,    
    updateThread,
    dumpDB
) where

import Data.Typeable

import qualified Data.Map as M
import qualified Data.Set as S
import Data.List


-- Type structures.

-- | Map of threads, last post numbers and jids subscribed on them.
type Threads = M.Map URL (Integer, S.Set JID)
-- | Map of users threads descriptions.
type Users = M.Map JID (M.Map URL String)

-- | Because jids and urls presented as strings they wrapped in data
-- type to avoid confuse.
data JID = JID String deriving (Eq, Ord, Typeable)
data URL = URL String deriving (Eq, Ord, Typeable)

instance Show JID where
    show (JID jid) = jid
instance Show URL where
    show (URL url) = url

type DB = (Threads, Users)
type DBProcess = DB -> (String, DB)


-- | New db.
newDB :: DB
newDB = (M.empty, M.empty)


-- | Add new thread.
addThread :: URL -> JID -> DBProcess
addThread url jid db@(threads, users) =
    -- lookup if thread exist
    case M.lookup url threads of
      -- thread exist, lookup if user subscribed
      Just (_, jids) ->
        if S.member jid jids
          then ("You've already subscribed on this thread.", db)
          -- update threads and add to user
          else ("Done", (M.update addJID url threads, updateUsers))
      -- no such thread, add him to threads and add to user
      _ -> ("Done.", (M.insert url (0, S.singleton jid) threads, updateUsers))
  where
    addJID (n, jids) = Just (n, S.insert jid jids)

    updateUsers = M.update addDescr jid users'
    addDescr = Just . M.insert url ""
    -- if no user then create him
    users' = case M.lookup jid users of
               Just _ -> users
               _ -> M.insert jid M.empty users


-- | Del thread.
delThread :: URL -> JID -> DBProcess
delThread url jid db@(threads, users) =
    -- lookup if thread exist
    case M.lookup url threads of
      -- thread exist, lookup if user subscribed
      Just (_, jids) ->
        if S.member jid jids
          -- del from threads and del from users
          then ("Done", (M.update delJID url threads
                        ,delURL url jid users))
          -- not subscribed, do nothing
          else noSubscribe
      -- no such thread, do nothing
      _ -> noSubscribe
  where
    noSubscribe = ("You haven't subscribed on this thread.", db)
    
    delJID (n, jids) = if S.size jids == 1
                         then Nothing
                         else Just (n, S.delete jid jids)


delURL :: URL -> JID -> Users -> Users
delURL url jid users =
    M.update (Just . M.delete url) jid users


-- | Del thread for all users.
delThreadAll :: URL -> DBProcess
delThreadAll url db@(threads, users) =
    -- lookup if thread exist
    case M.lookup url threads of
      Just (_, jids) -> ("Done", (M.delete url threads
                                ,foldr (delURL url) users (S.toList jids)))
      _ -> ("No such thread.", db) 


-- | Delete all info about user from database.
bye :: JID -> DBProcess
bye jid db@(_, users) =
    -- get all urls on which user subscribed
    let urls = map fst $ getUserThreads jid db
    in ("Done",
        -- fst $ snd $ (String, (Threads, Users))
        (fst $ snd $ foldr (\u (_, db') -> delThread u jid db') ("", db) urls
        -- delete info about user
        ,M.delete jid users))


-- | Get user description on thread.
getDescription :: URL -> JID -> DB -> String
getDescription url jid (_, users) =
    case M.lookup jid users of
      Just descrs ->
        maybe "" id $ M.lookup url descrs
      _ -> ""


-- | Set user description on thread.
setDescription :: URL -> JID -> String -> DBProcess
setDescription url jid descr db@(threads, users) =
    case M.lookup jid users of
      Just descrs ->
        if M.member url descrs
          then ("Done", (threads, M.insert jid (newDescrs descrs) users))
          else noSubscribe
      _ -> noSubscribe
  where
    noSubscribe = ("You haven't subscribed on this thread.", db)

    newDescrs = M.update (const (Just descr)) url


-- | Get user subscribed threads.
getUserThreads :: JID -> DB -> [(URL, String)]
getUserThreads jid (_, users) = maybe [] M.toList $ M.lookup jid users


-- | Get list of all threads.
getList :: DB -> [URL]
getList (threads, _) = M.keys threads


-- | Get last post and jids of url.
getThread :: URL -> DB -> (Integer, [JID])
getThread url (threads, _) =
    case M.lookup url threads of
      Just (n, jids) -> (n, S.toList jids)
      _ -> (0, [])


-- | Update thread with new last post info.
updateThread :: URL -> Integer -> DBProcess
updateThread url lastPost (threads, users) =
    ("Done",
     (M.update (\(_, jids) -> Just (lastPost, jids)) url threads
     ,users))


-- | Dump db.
dumpDB :: DB -> String
dumpDB (threads, users) =
    "\n" ++
    showUs users
    ++ "\n\n" ++
    showTs threads


-- Helpers.

-- show users as "jid | thread | description"
showUs users =
    let us = map (\(j, ds) -> showDs j (M.toList ds))
                 (M.toList users)
        showDs j = intercalate "\n" . showD j
        showD j = map (\(u, d) -> show j ++ " | "++show u++" | "++d)
    in intercalate "\n" us

-- show threads as "url | jid1 jid2 jid3 | LastPostNumber"
showTs threads =
    let ts = map (\(u, (l, js)) -> showT u l (S.toList js))
                 (M.toList threads)
        showT u l js' = show u++" | "++showJs js'++" | "++show l
        showJs = intercalate " " . map show
    in intercalate "\n" ts