git-annex/LocationLog.hs

174 lines
4.9 KiB
Haskell
Raw Normal View History

2010-10-09 23:22:40 +00:00
{- git-annex location log
-
2011-04-02 19:50:51 +00:00
- git-annex keeps track of which repositories have the contents of annexed
- files.
2010-10-09 23:22:40 +00:00
-
2010-10-13 00:04:36 +00:00
- Location tracking information is stored in `.git-annex/key.log`.
2010-10-12 22:06:34 +00:00
- Repositories record their UUID and the date when they --get or --drop
2010-10-13 00:04:36 +00:00
- a value.
2010-10-09 23:22:40 +00:00
-
2010-10-12 22:06:34 +00:00
- A line of the log will look like: "date N UUID"
2010-10-10 02:29:16 +00:00
- Where N=1 when the repo has the file, and 0 otherwise.
2010-10-10 16:31:14 +00:00
-
- Git is configured to use a union merge for this file,
- so the lines may be in arbitrary order, but it will never conflict.
2010-10-27 20:53:54 +00:00
-
2011-04-02 19:50:51 +00:00
- Copyright 2010-2011 Joey Hess <joey@kitenet.net>
2010-10-27 20:53:54 +00:00
-
- Licensed under the GNU GPL version 3 or higher.
2010-10-09 23:22:40 +00:00
-}
2010-10-11 21:52:46 +00:00
module LocationLog (
2010-10-12 22:25:41 +00:00
LogStatus(..),
logChange,
2011-03-16 15:53:46 +00:00
readLog,
writeLog,
2011-04-02 19:50:51 +00:00
keyLocations,
loggedKeys,
logFile
2010-10-11 21:52:46 +00:00
) where
2010-10-09 23:22:40 +00:00
import Data.Time.Clock.POSIX
import Data.Time
import System.Locale
2011-04-02 19:50:51 +00:00
import System.Directory
import System.FilePath
2010-10-10 16:31:14 +00:00
import qualified Data.Map as Map
import Control.Monad (when)
2011-04-02 19:50:51 +00:00
import Data.Maybe
import Control.Monad.State (liftIO)
2010-10-16 20:20:49 +00:00
2010-10-14 06:36:41 +00:00
import qualified GitRepo as Git
2010-10-10 02:46:35 +00:00
import Utility
2010-10-12 22:06:34 +00:00
import UUID
2010-10-14 07:18:11 +00:00
import Types
import Locations
2010-10-09 23:22:40 +00:00
data LogLine = LogLine {
date :: POSIXTime,
status :: LogStatus,
2010-10-12 22:06:34 +00:00
uuid :: UUID
} deriving (Eq)
2010-10-13 00:04:36 +00:00
data LogStatus = ValuePresent | ValueMissing | Undefined
2010-10-10 02:29:16 +00:00
deriving (Eq)
instance Show LogStatus where
2010-10-13 00:04:36 +00:00
show ValuePresent = "1"
show ValueMissing = "0"
2010-10-10 02:29:16 +00:00
show Undefined = "undefined"
instance Read LogStatus where
2010-10-13 00:04:36 +00:00
readsPrec _ "1" = [(ValuePresent, "")]
readsPrec _ "0" = [(ValueMissing, "")]
2010-10-10 02:29:16 +00:00
readsPrec _ _ = [(Undefined, "")]
2010-10-09 23:22:40 +00:00
instance Show LogLine where
2010-10-31 19:50:07 +00:00
show (LogLine d s u) = unwords [show d, show s, u]
2010-10-09 23:22:40 +00:00
instance Read LogLine where
2010-10-10 02:29:16 +00:00
-- This parser is robust in that even unparsable log lines are
-- read without an exception being thrown.
-- Such lines have a status of Undefined.
readsPrec _ string =
if length w == 3
2011-05-15 06:49:43 +00:00
then maybe bad good pdate
2010-10-31 19:50:07 +00:00
else bad
2010-10-09 23:22:40 +00:00
where
w = words string
2010-10-31 19:50:07 +00:00
s = read $ w !! 1
u = w !! 2
pdate :: Maybe UTCTime
pdate = parseTime defaultTimeLocale "%s%Qs" $ head w
2010-10-31 19:50:07 +00:00
good v = ret $ LogLine (utcTimeToPOSIXSeconds v) s u
bad = ret $ LogLine 0 Undefined ""
ret v = [(v, "")]
2010-10-09 23:22:40 +00:00
2010-10-26 20:15:29 +00:00
{- Log a change in the presence of a key's value in a repository,
- and returns the filename of the logfile. -}
logChange :: Git.Repo -> Key -> UUID -> LogStatus -> Annex FilePath
2010-10-31 19:50:07 +00:00
logChange repo key u s = do
when (null u) $
error $ "unknown UUID for " ++ Git.repoDescribe repo ++
" (have you run git annex init there?)"
2010-10-31 19:50:07 +00:00
line <- logNow s u
let f = logFile repo key
ls' <- readLog $ logFileOld repo key
ls <- readLog f
writeLog f (compactLog $ line:ls'++ls)
return f
2010-10-12 22:25:41 +00:00
2010-10-10 03:35:05 +00:00
{- Reads a log file.
- Note that the LogLines returned may be in any order. -}
readLog :: FilePath -> Annex [LogLine]
readLog file = liftIO $ catch (return . parseLog =<< readFileStrict file) (const $ return [])
2010-10-09 23:22:40 +00:00
2011-01-30 03:47:10 +00:00
parseLog :: String -> [LogLine]
parseLog s = filter parsable $ map read $ lines s
where
-- some lines may be unparseable, avoid them
parsable l = status l /= Undefined
2010-10-10 16:31:14 +00:00
{- Writes a set of lines to a log file -}
writeLog :: FilePath -> [LogLine] -> Annex ()
writeLog file ls = liftIO $ safeWriteFile file (unlines $ map show ls)
2010-10-10 16:31:14 +00:00
2010-10-10 02:46:35 +00:00
{- Generates a new LogLine with the current date. -}
logNow :: LogStatus -> UUID -> Annex LogLine
2010-10-31 19:50:07 +00:00
logNow s u = do
now <- liftIO $ getPOSIXTime
2010-10-31 19:50:07 +00:00
return $ LogLine now s u
2010-10-10 02:14:13 +00:00
2010-10-12 22:25:41 +00:00
{- Returns a list of repository UUIDs that, according to the log, have
2010-10-13 00:04:36 +00:00
- the value of a key. -}
keyLocations :: Git.Repo -> Key -> Annex [UUID]
2010-10-13 00:04:36 +00:00
keyLocations thisrepo key = do
2010-10-31 19:50:07 +00:00
ls <- readLog $ logFile thisrepo key
ls' <- readLog $ logFileOld thisrepo key
return $ map uuid $ filterPresent $ ls'++ls
2010-10-10 02:46:35 +00:00
2010-10-13 00:04:36 +00:00
{- Filters the list of LogLines to find ones where the value
2010-10-10 02:46:35 +00:00
- is (or should still be) present. -}
filterPresent :: [LogLine] -> [LogLine]
2010-10-31 19:50:07 +00:00
filterPresent ls = filter (\l -> ValuePresent == status l) $ compactLog ls
type LogMap = Map.Map UUID LogLine
2010-10-10 16:31:14 +00:00
{- Compacts a set of logs, returning a subset that contains the current
- status. -}
compactLog :: [LogLine] -> [LogLine]
2010-10-31 19:50:07 +00:00
compactLog ls = compactLog' Map.empty ls
compactLog' :: LogMap -> [LogLine] -> [LogLine]
compactLog' m [] = Map.elems m
compactLog' m (l:ls) = compactLog' (mapLog m l) ls
2010-10-10 16:31:14 +00:00
{- Inserts a log into a map of logs, if the log has better (ie, newer)
- information about a repo than the other logs in the map -}
2010-10-31 19:50:07 +00:00
mapLog :: LogMap -> LogLine -> LogMap
mapLog m l =
if better
2010-10-31 19:50:07 +00:00
then Map.insert u l m
else m
2010-10-10 16:31:14 +00:00
where
2011-05-15 19:27:49 +00:00
better = maybe True (\l' -> date l' <= date l) $ Map.lookup u m
2010-10-31 19:50:07 +00:00
u = uuid l
2011-04-02 19:50:51 +00:00
{- Finds all keys that have location log information.
- (There may be duplicate keys in the list.) -}
loggedKeys :: Git.Repo -> Annex [Key]
2011-04-02 19:50:51 +00:00
loggedKeys repo = do
exists <- liftIO $ doesDirectoryExist dir
2011-04-02 19:50:51 +00:00
if exists
then do
-- 2 levels of hashing
levela <- liftIO $ dirContents dir
2011-04-02 19:50:51 +00:00
levelb <- mapM tryDirContents levela
files <- mapM tryDirContents (concat levelb)
return $ catMaybes $
map (logFileKey . takeFileName) (concat files)
else return []
where
tryDirContents d = liftIO $ catch (dirContents d) (return . const [])
2011-05-15 19:27:49 +00:00
dir = gitStateDir repo