{-# LANGUAGE ScopedTypeVariables, CPP #-}
module Data.FileStore.Generic
( modify
, create
, Diff
, PolyDiff(..)
, diff
, searchRevisions
, smartRetrieve
, richDirectory
)
where
import Data.FileStore.Types
import Control.Exception as E
import Data.FileStore.Utils
import Data.List (isInfixOf)
import Data.Algorithm.Diff (Diff, PolyDiff (..), getGroupedDiff)
import System.FilePath ((</>))
handleUnknownError :: E.SomeException -> IO a
handleUnknownError :: forall a. SomeException -> IO a
handleUnknownError = forall e a. Exception e => e -> IO a
E.throwIO forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> FileStoreError
UnknownError forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Show a => a -> String
show
create :: Contents a
=> FileStore
-> FilePath
-> Author
-> Description
-> a
-> IO ()
create :: forall a.
Contents a =>
FileStore -> String -> Author -> String -> a -> IO ()
create FileStore
fs String
name Author
author String
logMsg a
contents = forall e a. Exception e => IO a -> (e -> IO a) -> IO a
E.catch (FileStore -> String -> IO String
latest FileStore
fs String
name forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> forall e a. Exception e => e -> IO a
E.throwIO FileStoreError
ResourceExists)
(\FileStoreError
e -> if FileStoreError
e forall a. Eq a => a -> a -> Bool
== FileStoreError
NotFound
then FileStore
-> forall a. Contents a => String -> Author -> String -> a -> IO ()
save FileStore
fs String
name Author
author String
logMsg a
contents
else forall e a. Exception e => e -> IO a
E.throwIO FileStoreError
e)
modify :: Contents a
=> FileStore
-> FilePath
-> RevisionId
-> Author
-> Description
-> a
-> IO (Either MergeInfo ())
modify :: forall a.
Contents a =>
FileStore
-> String
-> String
-> Author
-> String
-> a
-> IO (Either MergeInfo ())
modify FileStore
fs String
name String
originalRevId Author
author String
msg a
contents = do
String
latestRevId <- FileStore -> String -> IO String
latest FileStore
fs String
name
Revision
latestRev <- FileStore -> String -> IO Revision
revision FileStore
fs String
latestRevId
if FileStore -> String -> String -> Bool
idsMatch FileStore
fs String
originalRevId String
latestRevId
then FileStore
-> forall a. Contents a => String -> Author -> String -> a -> IO ()
save FileStore
fs String
name Author
author String
msg a
contents forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> forall (m :: * -> *) a. Monad m => a -> m a
return (forall a b. b -> Either a b
Right ())
else do
ByteString
latestContents <- FileStore -> forall a. Contents a => String -> Maybe String -> IO a
retrieve FileStore
fs String
name (forall a. a -> Maybe a
Just String
latestRevId)
ByteString
originalContents <- FileStore -> forall a. Contents a => String -> Maybe String -> IO a
retrieve FileStore
fs String
name (forall a. a -> Maybe a
Just String
originalRevId)
(Bool
conflicts, String
mergedText) <- forall e a. Exception e => IO a -> (e -> IO a) -> IO a
E.catch
((String, ByteString)
-> (String, ByteString)
-> (String, ByteString)
-> IO (Bool, String)
mergeContents (String
"edited", forall a. Contents a => a -> ByteString
toByteString a
contents) (String
originalRevId, ByteString
originalContents) (String
latestRevId, ByteString
latestContents))
forall a. SomeException -> IO a
handleUnknownError
forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall a b. a -> Either a b
Left (Revision -> Bool -> String -> MergeInfo
MergeInfo Revision
latestRev Bool
conflicts String
mergedText)
diff :: FileStore
-> FilePath
-> Maybe RevisionId
-> Maybe RevisionId
-> IO [Diff [String]]
diff :: FileStore
-> String -> Maybe String -> Maybe String -> IO [Diff [String]]
diff FileStore
fs String
name Maybe String
Nothing Maybe String
id2 = do
String
contents2 <- FileStore -> forall a. Contents a => String -> Maybe String -> IO a
retrieve FileStore
fs String
name Maybe String
id2
forall (m :: * -> *) a. Monad m => a -> m a
return [forall a b. b -> PolyDiff a b
Second (String -> [String]
lines String
contents2) ]
diff FileStore
fs String
name Maybe String
id1 Maybe String
id2 = do
String
contents1 <- FileStore -> forall a. Contents a => String -> Maybe String -> IO a
retrieve FileStore
fs String
name Maybe String
id1
String
contents2 <- FileStore -> forall a. Contents a => String -> Maybe String -> IO a
retrieve FileStore
fs String
name Maybe String
id2
forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall a. Eq a => [a] -> [a] -> [Diff [a]]
getGroupedDiff (String -> [String]
lines String
contents1) (String -> [String]
lines String
contents2)
searchRevisions :: FileStore
-> Bool
-> FilePath
-> Description
-> IO [Revision]
searchRevisions :: FileStore -> Bool -> String -> String -> IO [Revision]
searchRevisions FileStore
repo Bool
exact String
name String
desc = do
let matcher :: String -> Bool
matcher = if Bool
exact
then (forall a. Eq a => a -> a -> Bool
== String
desc)
else (String
desc forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf`)
[Revision]
revs <- FileStore -> [String] -> TimeRange -> Maybe Int -> IO [Revision]
history FileStore
repo [String
name] (Maybe UTCTime -> Maybe UTCTime -> TimeRange
TimeRange forall a. Maybe a
Nothing forall a. Maybe a
Nothing) forall a. Maybe a
Nothing
forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall a. (a -> Bool) -> [a] -> [a]
Prelude.filter (String -> Bool
matcher forall b c a. (b -> c) -> (a -> b) -> a -> c
. Revision -> String
revDescription) [Revision]
revs
smartRetrieve
:: Contents a
=> FileStore
-> Bool
-> FilePath
-> Maybe String
-> IO a
smartRetrieve :: forall a.
Contents a =>
FileStore -> Bool -> String -> Maybe String -> IO a
smartRetrieve FileStore
fs Bool
exact String
name Maybe String
mrev = do
Either FileStoreError a
edoc <- forall e a. Exception e => IO a -> IO (Either e a)
E.try (FileStore -> forall a. Contents a => String -> Maybe String -> IO a
retrieve FileStore
fs String
name Maybe String
mrev)
case (Either FileStoreError a
edoc, Maybe String
mrev) of
(Right a
doc, Maybe String
_) -> forall (m :: * -> *) a. Monad m => a -> m a
return a
doc
(Left FileStoreError
e, Maybe String
Nothing) -> forall e a. Exception e => e -> IO a
E.throwIO (FileStoreError
e :: FileStoreError)
(Left FileStoreError
_, Just String
rev) -> do
[Revision]
revs <- FileStore -> Bool -> String -> String -> IO [Revision]
searchRevisions FileStore
fs Bool
exact String
name String
rev
if forall (t :: * -> *) a. Foldable t => t a -> Bool
Prelude.null [Revision]
revs
then forall e a. Exception e => e -> IO a
E.throwIO FileStoreError
NotFound
else FileStore -> forall a. Contents a => String -> Maybe String -> IO a
retrieve FileStore
fs String
name (forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ Revision -> String
revId forall a b. (a -> b) -> a -> b
$ forall a. [a] -> a
Prelude.head [Revision]
revs)
richDirectory :: FileStore -> FilePath -> IO [(Resource, Either String Revision)]
richDirectory :: FileStore -> String -> IO [(Resource, Either String Revision)]
richDirectory FileStore
fs String
fp = FileStore -> String -> IO [Resource]
directory FileStore
fs String
fp forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
mapM Resource -> IO (Resource, Either String Revision)
f
where f :: Resource -> IO (Resource, Either String Revision)
f Resource
r = forall e a. Exception e => IO a -> (e -> IO a) -> IO a
E.catch (Resource -> IO (Resource, Either String Revision)
g Resource
r) (\(FileStoreError
e :: FileStoreError)-> forall (m :: * -> *) a. Monad m => a -> m a
return ( Resource
r, forall a b. a -> Either a b
Left forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Show a => a -> String
show forall a b. (a -> b) -> a -> b
$ FileStoreError
e ) )
g :: Resource -> IO (Resource, Either String Revision)
g r :: Resource
r@(FSDirectory String
_dir) = forall (m :: * -> *) a. Monad m => a -> m a
return (Resource
r,forall a b. a -> Either a b
Left String
"richDirectory, we don't care about revision info for directories")
g res :: Resource
res@(FSFile String
file) = do Revision
rev <- FileStore -> String -> IO Revision
revision FileStore
fs forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< FileStore -> String -> IO String
latest FileStore
fs ( String
fp String -> String -> String
</> String
file )
forall (m :: * -> *) a. Monad m => a -> m a
return (Resource
res,forall a b. b -> Either a b
Right Revision
rev)