blob: 0d6fe910c6073a778587a8f16f358df7fb0aaf33 [file] [log] [blame]
Matthias Andreas Benkard262110d2021-08-24 06:35:55 +02001{-# LANGUAGE BlockArguments #-}
2{-# LANGUAGE DuplicateRecordFields #-}
3{-# LANGUAGE LambdaCase #-}
4{-# LANGUAGE TypeOperators #-}
5{-# LANGUAGE UndecidableInstances #-}
6
7module Mulkup.Bupstash (BupItem(..), Bupstash (..), bupPut, bupGc, bupList, bupRemove, BupFilter (..), runBupstash, bupItemUTCTime) where
8
9import Mulkup.Config (MulkupConfig (..))
Matthias Andreas Benkardb06f5912026-05-25 17:32:24 +020010import Mulkup.Flags (Flags (..))
Matthias Andreas Benkard262110d2021-08-24 06:35:55 +020011import Mulkup.Prelude hiding (put)
12import Optics
13import Polysemy
14import Polysemy.Reader (Reader, asks)
15import Turtle hiding (err, x)
16import Data.Aeson (FromJSON, eitherDecode)
17import Polysemy.Error (Error, throw)
18import Data.Text (pack, unpack)
19import Mulkup.Logging
20import Colog.Polysemy (Log)
21import Colog (Message)
22import Data.Time
23import Data.Time.Clock.POSIX
24
25-- * API
26
27data BupItem = BupItem
28 { id :: Text,
29 unix_timestamp_millis :: Integer,
30 tags :: Map String String
31 }
32 deriving stock (Generic, Show)
33 deriving anyclass (FromJSON)
34
35data BupFilter = BupFilter
36 { labels :: [(Text, Text)],
37 minimumAge :: Maybe Text
38 }
39
40makeFieldLabelsNoPrefix ''BupFilter
41
42data Bupstash m a where
43 BupGc :: Bupstash m ()
44 BupPut :: Text -> [Text] -> [(Text, Text)] -> Bupstash m ()
45 BupList :: BupFilter -> Bupstash m [BupItem]
46 BupRemove :: [Text] -> Bupstash m ()
47
48makeSem ''Bupstash
49
50-- * Smart Accessors
51
52bupItemUTCTime :: BupItem -> UTCTime
53bupItemUTCTime item =
54 posixSecondsToUTCTime $
55 secondsToNominalDiffTime $
56 fromInteger (item ^. #unix_timestamp_millis) / 1000
57
58-- * Implementation
59
Matthias Andreas Benkardb06f5912026-05-25 17:32:24 +020060-- | Runs a 'Bupstash' using the "bupstash" CLI command.
61runBupstash :: (Member (Error Text) r, Member (Log Message) r, Member (Embed IO) r, Member (Reader MulkupConfig) r, Member (Reader Flags) r) => Sem (Bupstash ': r) a -> Sem r a
Matthias Andreas Benkard262110d2021-08-24 06:35:55 +020062runBupstash = interpret \case
63 BupGc ->
64 procs "bupstash" ["gc"] empty
65
66 BupPut baseDir exclusions labels -> do
67 host <- getHost
Matthias Andreas Benkardb06f5912026-05-25 17:32:24 +020068 verboseArg <- getVerboseArg
69 procs "bupstash" (["put", "--print-stats", "--xattrs"] ++ verboseArg ++ map exclusionArg exclusions ++ map labelArg labels ++ [labelArg ("host", host)] ++ [baseDir]) empty
Matthias Andreas Benkard262110d2021-08-24 06:35:55 +020070
71 BupList bupFilter -> do
72 host <- getHost
73 out <- strict $ inproc "bupstash" (["list", "--format=jsonl1"] ++ filterArgs host bupFilter) empty
74 let parsedItems = map parseItem (patchLines (lines out))
75 forM parsedItems \case
76 Left err -> do
77 let errtext = pack err
78 logError errtext
79 throw errtext
80 Right x ->
81 return x
82
83 BupRemove ids -> do
Matthias Andreas Benkarde1d0bbb2021-08-24 20:42:38 +020084 forM_ ids $ \id' -> do
85 procs "bupstash" ["rm", labelArg ("id", id')] empty
Matthias Andreas Benkard262110d2021-08-24 06:35:55 +020086
87 where
88 getHost :: Member (Reader MulkupConfig) r => Sem r Text
89 getHost =
90 asks @MulkupConfig (^. #host)
91
Matthias Andreas Benkardb06f5912026-05-25 17:32:24 +020092 getVerboseArg :: (Member (Reader Flags) r) => Sem r [Text]
93 getVerboseArg = do
94 isVerbose <- asks @Flags (^. #verbose)
95 return $ if isVerbose then ["-v"] else []
96
Matthias Andreas Benkard262110d2021-08-24 06:35:55 +020097-- | Fixes up the buggy two-line output that Buptash produces in
98-- jsonl1 output mode.
99--
100-- See: https://github.com/andrewchambers/bupstash/pull/241
101patchLines :: [Text] -> [Text]
102patchLines = concatMap patchLine
103 where
104 patchLine :: Text -> [Text]
105 patchLine line
106 | line == "}" =
107 []
108 | length (filter (== '}') $ unpack line) < length (filter (== '{') $ unpack line) =
109 [line <> "}"]
110 | otherwise =
111 [line]
112
113labelArg :: (Text, Text) -> Text
114labelArg (key, value) = key <> "=" <> value
115
116exclusionArg :: Text -> Text
117exclusionArg = ("--exclude=" <>)
118
119filterArgs :: Text -> BupFilter -> [Text]
120filterArgs host (BupFilter labels minimumAge) =
121 [labelArg ("host", host)] ++
122 concatMap (\label -> ["and", labelArg label]) labels ++
123 concatMap (\x -> ["and", "older-than", x]) minimumAge
124
125parseItem :: Text -> Either String BupItem
126parseItem = eitherDecode . encodeUtf8