2014-02-13 01:12:22 +00:00
|
|
|
{- git-annex command
|
|
|
|
-
|
2016-07-26 18:53:00 +00:00
|
|
|
- Copyright 2014-2016 Joey Hess <id@joeyh.name>
|
2014-02-13 01:12:22 +00:00
|
|
|
-
|
|
|
|
- Licensed under the GNU GPL version 3 or higher.
|
|
|
|
-}
|
|
|
|
|
|
|
|
module Command.MetaData where
|
|
|
|
|
|
|
|
import Command
|
2014-02-23 04:08:29 +00:00
|
|
|
import Annex.MetaData
|
2017-08-14 17:55:38 +00:00
|
|
|
import Annex.VectorClock
|
2014-02-13 01:12:22 +00:00
|
|
|
import Logs.MetaData
|
2016-07-27 14:46:25 +00:00
|
|
|
import Annex.WorkTree
|
2016-07-26 23:50:02 +00:00
|
|
|
import Messages.JSON (JSONActionItem(..))
|
2016-07-27 14:46:25 +00:00
|
|
|
import Types.Messages
|
2014-02-13 01:12:22 +00:00
|
|
|
|
|
|
|
import qualified Data.Set as S
|
2016-07-27 14:46:25 +00:00
|
|
|
import qualified Data.Map as M
|
2016-07-26 18:53:00 +00:00
|
|
|
import qualified Data.Text as T
|
|
|
|
import qualified Data.ByteString.Lazy.UTF8 as BU
|
|
|
|
import Data.Aeson
|
2016-12-13 15:07:49 +00:00
|
|
|
import Control.Concurrent
|
2014-02-13 01:12:22 +00:00
|
|
|
|
2015-07-08 16:33:27 +00:00
|
|
|
cmd :: Command
|
2015-07-12 13:25:43 +00:00
|
|
|
cmd = withGlobalOptions ([jsonOption] ++ annexedMatchingOptions) $
|
|
|
|
command "metadata" SectionMetaData
|
|
|
|
"sets or gets metadata of a file"
|
|
|
|
paramPaths (seek <$$> optParser)
|
2014-02-13 01:12:22 +00:00
|
|
|
|
2015-07-12 13:25:43 +00:00
|
|
|
data MetaDataOptions = MetaDataOptions
|
|
|
|
{ forFiles :: CmdParams
|
|
|
|
, getSet :: GetSet
|
|
|
|
, keyOptions :: Maybe KeyOptions
|
2016-07-27 14:46:25 +00:00
|
|
|
, batchOption :: BatchMode
|
2015-07-12 13:25:43 +00:00
|
|
|
}
|
2014-03-17 19:26:18 +00:00
|
|
|
|
2015-08-11 17:19:01 +00:00
|
|
|
data GetSet = Get MetaField | GetAll | Set [ModMeta]
|
2014-02-19 19:04:12 +00:00
|
|
|
|
2015-07-12 13:25:43 +00:00
|
|
|
optParser :: CmdParamsDesc -> Parser MetaDataOptions
|
|
|
|
optParser desc = MetaDataOptions
|
|
|
|
<$> cmdParams desc
|
2015-08-11 17:19:01 +00:00
|
|
|
<*> ((Get <$> getopt) <|> (Set <$> some modopts) <|> pure GetAll)
|
2016-08-03 16:37:12 +00:00
|
|
|
<*> optional parseKeyOptions
|
2016-07-27 14:46:25 +00:00
|
|
|
<*> parseBatchOption
|
2014-02-13 05:49:38 +00:00
|
|
|
where
|
2015-07-12 13:25:43 +00:00
|
|
|
getopt = option (eitherReader mkMetaField)
|
|
|
|
( long "get" <> short 'g' <> metavar paramField
|
|
|
|
<> help "get single metadata field"
|
|
|
|
)
|
|
|
|
modopts = option (eitherReader parseModMeta)
|
|
|
|
( long "set" <> short 's' <> metavar "FIELD[+-]=VALUE"
|
|
|
|
<> help "set or unset metadata value"
|
|
|
|
)
|
|
|
|
<|> (AddMeta tagMetaField . toMetaValue <$> strOption
|
|
|
|
( long "tag" <> short 't' <> metavar "TAG"
|
|
|
|
<> help "set a tag"
|
|
|
|
))
|
2016-02-29 17:00:46 +00:00
|
|
|
<|> (DelMeta tagMetaField . Just . toMetaValue <$> strOption
|
2015-07-12 13:25:43 +00:00
|
|
|
( long "untag" <> short 'u' <> metavar "TAG"
|
|
|
|
<> help "remove a tag"
|
|
|
|
))
|
2016-02-29 17:00:46 +00:00
|
|
|
<|> option (eitherReader (\f -> DelMeta <$> mkMetaField f <*> pure Nothing))
|
|
|
|
( long "remove" <> short 'r' <> metavar "FIELD"
|
|
|
|
<> help "remove all values of a field"
|
|
|
|
)
|
2014-02-19 19:04:12 +00:00
|
|
|
|
2015-07-12 13:25:43 +00:00
|
|
|
seek :: MetaDataOptions -> CommandSeek
|
2016-12-13 15:07:49 +00:00
|
|
|
seek o = case batchOption o of
|
|
|
|
NoBatch -> do
|
2017-08-14 17:55:38 +00:00
|
|
|
c <- liftIO currentVectorClock
|
2016-12-13 15:07:49 +00:00
|
|
|
let seeker = case getSet o of
|
|
|
|
Get _ -> withFilesInGit
|
|
|
|
GetAll -> withFilesInGit
|
|
|
|
Set _ -> withFilesInGitNonRecursive
|
|
|
|
"Not recursively setting metadata. Use --force to do that."
|
|
|
|
withKeyOptions (keyOptions o) False
|
2017-08-14 17:55:38 +00:00
|
|
|
(startKeys c o)
|
|
|
|
(seeker $ whenAnnexed $ start c o)
|
2016-12-13 15:07:49 +00:00
|
|
|
(forFiles o)
|
|
|
|
Batch -> withMessageState $ \s -> case outputType s of
|
|
|
|
JSONOutput _ -> batchInput parseJSONInput $
|
|
|
|
commandAction . startBatch
|
|
|
|
_ -> giveup "--batch is currently only supported in --json mode"
|
2014-02-13 01:12:22 +00:00
|
|
|
|
2017-08-14 17:55:38 +00:00
|
|
|
start :: VectorClock -> MetaDataOptions -> FilePath -> Key -> CommandStart
|
|
|
|
start c o file k = startKeys c o k (mkActionItem afile)
|
2016-07-20 19:22:55 +00:00
|
|
|
where
|
2017-03-10 17:12:24 +00:00
|
|
|
afile = AssociatedFile (Just file)
|
2014-03-17 19:26:18 +00:00
|
|
|
|
2017-08-14 17:55:38 +00:00
|
|
|
startKeys :: VectorClock -> MetaDataOptions -> Key -> ActionItem -> CommandStart
|
|
|
|
startKeys c o k ai = case getSet o of
|
2015-07-12 13:25:43 +00:00
|
|
|
Get f -> do
|
|
|
|
l <- S.toList . currentMetaDataValues f <$> getCurrentMetaData k
|
|
|
|
liftIO $ forM_ l $
|
|
|
|
putStrLn . fromMetaValue
|
|
|
|
stop
|
2015-08-11 17:19:01 +00:00
|
|
|
_ -> do
|
2016-07-20 19:22:55 +00:00
|
|
|
showStart' "metadata" k ai
|
2017-08-14 17:55:38 +00:00
|
|
|
next $ perform c o k
|
2015-08-11 17:19:01 +00:00
|
|
|
|
2017-08-14 17:55:38 +00:00
|
|
|
perform :: VectorClock -> MetaDataOptions -> Key -> CommandPerform
|
|
|
|
perform c o k = case getSet o of
|
2015-08-11 17:19:01 +00:00
|
|
|
Set ms -> do
|
|
|
|
oldm <- getCurrentMetaData k
|
|
|
|
let m = combineMetaData $ map (modMeta oldm) ms
|
2017-08-14 17:55:38 +00:00
|
|
|
addMetaData' k m c
|
2015-08-11 17:19:01 +00:00
|
|
|
next $ cleanup k
|
|
|
|
_ -> next $ cleanup k
|
2014-02-13 01:12:22 +00:00
|
|
|
|
2014-02-13 01:48:25 +00:00
|
|
|
cleanup :: Key -> CommandCleanup
|
|
|
|
cleanup k = do
|
2016-07-26 18:53:00 +00:00
|
|
|
m <- getCurrentMetaData k
|
|
|
|
let Object o = toJSON (MetaDataFields m)
|
|
|
|
maybeShowJSON $ AesonObject o
|
|
|
|
showLongNote $ unlines $ concatMap showmeta $
|
|
|
|
map unwrapmeta (fromMetaData m)
|
2014-02-13 01:12:22 +00:00
|
|
|
return True
|
|
|
|
where
|
2014-02-23 17:58:16 +00:00
|
|
|
unwrapmeta (f, v) = (fromMetaField f, map fromMetaValue (S.toList v))
|
|
|
|
showmeta (f, vs) = map ((f ++ "=") ++) vs
|
2016-07-26 18:53:00 +00:00
|
|
|
|
|
|
|
-- Metadata serialized to JSON in the field named "fields" of
|
|
|
|
-- a larger object.
|
|
|
|
newtype MetaDataFields = MetaDataFields MetaData
|
|
|
|
deriving (Show)
|
|
|
|
|
|
|
|
instance ToJSON MetaDataFields where
|
|
|
|
toJSON (MetaDataFields m) = object [ (fieldsField, toJSON m) ]
|
|
|
|
|
|
|
|
instance FromJSON MetaDataFields where
|
|
|
|
parseJSON (Object v) = do
|
|
|
|
f <- v .: fieldsField
|
|
|
|
case f of
|
|
|
|
Nothing -> return (MetaDataFields emptyMetaData)
|
|
|
|
Just v' -> MetaDataFields <$> parseJSON v'
|
|
|
|
parseJSON _ = fail "expected an object"
|
|
|
|
|
|
|
|
fieldsField :: T.Text
|
|
|
|
fieldsField = T.pack "fields"
|
|
|
|
|
2016-07-27 14:46:25 +00:00
|
|
|
parseJSONInput :: String -> Either String (Either FilePath Key, MetaData)
|
2016-07-26 18:53:00 +00:00
|
|
|
parseJSONInput i = do
|
2016-07-27 14:46:25 +00:00
|
|
|
v <- eitherDecode (BU.fromString i)
|
2016-07-26 23:50:02 +00:00
|
|
|
let m = case itemAdded v of
|
|
|
|
Nothing -> emptyMetaData
|
|
|
|
Just (MetaDataFields m') -> m'
|
2016-07-27 14:46:25 +00:00
|
|
|
case (itemKey v, itemFile v) of
|
|
|
|
(Just k, _) -> Right (Right k, m)
|
|
|
|
(Nothing, Just f) -> Right (Left f, m)
|
|
|
|
(Nothing, Nothing) -> Left "JSON input is missing either file or key"
|
|
|
|
|
2016-12-13 15:07:49 +00:00
|
|
|
startBatch :: (Either FilePath Key, MetaData) -> CommandStart
|
|
|
|
startBatch (i, (MetaData m)) = case i of
|
2016-07-27 14:46:25 +00:00
|
|
|
Left f -> do
|
|
|
|
mk <- lookupFile f
|
|
|
|
case mk of
|
2017-03-10 17:12:24 +00:00
|
|
|
Just k -> go k (mkActionItem (AssociatedFile (Just f)))
|
2016-11-16 01:29:54 +00:00
|
|
|
Nothing -> giveup $ "not an annexed file: " ++ f
|
2016-07-27 14:46:25 +00:00
|
|
|
Right k -> go k (mkActionItem k)
|
|
|
|
where
|
|
|
|
go k ai = do
|
|
|
|
showStart' "metadata" k ai
|
|
|
|
let o = MetaDataOptions
|
|
|
|
{ forFiles = []
|
|
|
|
, getSet = if MetaData m == emptyMetaData
|
|
|
|
then GetAll
|
|
|
|
else Set $ map mkModMeta (M.toList m)
|
|
|
|
, keyOptions = Nothing
|
|
|
|
, batchOption = NoBatch
|
|
|
|
}
|
2017-08-14 17:55:38 +00:00
|
|
|
t <- liftIO currentVectorClock
|
2016-12-13 15:07:49 +00:00
|
|
|
-- It would be bad if two batch mode changes used exactly
|
|
|
|
-- the same timestamp, since the order of adds and removals
|
|
|
|
-- of the same metadata value would then be indeterminate.
|
|
|
|
-- To guarantee that never happens, delay 1 microsecond,
|
|
|
|
-- so the timestamp will always be different. This is
|
|
|
|
-- probably less expensive than cleaner methods,
|
|
|
|
-- such as taking from a list of increasing timestamps.
|
|
|
|
liftIO $ threadDelay 1
|
2017-08-14 17:55:38 +00:00
|
|
|
next $ perform t o k
|
2016-07-27 14:46:25 +00:00
|
|
|
mkModMeta (f, s)
|
|
|
|
| S.null s = DelMeta f Nothing
|
|
|
|
| otherwise = SetMeta f s
|