113 lines
3 KiB
Haskell
113 lines
3 KiB
Haskell
{- Sqlite database used for incremental fsck.
|
|
-
|
|
- Copyright 2015 Joey Hess <id@joeyh.name>
|
|
-:
|
|
- Licensed under the GNU GPL version 3 or higher.
|
|
-}
|
|
|
|
{-# LANGUAGE QuasiQuotes, TypeFamilies, TemplateHaskell #-}
|
|
{-# LANGUAGE OverloadedStrings, GADTs, FlexibleContexts #-}
|
|
{-# LANGUAGE MultiParamTypeClasses, GeneralizedNewtypeDeriving #-}
|
|
{-# LANGUAGE RankNTypes #-}
|
|
|
|
module Database.Fsck (
|
|
FsckHandle,
|
|
newPass,
|
|
openDb,
|
|
closeDb,
|
|
addDb,
|
|
inDb,
|
|
FsckedId,
|
|
) where
|
|
|
|
import Database.Types
|
|
import qualified Database.Handle as H
|
|
import Locations
|
|
import Utility.PosixFiles
|
|
import Utility.Exception
|
|
import Common
|
|
import Annex
|
|
import Types.Key
|
|
import Types.UUID
|
|
import Annex.Perms
|
|
import Annex.LockFile
|
|
import Messages
|
|
|
|
import Database.Persist.TH
|
|
import Database.Esqueleto hiding (Key)
|
|
import Data.Time.Clock
|
|
|
|
data FsckHandle = FsckHandle H.DbHandle UUID
|
|
|
|
{- Each key stored in the database has already been fscked as part
|
|
- of the latest incremental fsck pass. -}
|
|
share [mkPersist sqlSettings, mkMigrate "migrateFsck"] [persistLowerCase|
|
|
Fscked
|
|
key SKey
|
|
UniqueKey key
|
|
|]
|
|
|
|
{- The database is removed when starting a new incremental fsck pass.
|
|
-
|
|
- This may fail, if other fsck processes are currently running using the
|
|
- database. Removing the database in that situation would lead to crashes
|
|
- or unknown behavior.
|
|
-}
|
|
newPass :: UUID -> Annex Bool
|
|
newPass u = isJust <$> tryExclusiveLock (gitAnnexFsckDbLock u) go
|
|
where
|
|
go = liftIO . void . tryIO . removeDirectoryRecursive
|
|
=<< fromRepo (gitAnnexFsckDbDir u)
|
|
|
|
{- Opens the database, creating it if it doesn't exist yet. -}
|
|
openDb :: UUID -> Annex FsckHandle
|
|
openDb u = do
|
|
dbdir <- fromRepo (gitAnnexFsckDbDir u)
|
|
let db = dbdir </> "db"
|
|
unlessM (liftIO $ doesFileExist db) $ do
|
|
let tmpdbdir = dbdir ++ ".tmp"
|
|
let tmpdb = tmpdbdir </> "db"
|
|
liftIO $ do
|
|
createDirectoryIfMissing True tmpdbdir
|
|
H.initDb tmpdb $ void $
|
|
runMigrationSilent migrateFsck
|
|
setAnnexDirPerm tmpdbdir
|
|
setAnnexFilePerm tmpdb
|
|
liftIO $ do
|
|
void $ tryIO $ removeDirectoryRecursive dbdir
|
|
rename tmpdbdir dbdir
|
|
lockFileCached =<< fromRepo (gitAnnexFsckDbLock u)
|
|
h <- liftIO $ H.openDb db "fscked"
|
|
|
|
-- work around https://github.com/yesodweb/persistent/issues/474
|
|
liftIO setConsoleEncoding
|
|
|
|
return $ FsckHandle h u
|
|
|
|
closeDb :: FsckHandle -> Annex ()
|
|
closeDb (FsckHandle h u) = do
|
|
liftIO $ H.closeDb h
|
|
unlockFile =<< fromRepo (gitAnnexFsckDbLock u)
|
|
|
|
addDb :: FsckHandle -> Key -> IO ()
|
|
addDb (FsckHandle h _) k = H.queueDb h checkcommit $
|
|
void $ insertUnique $ Fscked sk
|
|
where
|
|
sk = toSKey k
|
|
|
|
-- commit queue after 1000 files or 5 minutes, whichever comes first
|
|
checkcommit sz lastcommittime
|
|
| sz > 1000 = return True
|
|
| otherwise = do
|
|
now <- getCurrentTime
|
|
return $ diffUTCTime lastcommittime now > 300
|
|
|
|
inDb :: FsckHandle -> Key -> IO Bool
|
|
inDb (FsckHandle h _) = H.queryDb h . inDb' . toSKey
|
|
|
|
inDb' :: SKey -> SqlPersistM Bool
|
|
inDb' sk = do
|
|
r <- select $ from $ \r -> do
|
|
where_ (r ^. FsckedKey ==. val sk)
|
|
return (r ^. FsckedKey)
|
|
return $ not $ null r
|