2024-11-04 23:37:48 +00:00
|
|
|
{-# LANGUAGE DataKinds #-}
|
|
|
|
{-# LANGUAGE DerivingStrategies #-}
|
|
|
|
{-# LANGUAGE EmptyDataDecls #-}
|
|
|
|
{-# LANGUAGE FlexibleContexts #-}
|
|
|
|
{-# LANGUAGE FlexibleInstances #-}
|
|
|
|
{-# LANGUAGE GADTs #-}
|
|
|
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
|
|
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
{-# LANGUAGE QuasiQuotes #-}
|
|
|
|
{-# LANGUAGE StandaloneDeriving #-}
|
|
|
|
{-# LANGUAGE TemplateHaskell #-}
|
|
|
|
{-# LANGUAGE TypeFamilies #-}
|
|
|
|
{-# LANGUAGE UndecidableInstances #-}
|
|
|
|
|
2024-11-10 15:36:10 +00:00
|
|
|
module Store (save, fetch, sessions, sessionStartTime,
|
|
|
|
sessionDuration, migrateSession, migrateTrkpt
|
|
|
|
) where
|
2024-11-04 23:37:48 +00:00
|
|
|
|
2024-11-07 18:59:56 +00:00
|
|
|
import Control.Exception
|
2024-11-05 23:32:01 +00:00
|
|
|
import Control.Monad.IO.Class (MonadIO, liftIO)
|
2024-11-04 23:37:48 +00:00
|
|
|
import Control.Monad.Trans.Reader (ReaderT)
|
2024-11-07 18:59:56 +00:00
|
|
|
import Data.Maybe (isJust)
|
2024-11-06 22:00:51 +00:00
|
|
|
import Data.Time.Clock (DiffTime, NominalDiffTime, UTCTime, addUTCTime)
|
2024-11-04 23:37:48 +00:00
|
|
|
import Database.Persist
|
2024-11-05 23:32:01 +00:00
|
|
|
import Database.Persist.Class
|
2024-11-04 23:37:48 +00:00
|
|
|
import Database.Persist.Postgresql
|
|
|
|
( ConnectionString,
|
|
|
|
SqlBackend,
|
|
|
|
createPostgresqlPool,
|
|
|
|
pgConnStr,
|
|
|
|
pgPoolSize,
|
|
|
|
runMigration,
|
|
|
|
runSqlPool,
|
2024-11-10 13:11:13 +00:00
|
|
|
rawExecute,
|
|
|
|
PgInterval
|
2024-11-04 23:37:48 +00:00
|
|
|
)
|
|
|
|
import Database.Persist.TH
|
2024-11-06 22:00:51 +00:00
|
|
|
import Text.Read (readMaybe)
|
2024-11-05 23:32:01 +00:00
|
|
|
import Track (Point (..), Pos (..))
|
2024-11-04 23:37:48 +00:00
|
|
|
import Track as T
|
2024-11-10 13:11:13 +00:00
|
|
|
import Text.RawString.QQ (r)
|
2024-11-04 23:37:48 +00:00
|
|
|
|
|
|
|
connString :: ConnectionString
|
|
|
|
connString = "host=127.0.0.1 port=5432 user=souplesse dbname=souplesse password=secret"
|
|
|
|
|
|
|
|
share
|
2024-11-10 15:36:10 +00:00
|
|
|
[mkPersist sqlSettings, mkMigrate "migrateSession"]
|
|
|
|
[persistLowerCase|
|
|
|
|
Session
|
|
|
|
startTime UTCTime
|
|
|
|
duration PgInterval
|
|
|
|
draft Bool default=True
|
|
|
|
notes String Maybe
|
|
|
|
|]
|
|
|
|
|
|
|
|
share
|
|
|
|
[mkPersist sqlSettings, mkMigrate "migrateTrkpt"]
|
2024-11-04 23:37:48 +00:00
|
|
|
[persistLowerCase|
|
|
|
|
Trkpt
|
|
|
|
lat Double
|
|
|
|
lon Double
|
|
|
|
ele Double Maybe
|
|
|
|
time UTCTime
|
|
|
|
cadence Int Maybe
|
|
|
|
power Int Maybe
|
|
|
|
heartRate Int Maybe
|
|
|
|
|]
|
|
|
|
|
2024-11-10 15:36:10 +00:00
|
|
|
|
2024-11-04 23:37:48 +00:00
|
|
|
fromPoint :: Point -> Trkpt
|
|
|
|
fromPoint p =
|
|
|
|
let Pos lat lon ele = T.pos p
|
|
|
|
in Trkpt lat lon ele (T.time p) (T.cadence p) (T.power p) (T.heartRate p)
|
|
|
|
|
2024-11-05 23:32:01 +00:00
|
|
|
toPoint :: Entity Trkpt -> Point
|
|
|
|
toPoint entity =
|
|
|
|
let tkp = (\(Entity _ tkp) -> tkp) entity
|
|
|
|
pos = Pos (trkptLat tkp) (trkptLon tkp) (trkptEle tkp)
|
|
|
|
in Point
|
|
|
|
pos
|
|
|
|
(trkptTime tkp)
|
2024-11-06 19:13:47 +00:00
|
|
|
(trkptCadence tkp)
|
|
|
|
(trkptPower tkp)
|
|
|
|
(trkptHeartRate tkp)
|
2024-11-05 23:32:01 +00:00
|
|
|
|
2024-11-10 13:11:13 +00:00
|
|
|
updateSessions = [r|
|
|
|
|
-- delete existing drafts as new data may extend one of them
|
|
|
|
delete from session where draft;
|
|
|
|
-- find all potential start points in the new data
|
|
|
|
insert into session(start_time, duration, draft) (select time as start_time, make_interval(0) as duration, true as draft from trkpt as p where not exists (select id from session where start_time <= p.time and (start_time + duration) > p.time) and not exists(select * from trkpt as p1 where time > p.time - '600 seconds'::interval and time < p.time));
|
|
|
|
-- reset durations
|
|
|
|
update session s1
|
|
|
|
set duration =
|
|
|
|
coalesce(
|
|
|
|
(select max(time) from trkpt t1
|
|
|
|
where t1.time < coalesce((select min(start_time) from session s2 where s2.start_time > s1.start_time), now())) - s1.start_time
|
|
|
|
, make_interval())
|
|
|
|
where draft;
|
|
|
|
|]
|
|
|
|
|
2024-11-07 18:59:56 +00:00
|
|
|
data OverlapExists = OverlapExists String deriving (Show)
|
|
|
|
|
|
|
|
instance Exception OverlapExists
|
|
|
|
|
2024-11-10 13:11:13 +00:00
|
|
|
sessions :: (MonadIO m) => ReaderT SqlBackend m [Session]
|
|
|
|
sessions = do
|
|
|
|
s <- selectList [SessionDraft !=. True] [Desc SessionStartTime, LimitTo 10]
|
|
|
|
return $ map (\(Entity _ x) -> x) s
|
|
|
|
|
|
|
|
|
2024-11-07 18:59:56 +00:00
|
|
|
save :: (MonadIO m) => Track -> ReaderT SqlBackend m (Either OverlapExists Track)
|
|
|
|
save track = do
|
|
|
|
let start = startTime track
|
|
|
|
finish = addUTCTime (duration track) (startTime track)
|
|
|
|
priors <- selectFirst [TrkptTime >. start, TrkptTime <. finish] []
|
|
|
|
if isJust priors
|
|
|
|
then return $ Left (OverlapExists "track overlaps with existing data")
|
|
|
|
else do
|
|
|
|
mapM_ (insert . fromPoint) track
|
2024-11-10 13:11:13 +00:00
|
|
|
rawExecute updateSessions []
|
2024-11-07 18:59:56 +00:00
|
|
|
return $ Right track
|
2024-11-05 23:32:01 +00:00
|
|
|
|
2024-11-06 22:00:51 +00:00
|
|
|
fetch :: (MonadIO m) => UTCTime -> NominalDiffTime -> ReaderT SqlBackend m [Point]
|
|
|
|
fetch start duration = do
|
|
|
|
let finish = addUTCTime duration start
|
|
|
|
trkpts <- selectList [TrkptTime >. start, TrkptTime <. finish] []
|
2024-11-05 23:32:01 +00:00
|
|
|
return $ map toPoint trkpts
|