2015-12-09 21:00:37 +00:00
|
|
|
|
{- Sqlite database of information about Keys
|
2015-12-07 17:42:03 +00:00
|
|
|
|
-
|
|
|
|
|
- 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 #-}
|
|
|
|
|
|
2015-12-09 21:00:37 +00:00
|
|
|
|
module Database.Keys (
|
2015-12-07 17:42:03 +00:00
|
|
|
|
DbHandle,
|
|
|
|
|
openDb,
|
|
|
|
|
closeDb,
|
2015-12-09 21:00:37 +00:00
|
|
|
|
addAssociatedFile,
|
|
|
|
|
getAssociatedFiles,
|
2015-12-15 17:05:23 +00:00
|
|
|
|
getAssociatedKey,
|
2015-12-09 21:00:37 +00:00
|
|
|
|
removeAssociatedFile,
|
2015-12-09 21:47:05 +00:00
|
|
|
|
storeInodeCaches,
|
|
|
|
|
addInodeCaches,
|
|
|
|
|
getInodeCaches,
|
|
|
|
|
removeInodeCaches,
|
2015-12-07 17:42:03 +00:00
|
|
|
|
AssociatedId,
|
2015-12-09 21:47:05 +00:00
|
|
|
|
ContentId,
|
2015-12-07 17:42:03 +00:00
|
|
|
|
) where
|
|
|
|
|
|
|
|
|
|
import Database.Types
|
2015-12-09 21:00:37 +00:00
|
|
|
|
import Database.Keys.Types
|
2015-12-07 17:42:03 +00:00
|
|
|
|
import qualified Database.Handle as H
|
|
|
|
|
import Locations
|
|
|
|
|
import Common hiding (delete)
|
|
|
|
|
import Annex
|
|
|
|
|
import Types.Key
|
|
|
|
|
import Annex.Perms
|
|
|
|
|
import Annex.LockFile
|
|
|
|
|
import Messages
|
2015-12-09 21:00:37 +00:00
|
|
|
|
import Utility.InodeCache
|
2015-12-09 21:47:05 +00:00
|
|
|
|
import Annex.InodeSentinal
|
2015-12-07 17:42:03 +00:00
|
|
|
|
|
|
|
|
|
import Database.Persist.TH
|
|
|
|
|
import Database.Esqueleto hiding (Key)
|
|
|
|
|
|
2015-12-09 21:00:37 +00:00
|
|
|
|
share [mkPersist sqlSettings, mkMigrate "migrateKeysDb"] [persistLowerCase|
|
2015-12-07 17:42:03 +00:00
|
|
|
|
Associated
|
|
|
|
|
key SKey
|
|
|
|
|
file FilePath
|
|
|
|
|
KeyFileIndex key file
|
2015-12-09 21:47:05 +00:00
|
|
|
|
Content
|
2015-12-09 21:00:37 +00:00
|
|
|
|
key SKey
|
2015-12-09 21:47:05 +00:00
|
|
|
|
cache SInodeCache
|
|
|
|
|
KeyCacheIndex key cache
|
2015-12-07 17:42:03 +00:00
|
|
|
|
|]
|
|
|
|
|
|
2015-12-16 17:24:45 +00:00
|
|
|
|
{- Opens the database, creating it if it doesn't exist yet.
|
|
|
|
|
-
|
|
|
|
|
- Multiple readers and writers can have the database open at the same
|
|
|
|
|
- time. Database.Handle deals with the concurrency issues.
|
|
|
|
|
- The lock is held while opening the database, so that when
|
|
|
|
|
- the database doesn't exist yet, one caller wins the lock and
|
|
|
|
|
- can create it undisturbed.
|
|
|
|
|
-}
|
2015-12-07 17:42:03 +00:00
|
|
|
|
openDb :: Annex DbHandle
|
2015-12-09 21:00:37 +00:00
|
|
|
|
openDb = withExclusiveLock gitAnnexKeysDbLock $ do
|
|
|
|
|
dbdir <- fromRepo gitAnnexKeysDb
|
2015-12-07 17:42:03 +00:00
|
|
|
|
let db = dbdir </> "db"
|
|
|
|
|
unlessM (liftIO $ doesFileExist db) $ do
|
|
|
|
|
liftIO $ do
|
|
|
|
|
createDirectoryIfMissing True dbdir
|
|
|
|
|
H.initDb db $ void $
|
2015-12-09 21:00:37 +00:00
|
|
|
|
runMigrationSilent migrateKeysDb
|
2015-12-07 17:42:03 +00:00
|
|
|
|
setAnnexDirPerm dbdir
|
|
|
|
|
setAnnexFilePerm db
|
2015-12-09 21:47:05 +00:00
|
|
|
|
h <- liftIO $ H.openDb db "content"
|
2015-12-07 17:42:03 +00:00
|
|
|
|
|
|
|
|
|
-- work around https://github.com/yesodweb/persistent/issues/474
|
|
|
|
|
liftIO setConsoleEncoding
|
|
|
|
|
|
|
|
|
|
return $ DbHandle h
|
|
|
|
|
|
|
|
|
|
closeDb :: DbHandle -> IO ()
|
|
|
|
|
closeDb (DbHandle h) = H.closeDb h
|
|
|
|
|
|
2015-12-09 18:55:47 +00:00
|
|
|
|
withDbHandle :: (H.DbHandle -> IO a) -> Annex a
|
temporarily remove cached keys database connection
The problem is that shutdown is not always called, particularly in the test
suite. So, a database connection would be opened, possibly some changes
queued, and then not shut down.
One way this can happen is when using Annex.eval or Annex.run with a new
state. A better fix might be to make both of them call Keys.shutdown
(and be sure to do it even if the annex action threw an error).
Complication: Sometimes they're run reusing an existing state, so shutting
down a database connection could cause problems for other users of that
same state. I think this would need a MVar holding the database handle,
so it could be emptied once shut down, and another user of the database
connection could then start up a new one if it got shut down. But, what if
2 threads were concurrently using the same database handle and one shut it
down while the other was writing to it? Urgh.
Might have to go that route eventually to get the database access to run
fast enough. For now, a quick fix to get the test suite happier, at the
expense of speed.
2015-12-16 18:05:26 +00:00
|
|
|
|
withDbHandle a = bracket openDb (liftIO . closeDb) (\(DbHandle h) -> liftIO (a h))
|
2015-12-09 18:55:47 +00:00
|
|
|
|
|
2015-12-09 21:00:37 +00:00
|
|
|
|
addAssociatedFile :: Key -> FilePath -> Annex ()
|
|
|
|
|
addAssociatedFile k f = withDbHandle $ \h -> H.queueDb h (\_ _ -> pure True) $ do
|
2015-12-07 17:42:03 +00:00
|
|
|
|
-- If the same file was associated with a different key before,
|
|
|
|
|
-- remove that.
|
|
|
|
|
delete $ from $ \r -> do
|
|
|
|
|
where_ (r ^. AssociatedFile ==. val f &&. r ^. AssociatedKey ==. val sk)
|
|
|
|
|
void $ insertUnique $ Associated sk f
|
|
|
|
|
where
|
|
|
|
|
sk = toSKey k
|
|
|
|
|
|
2015-12-09 21:00:37 +00:00
|
|
|
|
{- Note that the files returned were once associated with the key, but
|
2015-12-07 17:42:03 +00:00
|
|
|
|
- some of them may not be any longer. -}
|
2015-12-09 21:00:37 +00:00
|
|
|
|
getAssociatedFiles :: Key -> Annex [FilePath]
|
|
|
|
|
getAssociatedFiles k = withDbHandle $ \h -> H.queryDb h $
|
|
|
|
|
getAssociatedFiles' $ toSKey k
|
2015-12-07 17:42:03 +00:00
|
|
|
|
|
2015-12-09 21:00:37 +00:00
|
|
|
|
getAssociatedFiles' :: SKey -> SqlPersistM [FilePath]
|
|
|
|
|
getAssociatedFiles' sk = do
|
2015-12-07 17:42:03 +00:00
|
|
|
|
l <- select $ from $ \r -> do
|
|
|
|
|
where_ (r ^. AssociatedKey ==. val sk)
|
|
|
|
|
return (r ^. AssociatedFile)
|
|
|
|
|
return $ map unValue l
|
|
|
|
|
|
2015-12-15 17:05:23 +00:00
|
|
|
|
{- Gets any keys that are on record as having a particular associated file.
|
|
|
|
|
- (Should be one or none but the database doesn't enforce that.) -}
|
|
|
|
|
getAssociatedKey :: FilePath -> Annex [Key]
|
|
|
|
|
getAssociatedKey f = withDbHandle $ \h -> H.queryDb h $
|
|
|
|
|
getAssociatedKey' f
|
|
|
|
|
|
|
|
|
|
getAssociatedKey' :: FilePath -> SqlPersistM [Key]
|
|
|
|
|
getAssociatedKey' f = do
|
|
|
|
|
l <- select $ from $ \r -> do
|
|
|
|
|
where_ (r ^. AssociatedFile ==. val f)
|
|
|
|
|
return (r ^. AssociatedKey)
|
|
|
|
|
return $ map (fromSKey . unValue) l
|
|
|
|
|
|
2015-12-09 21:00:37 +00:00
|
|
|
|
removeAssociatedFile :: Key -> FilePath -> Annex ()
|
2015-12-09 21:47:05 +00:00
|
|
|
|
removeAssociatedFile k f = withDbHandle $ \h -> H.queueDb h (\_ _ -> pure True) $
|
2015-12-07 17:42:03 +00:00
|
|
|
|
delete $ from $ \r -> do
|
|
|
|
|
where_ (r ^. AssociatedKey ==. val sk &&. r ^. AssociatedFile ==. val f)
|
|
|
|
|
where
|
|
|
|
|
sk = toSKey k
|
2015-12-09 21:00:37 +00:00
|
|
|
|
|
2015-12-09 21:47:05 +00:00
|
|
|
|
{- Stats the files, and stores their InodeCaches. -}
|
|
|
|
|
storeInodeCaches :: Key -> [FilePath] -> Annex ()
|
|
|
|
|
storeInodeCaches k fs = withTSDelta $ \d ->
|
|
|
|
|
addInodeCaches k . catMaybes =<< liftIO (mapM (`genInodeCache` d) fs)
|
|
|
|
|
|
|
|
|
|
addInodeCaches :: Key -> [InodeCache] -> Annex ()
|
|
|
|
|
addInodeCaches k is = withDbHandle $ \h -> H.queueDb h (\_ _ -> pure True) $
|
|
|
|
|
forM_ is $ \i -> insertUnique $ Content (toSKey k) (toSInodeCache i)
|
2015-12-09 21:00:37 +00:00
|
|
|
|
|
2015-12-09 21:47:05 +00:00
|
|
|
|
{- A key may have multiple InodeCaches; one for the annex object, and one
|
|
|
|
|
- for each pointer file that is a copy of it. -}
|
|
|
|
|
getInodeCaches :: Key -> Annex [InodeCache]
|
|
|
|
|
getInodeCaches k = withDbHandle $ \h -> H.queryDb h $ do
|
2015-12-09 21:00:37 +00:00
|
|
|
|
l <- select $ from $ \r -> do
|
2015-12-09 21:47:05 +00:00
|
|
|
|
where_ (r ^. ContentKey ==. val sk)
|
|
|
|
|
return (r ^. ContentCache)
|
|
|
|
|
return $ map (fromSInodeCache . unValue) l
|
|
|
|
|
where
|
|
|
|
|
sk = toSKey k
|
|
|
|
|
|
|
|
|
|
removeInodeCaches :: Key -> Annex ()
|
|
|
|
|
removeInodeCaches k = withDbHandle $ \h -> H.queueDb h (\_ _ -> pure True) $
|
|
|
|
|
delete $ from $ \r -> do
|
|
|
|
|
where_ (r ^. ContentKey ==. val sk)
|
2015-12-09 21:00:37 +00:00
|
|
|
|
where
|
|
|
|
|
sk = toSKey k
|