packages feed

musicScroll-0.2.3.0: src/MusicScroll/DatabaseUtils.hs

{-# language OverloadedStrings, TypeApplications, RecordWildCards #-}
module MusicScroll.DatabaseUtils
  ( getDBLyrics
  , getDBSong
  , insertDBLyrics
  , getDBPath
  , sqlDBCreate
  ) where

import           Prelude hiding (null)
import           Control.Applicative (Alternative(..))
import           Control.Exception (evaluate)
import           Control.Monad.Trans.Reader (ReaderT, ask)
import           Control.Monad.IO.Class (MonadIO(..))
import           Control.DeepSeq (rnf)
import           Crypto.Hash (SHA1, hashUpdate, hashInit, hashFinalize)
import           Data.ByteString (hGet, null)
import           System.IO (withFile, IOMode(..))
import           Data.Text (Text)
import           Database.SQLite.Simple
import           System.Environment.XDG.BaseDir (getUserCacheDir)
import           Data.Coerce
import           System.Directory (createDirectory)

import           MusicScroll.TrackInfo (TrackInfo(..), SongFilePath)
import           MusicScroll.Providers.Utils (Lyrics(..))

getDBLyrics :: SongFilePath -> ReaderT Connection IO Lyrics
getDBLyrics songUrl = snd <$> getDBSong songUrl

getDBSong :: SongFilePath -> ReaderT Connection IO (TrackInfo, Lyrics)
getDBSong songUrl =
  do conn <- ask
     liftIO $ do
       songHash <- fileHash songUrl
       songRaw <- query conn sqlExtractSong (Only songHash)
       case (songRaw :: [ (Text, Text, Text) ]) of
         [] -> empty
         (title, artist, lyrics):_ ->
           let track = TrackInfo title artist songUrl
           in return (track, coerce lyrics)

insertDBLyrics :: TrackInfo -> Lyrics -> ReaderT Connection IO ()
insertDBLyrics (TrackInfo {..}) lyrics =
  do conn <- ask
     liftIO $ do
       songHash <- fileHash tUrl
       let params = (songHash, tArtist, tTitle, coerce lyrics :: Text)
       execute conn sqlInsertSong params

getDBPath :: IO FilePath
getDBPath = do cacheDir <- getUserCacheDir "musicScroll"
               createDirectory cacheDir <|> return ()
               return $ cacheDir ++ "/" ++ "lyrics.db"

-- | We use the exception thrown by withFile.
fileHash :: FilePath -> IO String
fileHash fp = withFile fp ReadMode $ \hdl ->
  let chunkSize = 512 * 1024
      looper ctx =
          do upd <- hGet hdl chunkSize
             if null upd
               then return (show (hashFinalize ctx))
               else do let newCtx = hashUpdate ctx upd
                       evaluate (rnf newCtx) -- Important!
                       looper newCtx
  in looper (hashInit @SHA1)

sqlDBCreate, sqlInsertSong, sqlExtractSong :: Query
sqlDBCreate =
  "create table if not exists MusicScrollTable(\n\
  \  songHash text primary key,\n\
  \  artist text,\n\
  \  title text, \n\
  \  lyrics text );"

sqlInsertSong = "insert into MusicScrollTable values (?, ?, ?, ?);"

sqlExtractSong =
  "select title, artist, lyrics from MusicScrollTable where songHash == ?;"