1 module Rakka.Storage.Repos
2 ( findAllPagesInRevision
3 , findChangedPagesAtRevision
5 , putPageIntoRepository
9 import Control.Exception
12 import qualified Data.Map as M
15 import qualified Data.Set as S hiding (Set)
17 import Network.HTTP.Lucu hiding (redirect)
19 import Rakka.SystemConfig
21 import Rakka.W3CDateTime
22 import Subversion.Error
23 import Subversion.FileSystem
24 import Subversion.FileSystem.DirEntry
25 import Subversion.FileSystem.Revision
26 import Subversion.FileSystem.Root
27 import Subversion.FileSystem.Transaction
28 import Subversion.Repository
29 import Subversion.Types
30 import System.FilePath.Posix
33 mkPagePath :: PageName -> FilePath
35 = "/pages" </> encodePageName name <.> "page"
38 findAllPagesInRevision :: Repository -> RevNum -> IO (Set PageName)
39 findAllPagesInRevision repos rev
40 = do fs <- getRepositoryFS repos
42 $ do exists <- isDirectory root
51 traverse :: FilePath -> Rev (Set PageName)
53 = getDirEntries dir >>= mapM (traverse' dir) >>= return . S.unions
55 traverse' :: FilePath -> DirEntry -> Rev (Set PageName)
57 = let path = dir </> entName entry
59 do kind <- checkPath path
61 NoNode -> return S.empty
62 FileNode -> return $ S.singleton (decodePath path)
63 DirNode -> traverse path
65 decodePath :: FilePath -> PageName
66 decodePath = decodePageName . makeRelative root . dropExtension
69 findChangedPagesAtRevision :: Repository -> RevNum -> IO (Set PageName)
70 findChangedPagesAtRevision repos rev
71 = do fs <- getRepositoryFS repos
73 $ getPathsChanged >>= return . foldl accumulatePages S.empty . map fst
75 accumulatePages :: Set PageName -> FilePath -> Set PageName
76 accumulatePages s path
77 | "/pages/" `isPrefixOf` path && ".page" `isSuffixOf` path
78 = let encoded = makeRelative "/pages" $ dropExtension path
79 name = decodePageName encoded
86 loadPageInRepository :: Repository -> PageName -> Maybe RevNum -> IO (Maybe Page)
87 loadPageInRepository repos name rev
88 = do fs <- getRepositoryFS repos
90 Nothing -> getYoungestRev fs
93 $ do exists <- isFile path
96 -> return . Just =<< loadPage'
101 path = mkPagePath name
103 loadPage' :: Rev Page
104 loadPage' = do redirect <- getNodeProp path "rakka:redirect"
111 loadPageEntity :: Rev Page
113 = do props <- getNodePropList path
114 hist <- getNodeHistory True path
115 content <- getFileContentsLBS path
117 let pageRev = fst $ head hist
119 $ fromMaybe "text/x-rakka"
120 $ fmap chomp (lookup "svn:mime-type" props)
122 lastMod <- getRevisionProp "svn:date"
123 >>= return . fromJust . parseW3CDateTime . chomp . fromJust
127 , entityType = mimeType
128 , entityLanguage = fmap chomp (lookup "rakka:lang" props)
129 , entityFileName = fmap chomp (lookup "rakka:fileName" props)
130 , entityIsTheme = any ((== "rakka:isTheme") . fst) props
131 , entityIsFeed = any ((== "rakka:isFeed") . fst) props
132 , entityIsLocked = any ((== "rakka:isLocked") . fst) props
133 , entityIsBoring = any ((== "rakka:isBoring") . fst) props
134 , entityIsBinary = case mimeType of
136 -> any ((== "rakka:isBinary") . fst) props
139 , entityRevision = pageRev
140 , entityLastMod = zonedTimeToUTC lastMod
141 , entitySummary = lookup "rakka:summary" props
142 , entityOtherLang = fromMaybe M.empty
144 (M.fromList . fromJust . deserializeStringPairs)
145 (lookup "rakka:otherLang" props)
146 , entityContent = content
147 , entityUpdateInfo = undefined
150 loadPageRedirect :: Rev Page
151 loadPageRedirect = fail "FIXME: loadPageRedirect: not implemented"
154 putPageIntoRepository :: Repository -> Page -> IO StatusCode
155 putPageIntoRepository repos page
157 do let name = pageName page
158 ret <- case pageUpdateInfo page of
164 (Just "Automatic commit by Rakka for page update")
165 $ do case uiOldName ui of
167 Just oldName -> renamePage oldName name
170 -> do fs <- getRepositoryFS repos
171 rev <- getYoungestRev fs
175 (Just "Automatic commit by Rakka for page creation")
179 Left _ -> return Conflict
180 Right _ -> return Created
182 renamePage :: PageName -> PageName -> Txn ()
183 renamePage oldName newName
184 = fail "FIXME: renamePage: not implemented yet"
186 createPage :: PageName -> Txn ()
188 = do let path = mkPagePath name
189 createParentDirectories path
192 createParentDirectories :: FilePath -> Txn ()
193 createParentDirectories path
194 = do let parentPath = takeDirectory path
195 kind <- checkPath parentPath
197 NoNode -> do createParentDirectories parentPath
198 makeDirectory parentPath
199 FileNode -> fail ("createParentDirectories: already exists a file: " ++ parentPath)
202 updatePage :: PageName -> Txn ()
204 | isRedirect page = updatePageRedirect name
205 | isEntity page = updatePageEntity name
206 | otherwise = fail "neither redirection nor page"
208 updatePageRedirect :: PageName -> Txn ()
209 updatePageRedirect name
210 = fail "FIXME: updatePageRedirect: not implemented yet"
212 updatePageEntity :: PageName -> Txn ()
213 updatePageEntity name
214 = do let path = mkPagePath name
215 setNodeProp path "svn:mime-type" ((Just . show . entityType) page)
216 setNodeProp path "rakka:lang" (entityLanguage page)
217 setNodeProp path "rakka:fileName" (entityFileName page)
218 setNodeProp path "rakka:isTheme" (encodeFlag $ entityIsTheme page)
219 setNodeProp path "rakka:isFeed" (encodeFlag $ entityIsFeed page)
220 setNodeProp path "rakka:isLocked" (encodeFlag $ entityIsLocked page)
221 setNodeProp path "rakka:isBoring" (encodeFlag $ entityIsBoring page)
222 setNodeProp path "rakka:isBinary" (encodeFlag $ entityIsBinary page)
223 setNodeProp path "rakka:summary" (entitySummary page)
224 setNodeProp path "rakka:otherLang" (let otherLang = entityOtherLang page
226 if M.null otherLang then
229 Just (serializeStringPairs $ M.toList otherLang))
230 applyTextLBS path Nothing (entityContent page)
232 encodeFlag :: Bool -> Maybe String
233 encodeFlag True = Just "*\n"
234 encodeFlag False = Nothing
237 filterSvnError :: IO a -> IO a
238 filterSvnError f = catchDyn f rethrow
240 rethrow :: SvnError -> IO a
242 = let code = svnErrCode err
245 fail $ "SvnError: " ++ (show code) ++ ": " ++ msg