2019-02-20 19:36:09 +00:00
|
|
|
{- Remote content identifier logs, pure operations.
|
|
|
|
-
|
|
|
|
- Copyright 2019 Joey Hess <id@joeyh.name>
|
|
|
|
-
|
|
|
|
- Licensed under the GNU AGPL version 3 or higher.
|
|
|
|
-}
|
|
|
|
|
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
|
2019-02-21 16:22:50 +00:00
|
|
|
module Logs.ContentIdentifier.Pure where
|
2019-02-20 19:36:09 +00:00
|
|
|
|
|
|
|
import Annex.Common
|
2019-02-21 17:43:21 +00:00
|
|
|
import Logs.UUIDBased
|
|
|
|
import Types.Import
|
2019-02-20 19:36:09 +00:00
|
|
|
import Utility.Base64
|
|
|
|
|
|
|
|
import qualified Data.ByteString as S
|
|
|
|
import qualified Data.ByteString.Char8 as S8
|
|
|
|
import qualified Data.ByteString.Lazy as L
|
2021-03-24 16:11:50 +00:00
|
|
|
import qualified Data.Attoparsec.ByteString as A
|
2019-02-20 19:36:09 +00:00
|
|
|
import qualified Data.Attoparsec.ByteString.Char8 as A8
|
|
|
|
import Data.ByteString.Builder
|
2019-03-06 18:17:33 +00:00
|
|
|
import Data.List.NonEmpty (NonEmpty(..))
|
|
|
|
import qualified Data.List.NonEmpty
|
2019-02-20 19:36:09 +00:00
|
|
|
|
2019-03-06 18:17:33 +00:00
|
|
|
-- A ContentIdentifier can contain "", so to avoid ambiguity
|
|
|
|
-- in parsing, the list of them in the log must be non-empty.
|
|
|
|
type ContentIdentifierLog = Log (NonEmpty ContentIdentifier)
|
|
|
|
|
|
|
|
contentIdentifierList :: Maybe (NonEmpty ContentIdentifier) -> [ContentIdentifier]
|
|
|
|
contentIdentifierList (Just l) = Data.List.NonEmpty.toList l
|
|
|
|
contentIdentifierList Nothing = []
|
2019-02-20 19:36:09 +00:00
|
|
|
|
|
|
|
buildLog :: ContentIdentifierLog -> Builder
|
2019-02-21 17:43:21 +00:00
|
|
|
buildLog = buildLogNew buildContentIdentifierList
|
2019-02-21 16:22:50 +00:00
|
|
|
|
2019-03-06 18:17:33 +00:00
|
|
|
buildContentIdentifierList :: (NonEmpty ContentIdentifier) -> Builder
|
2019-02-21 16:22:50 +00:00
|
|
|
buildContentIdentifierList l = case l of
|
2019-03-06 18:17:33 +00:00
|
|
|
c :| [] -> buildcid c
|
|
|
|
(c :| cs) -> go (c:cs)
|
2019-02-20 19:36:09 +00:00
|
|
|
where
|
|
|
|
buildcid (ContentIdentifier c)
|
2019-02-21 17:45:16 +00:00
|
|
|
| S8.any (`elem` [':', '\r', '\n']) c || "!" `S8.isPrefixOf` c =
|
2019-02-20 19:36:09 +00:00
|
|
|
charUtf8 '!' <> byteString (toB64' c)
|
|
|
|
| otherwise = byteString c
|
2019-03-06 18:17:33 +00:00
|
|
|
go [] = mempty
|
2019-03-06 18:43:18 +00:00
|
|
|
go (c:[]) = buildcid c
|
|
|
|
go (c:cs) = buildcid c <> charUtf8 ':' <> go cs
|
2019-02-20 19:36:09 +00:00
|
|
|
|
|
|
|
parseLog :: L.ByteString -> ContentIdentifierLog
|
2019-02-21 17:43:21 +00:00
|
|
|
parseLog = parseLogNew parseContentIdentifierList
|
2019-02-21 16:22:50 +00:00
|
|
|
|
2019-03-06 18:17:33 +00:00
|
|
|
parseContentIdentifierList :: A.Parser (NonEmpty ContentIdentifier)
|
|
|
|
parseContentIdentifierList = do
|
|
|
|
first <- cidparser
|
|
|
|
listparser first []
|
2019-02-20 19:36:09 +00:00
|
|
|
where
|
2019-03-06 18:17:33 +00:00
|
|
|
cidparser = do
|
2019-02-21 17:45:16 +00:00
|
|
|
b <- A8.takeWhile (/= ':')
|
2019-03-06 18:17:33 +00:00
|
|
|
return $ if "!" `S8.isPrefixOf` b
|
|
|
|
then ContentIdentifier $ fromMaybe b (fromB64Maybe' (S.drop 1 b))
|
|
|
|
else ContentIdentifier b
|
2019-03-06 18:43:18 +00:00
|
|
|
listparser first rest = ifM A8.atEnd
|
|
|
|
( return (first :| reverse rest)
|
|
|
|
, do
|
|
|
|
_ <- A8.char ':'
|
|
|
|
cid <- cidparser
|
|
|
|
listparser first (cid:rest)
|
|
|
|
)
|
2019-02-21 16:22:50 +00:00
|
|
|
|
2019-03-06 20:43:41 +00:00
|
|
|
prop_parse_build_contentidentifier_log :: NonEmpty ContentIdentifier -> Bool
|
2019-02-21 16:22:50 +00:00
|
|
|
prop_parse_build_contentidentifier_log l =
|
2019-03-06 20:43:41 +00:00
|
|
|
let v = A.parseOnly parseContentIdentifierList $ L.toStrict $
|
|
|
|
toLazyByteString $ buildContentIdentifierList l
|
|
|
|
in v == Right l
|