tagging version 4.20130802
-----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.12 (GNU/Linux) iQIVAwUAUfvuiskQ2SIlEuPHAQhMtRAArjO6f20PXrrfOqiA0X322BPeA7LZ1Fqk jF6npxtLPE/ZqL+fJ7lC2pjBomqYYr1d9eTTQm5+L+6YYBu3ICFFNYkEefpLrj7N JYQsw9WFcJ+c7q3CVw7HtDwJlZ8g/NrZsOLAtNrsDiS6edpY5YBN67R7fKu5f2CY 0dAbSAGo16ViEpPRaJZ+lIJyqxy8VTvMy82Zi+90o//Iu4Abz/BfycVXEXapxL3g GRmcVYW3eY6Kwu/GGlzhcuseKUu7PA8RqCSNyPmihpCB2xPJ2EEiAUYQhLSE0k3L aYxpTi0rXVhRV7Ht5AsfBKs5u6gdpsic18M7Wn9E6+L0P0NTYdF+rjj3YmLaYx7Y jFcKDzHUad8GwsrbHfHBnbI546yLfu9b3pvEDgZ+JTw/YLKcDqFA1cvp8E/Kfi/x rZxU9cyBnWd+gksJp93QfhLjxXUDfc5f1dNJ1/W13/kozPkrHTvVNNLosvR80+gE /KH5N2WiQsK0FYuKXCNVvW5EfQ0H8fUYc3rXcTpSawOVWfstmGsLLQWxeeK+NsEW B0ktmLXku7wbWeVnDgZ9WP8/NrbU2MoPry6Pvlpbh8SEpd+22fESJr+tiSv9ZwsK 3JaF51uDo24bDoMkc9w7nPMApJUo45aBry5GP2Z3/yj29NsroXnsAW68AOkCkCaL aBLNp754fD8= =Qtd1 -----END PGP SIGNATURE----- Merge tag '4.20130802' into debian-wheezy-backport Conflicts: debian/control
This commit is contained in:
commit
b17ec21746
1312 changed files with 28294 additions and 2041 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -13,8 +13,6 @@ git-union-merge.1
|
||||||
doc/.ikiwiki
|
doc/.ikiwiki
|
||||||
html
|
html
|
||||||
*.tix
|
*.tix
|
||||||
*.o
|
|
||||||
*.hi
|
|
||||||
.hpc
|
.hpc
|
||||||
dist
|
dist
|
||||||
# Sandboxed builds
|
# Sandboxed builds
|
||||||
|
@ -26,3 +24,5 @@ cabal-dev
|
||||||
.virthualenv
|
.virthualenv
|
||||||
tags
|
tags
|
||||||
Setup
|
Setup
|
||||||
|
*.hi
|
||||||
|
*.o
|
||||||
|
|
12
Annex.hs
12
Annex.hs
|
@ -10,7 +10,6 @@
|
||||||
module Annex (
|
module Annex (
|
||||||
Annex,
|
Annex,
|
||||||
AnnexState(..),
|
AnnexState(..),
|
||||||
FileInfo(..),
|
|
||||||
PreferredContentMap,
|
PreferredContentMap,
|
||||||
new,
|
new,
|
||||||
newState,
|
newState,
|
||||||
|
@ -55,6 +54,7 @@ import Types.TrustLevel
|
||||||
import Types.Group
|
import Types.Group
|
||||||
import Types.Messages
|
import Types.Messages
|
||||||
import Types.UUID
|
import Types.UUID
|
||||||
|
import Types.FileMatcher
|
||||||
import qualified Utility.Matcher
|
import qualified Utility.Matcher
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
import qualified Data.Set as S
|
import qualified Data.Set as S
|
||||||
|
@ -74,12 +74,6 @@ newtype Annex a = Annex { runAnnex :: ReaderT (MVar AnnexState) IO a }
|
||||||
)
|
)
|
||||||
|
|
||||||
type Matcher a = Either [Utility.Matcher.Token a] (Utility.Matcher.Matcher a)
|
type Matcher a = Either [Utility.Matcher.Token a] (Utility.Matcher.Matcher a)
|
||||||
|
|
||||||
data FileInfo = FileInfo
|
|
||||||
{ relFile :: FilePath -- may be relative to cwd
|
|
||||||
, matchFile :: FilePath -- filepath to match on; may be relative to top
|
|
||||||
}
|
|
||||||
|
|
||||||
type PreferredContentMap = M.Map UUID (Utility.Matcher.Matcher (S.Set UUID -> FileInfo -> Annex Bool))
|
type PreferredContentMap = M.Map UUID (Utility.Matcher.Matcher (S.Set UUID -> FileInfo -> Annex Bool))
|
||||||
|
|
||||||
-- internal state storage
|
-- internal state storage
|
||||||
|
@ -92,11 +86,13 @@ data AnnexState = AnnexState
|
||||||
, force :: Bool
|
, force :: Bool
|
||||||
, fast :: Bool
|
, fast :: Bool
|
||||||
, auto :: Bool
|
, auto :: Bool
|
||||||
|
, daemon :: Bool
|
||||||
, branchstate :: BranchState
|
, branchstate :: BranchState
|
||||||
, repoqueue :: Maybe Git.Queue.Queue
|
, repoqueue :: Maybe Git.Queue.Queue
|
||||||
, catfilehandles :: M.Map FilePath CatFileHandle
|
, catfilehandles :: M.Map FilePath CatFileHandle
|
||||||
, checkattrhandle :: Maybe CheckAttrHandle
|
, checkattrhandle :: Maybe CheckAttrHandle
|
||||||
, forcebackend :: Maybe String
|
, forcebackend :: Maybe String
|
||||||
|
, forcenumcopies :: Maybe Int
|
||||||
, limit :: Matcher (FileInfo -> Annex Bool)
|
, limit :: Matcher (FileInfo -> Annex Bool)
|
||||||
, uuidmap :: Maybe UUIDMap
|
, uuidmap :: Maybe UUIDMap
|
||||||
, preferredcontentmap :: Maybe PreferredContentMap
|
, preferredcontentmap :: Maybe PreferredContentMap
|
||||||
|
@ -122,11 +118,13 @@ newState gitrepo = AnnexState
|
||||||
, force = False
|
, force = False
|
||||||
, fast = False
|
, fast = False
|
||||||
, auto = False
|
, auto = False
|
||||||
|
, daemon = False
|
||||||
, branchstate = startBranchState
|
, branchstate = startBranchState
|
||||||
, repoqueue = Nothing
|
, repoqueue = Nothing
|
||||||
, catfilehandles = M.empty
|
, catfilehandles = M.empty
|
||||||
, checkattrhandle = Nothing
|
, checkattrhandle = Nothing
|
||||||
, forcebackend = Nothing
|
, forcebackend = Nothing
|
||||||
|
, forcenumcopies = Nothing
|
||||||
, limit = Left []
|
, limit = Left []
|
||||||
, uuidmap = Nothing
|
, uuidmap = Nothing
|
||||||
, preferredcontentmap = Nothing
|
, preferredcontentmap = Nothing
|
||||||
|
|
|
@ -21,6 +21,7 @@ module Annex.Branch (
|
||||||
change,
|
change,
|
||||||
commit,
|
commit,
|
||||||
files,
|
files,
|
||||||
|
withIndex,
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import qualified Data.ByteString.Lazy.Char8 as L
|
import qualified Data.ByteString.Lazy.Char8 as L
|
||||||
|
@ -65,7 +66,7 @@ hasSibling = not . null <$> siblingBranches
|
||||||
{- List of git-annex (refs, branches), including the main one and any
|
{- List of git-annex (refs, branches), including the main one and any
|
||||||
- from remotes. Duplicate refs are filtered out. -}
|
- from remotes. Duplicate refs are filtered out. -}
|
||||||
siblingBranches :: Annex [(Git.Ref, Git.Branch)]
|
siblingBranches :: Annex [(Git.Ref, Git.Branch)]
|
||||||
siblingBranches = inRepo $ Git.Ref.matchingUniq name
|
siblingBranches = inRepo $ Git.Ref.matchingUniq [name]
|
||||||
|
|
||||||
{- Creates the branch, if it does not already exist. -}
|
{- Creates the branch, if it does not already exist. -}
|
||||||
create :: Annex ()
|
create :: Annex ()
|
||||||
|
|
|
@ -57,15 +57,36 @@ catFileHandle = do
|
||||||
{- From the Sha or Ref of a symlink back to the key. -}
|
{- From the Sha or Ref of a symlink back to the key. -}
|
||||||
catKey :: Ref -> Annex (Maybe Key)
|
catKey :: Ref -> Annex (Maybe Key)
|
||||||
catKey ref = do
|
catKey ref = do
|
||||||
l <- fromInternalGitPath . encodeW8 . L.unpack <$> catObject ref
|
l <- fromInternalGitPath . encodeW8 . L.unpack <$> catObject ref
|
||||||
return $ if isLinkToAnnex l
|
return $ if isLinkToAnnex l
|
||||||
then fileKey $ takeFileName l
|
then fileKey $ takeFileName l
|
||||||
else Nothing
|
else Nothing
|
||||||
|
|
||||||
{- From a file in git back to the key.
|
{- From a file in the repository back to the key.
|
||||||
-
|
-
|
||||||
- Prefixing the file with ./ makes this work even if in a subdirectory
|
- Prefixing the file with ./ makes this work even if in a subdirectory
|
||||||
- of a repo.
|
- of a repo.
|
||||||
|
-
|
||||||
|
- Ideally, this should reflect the key that's staged in the index,
|
||||||
|
- not the key that's committed to HEAD. Unfortunately, git cat-file
|
||||||
|
- does not refresh the index file after it's started up, so things
|
||||||
|
- newly staged in the index won't show up. It does, however, notice
|
||||||
|
- when branches change.
|
||||||
|
-
|
||||||
|
- For command-line git-annex use, that doesn't matter. It's perfectly
|
||||||
|
- reasonable for things staged in the index after the currently running
|
||||||
|
- git-annex process to not be noticed by it.
|
||||||
|
-
|
||||||
|
- For the assistant, this is much more of a problem, since it commits
|
||||||
|
- files and then needs to be able to immediately look up their keys.
|
||||||
|
- OTOH, the assistant doesn't keep changes staged in the index for very
|
||||||
|
- long at all before committing them -- and it won't look at the keys
|
||||||
|
- of files until after committing them.
|
||||||
|
-
|
||||||
|
- So, this gets info from the index, unless running as a daemon.
|
||||||
-}
|
-}
|
||||||
catKeyFile :: FilePath -> Annex (Maybe Key)
|
catKeyFile :: FilePath -> Annex (Maybe Key)
|
||||||
catKeyFile f = catKey $ Ref $ ":./" ++ f
|
catKeyFile f = ifM (Annex.getState Annex.daemon)
|
||||||
|
( catKey $ Ref $ "HEAD:./" ++ f
|
||||||
|
, catKey $ Ref $ ":./" ++ f
|
||||||
|
)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{- git-annex file content managing
|
{- git-annex file content managing
|
||||||
-
|
-
|
||||||
- Copyright 2010,2012 Joey Hess <joey@kitenet.net>
|
- Copyright 2010-2013 Joey Hess <joey@kitenet.net>
|
||||||
-
|
-
|
||||||
- Licensed under the GNU GPL version 3 or higher.
|
- Licensed under the GNU GPL version 3 or higher.
|
||||||
-}
|
-}
|
||||||
|
@ -10,6 +10,7 @@
|
||||||
module Annex.Content (
|
module Annex.Content (
|
||||||
inAnnex,
|
inAnnex,
|
||||||
inAnnexSafe,
|
inAnnexSafe,
|
||||||
|
inAnnexCheck,
|
||||||
lockContent,
|
lockContent,
|
||||||
getViaTmp,
|
getViaTmp,
|
||||||
getViaTmpChecked,
|
getViaTmpChecked,
|
||||||
|
@ -56,7 +57,11 @@ import Annex.ReplaceFile
|
||||||
|
|
||||||
{- Checks if a given key's content is currently present. -}
|
{- Checks if a given key's content is currently present. -}
|
||||||
inAnnex :: Key -> Annex Bool
|
inAnnex :: Key -> Annex Bool
|
||||||
inAnnex = inAnnex' id False $ liftIO . doesFileExist
|
inAnnex key = inAnnexCheck key $ liftIO . doesFileExist
|
||||||
|
|
||||||
|
{- Runs an arbitrary check on a key's content. -}
|
||||||
|
inAnnexCheck :: Key -> (FilePath -> Annex Bool) -> Annex Bool
|
||||||
|
inAnnexCheck key check = inAnnex' id False check key
|
||||||
|
|
||||||
{- Generic inAnnex, handling both indirect and direct mode.
|
{- Generic inAnnex, handling both indirect and direct mode.
|
||||||
-
|
-
|
||||||
|
@ -87,14 +92,14 @@ inAnnexSafe = inAnnex' (fromMaybe False) (Just False) go
|
||||||
where
|
where
|
||||||
go f = liftIO $ openforlock f >>= check
|
go f = liftIO $ openforlock f >>= check
|
||||||
openforlock f = catchMaybeIO $
|
openforlock f = catchMaybeIO $
|
||||||
#ifndef __WINDOWS__
|
#ifndef mingw32_HOST_OS
|
||||||
openFd f ReadOnly Nothing defaultFileFlags
|
openFd f ReadOnly Nothing defaultFileFlags
|
||||||
#else
|
#else
|
||||||
return ()
|
return ()
|
||||||
#endif
|
#endif
|
||||||
check Nothing = return is_missing
|
check Nothing = return is_missing
|
||||||
check (Just h) = do
|
check (Just h) = do
|
||||||
#ifndef __WINDOWS__
|
#ifndef mingw32_HOST_OS
|
||||||
v <- getLock h (ReadLock, AbsoluteSeek, 0, 0)
|
v <- getLock h (ReadLock, AbsoluteSeek, 0, 0)
|
||||||
closeFd h
|
closeFd h
|
||||||
return $ case v of
|
return $ case v of
|
||||||
|
@ -111,11 +116,11 @@ inAnnexSafe = inAnnex' (fromMaybe False) (Just False) go
|
||||||
- it. (If the content is not present, no locking is done.) -}
|
- it. (If the content is not present, no locking is done.) -}
|
||||||
lockContent :: Key -> Annex a -> Annex a
|
lockContent :: Key -> Annex a -> Annex a
|
||||||
lockContent key a = do
|
lockContent key a = do
|
||||||
#ifdef __WINDOWS__
|
#ifdef mingw32_HOST_OS
|
||||||
a
|
a
|
||||||
#else
|
#else
|
||||||
file <- calcRepo $ gitAnnexLocation key
|
file <- calcRepo $ gitAnnexLocation key
|
||||||
bracketIO (openforlock file >>= lock) unlock a
|
bracketIO (openforlock file >>= lock) unlock (const a)
|
||||||
where
|
where
|
||||||
{- Since files are stored with the write bit disabled, have
|
{- Since files are stored with the write bit disabled, have
|
||||||
- to fiddle with permissions to open for an exclusive lock. -}
|
- to fiddle with permissions to open for an exclusive lock. -}
|
||||||
|
@ -205,8 +210,7 @@ checkDiskSpace destination key alreadythere = do
|
||||||
case (free, keySize key) of
|
case (free, keySize key) of
|
||||||
(Just have, Just need) -> do
|
(Just have, Just need) -> do
|
||||||
let ok = (need + reserve <= have + alreadythere) || force
|
let ok = (need + reserve <= have + alreadythere) || force
|
||||||
unless ok $ do
|
unless ok $
|
||||||
liftIO $ print (need, reserve, have, alreadythere)
|
|
||||||
needmorespace (need + reserve - have - alreadythere)
|
needmorespace (need + reserve - have - alreadythere)
|
||||||
return ok
|
return ok
|
||||||
_ -> return True
|
_ -> return True
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
module Annex.Content.Direct (
|
module Annex.Content.Direct (
|
||||||
associatedFiles,
|
associatedFiles,
|
||||||
|
associatedFilesRelative,
|
||||||
removeAssociatedFile,
|
removeAssociatedFile,
|
||||||
removeAssociatedFileUnchecked,
|
removeAssociatedFileUnchecked,
|
||||||
addAssociatedFile,
|
addAssociatedFile,
|
||||||
|
@ -193,7 +194,7 @@ compareInodeCachesWith :: Annex InodeComparisonType
|
||||||
compareInodeCachesWith = ifM inodesChanged ( return Weakly, return Strongly )
|
compareInodeCachesWith = ifM inodesChanged ( return Weakly, return Strongly )
|
||||||
|
|
||||||
{- Copies the contentfile to the associated file, if the associated
|
{- Copies the contentfile to the associated file, if the associated
|
||||||
- file has not content. If the associated file does have content,
|
- file has no content. If the associated file does have content,
|
||||||
- even if the content differs, it's left unchanged. -}
|
- even if the content differs, it's left unchanged. -}
|
||||||
addContentWhenNotPresent :: Key -> FilePath -> FilePath -> Annex ()
|
addContentWhenNotPresent :: Key -> FilePath -> FilePath -> Annex ()
|
||||||
addContentWhenNotPresent key contentfile associatedfile = do
|
addContentWhenNotPresent key contentfile associatedfile = do
|
||||||
|
@ -232,6 +233,7 @@ readInodeSentinalFile = do
|
||||||
writeInodeSentinalFile :: Annex ()
|
writeInodeSentinalFile :: Annex ()
|
||||||
writeInodeSentinalFile = do
|
writeInodeSentinalFile = do
|
||||||
sentinalfile <- fromRepo gitAnnexInodeSentinal
|
sentinalfile <- fromRepo gitAnnexInodeSentinal
|
||||||
|
createAnnexDirectory (parentDir sentinalfile)
|
||||||
sentinalcachefile <- fromRepo gitAnnexInodeSentinalCache
|
sentinalcachefile <- fromRepo gitAnnexInodeSentinalCache
|
||||||
liftIO $ writeFile sentinalfile ""
|
liftIO $ writeFile sentinalfile ""
|
||||||
liftIO $ maybe noop (writeFile sentinalcachefile . showInodeCache)
|
liftIO $ maybe noop (writeFile sentinalcachefile . showInodeCache)
|
||||||
|
|
|
@ -27,6 +27,7 @@ import Utility.InodeCache
|
||||||
import Utility.CopyFile
|
import Utility.CopyFile
|
||||||
import Annex.Perms
|
import Annex.Perms
|
||||||
import Annex.ReplaceFile
|
import Annex.ReplaceFile
|
||||||
|
import Annex.Exception
|
||||||
|
|
||||||
{- Uses git ls-files to find files that need to be committed, and stages
|
{- Uses git ls-files to find files that need to be committed, and stages
|
||||||
- them into the index. Returns True if some changes were staged. -}
|
- them into the index. Returns True if some changes were staged. -}
|
||||||
|
@ -34,7 +35,7 @@ stageDirect :: Annex Bool
|
||||||
stageDirect = do
|
stageDirect = do
|
||||||
Annex.Queue.flush
|
Annex.Queue.flush
|
||||||
top <- fromRepo Git.repoPath
|
top <- fromRepo Git.repoPath
|
||||||
(l, cleanup) <- inRepo $ Git.LsFiles.stagedDetails [top]
|
(l, cleanup) <- inRepo $ Git.LsFiles.stagedOthersDetails [top]
|
||||||
forM_ l go
|
forM_ l go
|
||||||
void $ liftIO cleanup
|
void $ liftIO cleanup
|
||||||
staged <- Annex.Queue.size
|
staged <- Annex.Queue.size
|
||||||
|
@ -139,8 +140,10 @@ mergeDirectCleanup d oldsha newsha = do
|
||||||
liftIO $ removeDirectoryRecursive d
|
liftIO $ removeDirectoryRecursive d
|
||||||
where
|
where
|
||||||
updated item = do
|
updated item = do
|
||||||
go DiffTree.srcsha DiffTree.srcmode moveout moveout_raw
|
void $ tryAnnex $
|
||||||
go DiffTree.dstsha DiffTree.dstmode movein movein_raw
|
go DiffTree.srcsha DiffTree.srcmode moveout moveout_raw
|
||||||
|
void $ tryAnnex $
|
||||||
|
go DiffTree.dstsha DiffTree.dstmode movein movein_raw
|
||||||
where
|
where
|
||||||
go getsha getmode a araw
|
go getsha getmode a araw
|
||||||
| getsha item == nullSha = noop
|
| getsha item == nullSha = noop
|
||||||
|
@ -173,7 +176,8 @@ mergeDirectCleanup d oldsha newsha = do
|
||||||
void $ tryIO $ rename (d </> f) f
|
void $ tryIO $ rename (d </> f) f
|
||||||
|
|
||||||
{- If possible, converts a symlink in the working tree into a direct
|
{- If possible, converts a symlink in the working tree into a direct
|
||||||
- mode file. -}
|
- mode file. If the content is not available, leaves the symlink
|
||||||
|
- unchanged. -}
|
||||||
toDirect :: Key -> FilePath -> Annex ()
|
toDirect :: Key -> FilePath -> Annex ()
|
||||||
toDirect k f = fromMaybe noop =<< toDirectGen k f
|
toDirect k f = fromMaybe noop =<< toDirectGen k f
|
||||||
|
|
||||||
|
@ -181,28 +185,29 @@ toDirectGen :: Key -> FilePath -> Annex (Maybe (Annex ()))
|
||||||
toDirectGen k f = do
|
toDirectGen k f = do
|
||||||
loc <- calcRepo $ gitAnnexLocation k
|
loc <- calcRepo $ gitAnnexLocation k
|
||||||
ifM (liftIO $ doesFileExist loc)
|
ifM (liftIO $ doesFileExist loc)
|
||||||
( fromindirect loc
|
( return $ Just $ fromindirect loc
|
||||||
, fromdirect
|
, do
|
||||||
|
{- Copy content from another direct file. -}
|
||||||
|
absf <- liftIO $ absPath f
|
||||||
|
dlocs <- filterM (goodContent k) =<<
|
||||||
|
filterM (\l -> isNothing <$> getAnnexLinkTarget l) =<<
|
||||||
|
(filter (/= absf) <$> addAssociatedFile k f)
|
||||||
|
case dlocs of
|
||||||
|
[] -> return Nothing
|
||||||
|
(dloc:_) -> return $ Just $ fromdirect dloc
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
fromindirect loc = return $ Just $ do
|
fromindirect loc = do
|
||||||
{- Move content from annex to direct file. -}
|
{- Move content from annex to direct file. -}
|
||||||
thawContentDir loc
|
thawContentDir loc
|
||||||
updateInodeCache k loc
|
updateInodeCache k loc
|
||||||
void $ addAssociatedFile k f
|
void $ addAssociatedFile k f
|
||||||
thawContent loc
|
thawContent loc
|
||||||
replaceFile f $ liftIO . moveFile loc
|
replaceFile f $ liftIO . moveFile loc
|
||||||
fromdirect = do
|
fromdirect loc = do
|
||||||
{- Copy content from another direct file. -}
|
replaceFile f $
|
||||||
absf <- liftIO $ absPath f
|
liftIO . void . copyFileExternal loc
|
||||||
locs <- filterM (\loc -> isNothing <$> getAnnexLinkTarget loc) =<<
|
updateInodeCache k f
|
||||||
(filter (/= absf) <$> addAssociatedFile k f)
|
|
||||||
case locs of
|
|
||||||
(loc:_) -> return $ Just $ do
|
|
||||||
replaceFile f $
|
|
||||||
liftIO . void . copyFileExternal loc
|
|
||||||
updateInodeCache k f
|
|
||||||
_ -> return Nothing
|
|
||||||
|
|
||||||
{- Removes a direct mode file, while retaining its content in the annex
|
{- Removes a direct mode file, while retaining its content in the annex
|
||||||
- (unless its content has already been changed). -}
|
- (unless its content has already been changed). -}
|
||||||
|
|
|
@ -13,10 +13,19 @@ import Common.Annex
|
||||||
import Utility.Env
|
import Utility.Env
|
||||||
import Utility.UserInfo
|
import Utility.UserInfo
|
||||||
import qualified Git.Config
|
import qualified Git.Config
|
||||||
|
import Config
|
||||||
|
import Annex.Exception
|
||||||
|
|
||||||
{- Checks that the system's environment allows git to function.
|
{- Checks that the system's environment allows git to function.
|
||||||
- Git requires a GECOS username, or suitable git configuration, or
|
- Git requires a GECOS username, or suitable git configuration, or
|
||||||
- environment variables. -}
|
- environment variables.
|
||||||
|
-
|
||||||
|
- Git also requires the system have a hostname containing a dot.
|
||||||
|
- Otherwise, it tries various methods to find a FQDN, and will fail if it
|
||||||
|
- does not. To avoid replicating that code here, which would break if its
|
||||||
|
- methods change, this function does not check the hostname is valid.
|
||||||
|
- Instead, code that commits can use ensureCommit.
|
||||||
|
-}
|
||||||
checkEnvironment :: Annex ()
|
checkEnvironment :: Annex ()
|
||||||
checkEnvironment = do
|
checkEnvironment = do
|
||||||
gitusername <- fromRepo $ Git.Config.getMaybe "user.name"
|
gitusername <- fromRepo $ Git.Config.getMaybe "user.name"
|
||||||
|
@ -25,7 +34,7 @@ checkEnvironment = do
|
||||||
|
|
||||||
checkEnvironmentIO :: IO ()
|
checkEnvironmentIO :: IO ()
|
||||||
checkEnvironmentIO =
|
checkEnvironmentIO =
|
||||||
#ifdef __WINDOWS__
|
#ifdef mingw32_HOST_OS
|
||||||
noop
|
noop
|
||||||
#else
|
#else
|
||||||
whenM (null <$> myUserGecos) $ do
|
whenM (null <$> myUserGecos) $ do
|
||||||
|
@ -42,3 +51,12 @@ checkEnvironmentIO =
|
||||||
ensureEnv _ _ = noop
|
ensureEnv _ _ = noop
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
{- Runs an action that commits to the repository, and if it fails,
|
||||||
|
- sets user.email to a dummy value and tries the action again. -}
|
||||||
|
ensureCommit :: Annex a -> Annex a
|
||||||
|
ensureCommit a = either retry return =<< tryAnnex a
|
||||||
|
where
|
||||||
|
retry _ = do
|
||||||
|
setConfig (ConfigKey "user.email") =<< liftIO myUserName
|
||||||
|
a
|
||||||
|
|
|
@ -13,25 +13,27 @@
|
||||||
module Annex.Exception (
|
module Annex.Exception (
|
||||||
bracketIO,
|
bracketIO,
|
||||||
tryAnnex,
|
tryAnnex,
|
||||||
throw,
|
throwAnnex,
|
||||||
catchAnnex,
|
catchAnnex,
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Prelude hiding (catch)
|
import qualified "MonadCatchIO-transformers" Control.Monad.CatchIO as M
|
||||||
import "MonadCatchIO-transformers" Control.Monad.CatchIO (bracket, try, throw, catch)
|
import Control.Exception
|
||||||
import Control.Exception hiding (handle, try, throw, bracket, catch)
|
|
||||||
|
|
||||||
import Common.Annex
|
import Common.Annex
|
||||||
|
|
||||||
{- Runs an Annex action, with setup and cleanup both in the IO monad. -}
|
{- Runs an Annex action, with setup and cleanup both in the IO monad. -}
|
||||||
bracketIO :: IO c -> (c -> IO b) -> Annex a -> Annex a
|
bracketIO :: IO v -> (v -> IO b) -> (v -> Annex a) -> Annex a
|
||||||
bracketIO setup cleanup go =
|
bracketIO setup cleanup go = M.bracket (liftIO setup) (liftIO . cleanup) go
|
||||||
bracket (liftIO setup) (liftIO . cleanup) (const go)
|
|
||||||
|
|
||||||
{- try in the Annex monad -}
|
{- try in the Annex monad -}
|
||||||
tryAnnex :: Annex a -> Annex (Either SomeException a)
|
tryAnnex :: Annex a -> Annex (Either SomeException a)
|
||||||
tryAnnex = try
|
tryAnnex = M.try
|
||||||
|
|
||||||
|
{- throw in the Annex monad -}
|
||||||
|
throwAnnex :: Exception e => e -> Annex a
|
||||||
|
throwAnnex = M.throw
|
||||||
|
|
||||||
{- catch in the Annex monad -}
|
{- catch in the Annex monad -}
|
||||||
catchAnnex :: Exception e => Annex a -> (e -> Annex a) -> Annex a
|
catchAnnex :: Exception e => Annex a -> (e -> Annex a) -> Annex a
|
||||||
catchAnnex = catch
|
catchAnnex = M.catch
|
||||||
|
|
|
@ -17,6 +17,7 @@ import Logs.Group
|
||||||
import Logs.Remote
|
import Logs.Remote
|
||||||
import Annex.UUID
|
import Annex.UUID
|
||||||
import qualified Annex
|
import qualified Annex
|
||||||
|
import Types.FileMatcher
|
||||||
import Git.FilePath
|
import Git.FilePath
|
||||||
import Types.Remote (RemoteConfig)
|
import Types.Remote (RemoteConfig)
|
||||||
|
|
||||||
|
@ -33,9 +34,9 @@ checkFileMatcher' matcher file notpresent def
|
||||||
| isEmpty matcher = return def
|
| isEmpty matcher = return def
|
||||||
| otherwise = do
|
| otherwise = do
|
||||||
matchfile <- getTopFilePath <$> inRepo (toTopFilePath file)
|
matchfile <- getTopFilePath <$> inRepo (toTopFilePath file)
|
||||||
let fi = Annex.FileInfo
|
let fi = FileInfo
|
||||||
{ Annex.matchFile = matchfile
|
{ matchFile = matchfile
|
||||||
, Annex.relFile = file
|
, relFile = file
|
||||||
}
|
}
|
||||||
matchMrun matcher $ \a -> a notpresent fi
|
matchMrun matcher $ \a -> a notpresent fi
|
||||||
|
|
||||||
|
|
|
@ -84,10 +84,10 @@ lockJournal a = do
|
||||||
lockfile <- fromRepo gitAnnexJournalLock
|
lockfile <- fromRepo gitAnnexJournalLock
|
||||||
createAnnexDirectory $ takeDirectory lockfile
|
createAnnexDirectory $ takeDirectory lockfile
|
||||||
mode <- annexFileMode
|
mode <- annexFileMode
|
||||||
bracketIO (lock lockfile mode) unlock a
|
bracketIO (lock lockfile mode) unlock (const a)
|
||||||
where
|
where
|
||||||
lock lockfile mode = do
|
lock lockfile mode = do
|
||||||
#ifndef __WINDOWS__
|
#ifndef mingw32_HOST_OS
|
||||||
l <- noUmask mode $ createFile lockfile mode
|
l <- noUmask mode $ createFile lockfile mode
|
||||||
waitToSetLock l (WriteLock, AbsoluteSeek, 0, 0)
|
waitToSetLock l (WriteLock, AbsoluteSeek, 0, 0)
|
||||||
return l
|
return l
|
||||||
|
@ -95,7 +95,7 @@ lockJournal a = do
|
||||||
writeFile lockfile ""
|
writeFile lockfile ""
|
||||||
return lockfile
|
return lockfile
|
||||||
#endif
|
#endif
|
||||||
#ifndef __WINDOWS__
|
#ifndef mingw32_HOST_OS
|
||||||
unlock = closeFd
|
unlock = closeFd
|
||||||
#else
|
#else
|
||||||
unlock = removeFile
|
unlock = removeFile
|
||||||
|
|
|
@ -29,17 +29,19 @@ isAnnexLink file = maybe Nothing (fileKey . takeFileName) <$> getAnnexLinkTarget
|
||||||
{- Gets the link target of a symlink.
|
{- Gets the link target of a symlink.
|
||||||
-
|
-
|
||||||
- On a filesystem that does not support symlinks, fall back to getting the
|
- On a filesystem that does not support symlinks, fall back to getting the
|
||||||
- link target by looking inside the file. (Only return at first 8k of the
|
- link target by looking inside the file.
|
||||||
- file, more than enough for any symlink target.)
|
|
||||||
-
|
-
|
||||||
- Returns Nothing if the file is not a symlink, or not a link to annex
|
- Returns Nothing if the file is not a symlink, or not a link to annex
|
||||||
- content.
|
- content.
|
||||||
-}
|
-}
|
||||||
getAnnexLinkTarget :: FilePath -> Annex (Maybe LinkTarget)
|
getAnnexLinkTarget :: FilePath -> Annex (Maybe LinkTarget)
|
||||||
getAnnexLinkTarget file =
|
getAnnexLinkTarget file = ifM (coreSymlinks <$> Annex.getGitConfig)
|
||||||
check readSymbolicLink $
|
( check readSymbolicLink $
|
||||||
check readfilestart $
|
return Nothing
|
||||||
|
, check readSymbolicLink $
|
||||||
|
check probefilecontent $
|
||||||
return Nothing
|
return Nothing
|
||||||
|
)
|
||||||
where
|
where
|
||||||
check getlinktarget fallback = do
|
check getlinktarget fallback = do
|
||||||
v <- liftIO $ catchMaybeIO $ getlinktarget file
|
v <- liftIO $ catchMaybeIO $ getlinktarget file
|
||||||
|
@ -49,10 +51,26 @@ getAnnexLinkTarget file =
|
||||||
| otherwise -> return Nothing
|
| otherwise -> return Nothing
|
||||||
Nothing -> fallback
|
Nothing -> fallback
|
||||||
|
|
||||||
readfilestart f = do
|
probefilecontent f = do
|
||||||
h <- openFile f ReadMode
|
h <- openFile f ReadMode
|
||||||
fileEncoding h
|
fileEncoding h
|
||||||
take 8192 <$> hGetContents h
|
-- The first 8k is more than enough to read; link
|
||||||
|
-- files are small.
|
||||||
|
s <- take 8192 <$> hGetContents h
|
||||||
|
-- If we got the full 8k, the file is too large
|
||||||
|
if length s == 8192
|
||||||
|
then do
|
||||||
|
hClose h
|
||||||
|
return ""
|
||||||
|
else do
|
||||||
|
hClose h
|
||||||
|
-- If there are any NUL or newline
|
||||||
|
-- characters, or whitespace, we
|
||||||
|
-- certianly don't have a link to a
|
||||||
|
-- git-annex key.
|
||||||
|
if any (`elem` s) "\0\n\r \t"
|
||||||
|
then return ""
|
||||||
|
else return s
|
||||||
|
|
||||||
{- Creates a link on disk.
|
{- Creates a link on disk.
|
||||||
-
|
-
|
||||||
|
|
|
@ -22,7 +22,7 @@ lockFile file = go =<< fromPool file
|
||||||
where
|
where
|
||||||
go (Just _) = noop -- already locked
|
go (Just _) = noop -- already locked
|
||||||
go Nothing = do
|
go Nothing = do
|
||||||
#ifndef __WINDOWS__
|
#ifndef mingw32_HOST_OS
|
||||||
mode <- annexFileMode
|
mode <- annexFileMode
|
||||||
fd <- liftIO $ noUmask mode $
|
fd <- liftIO $ noUmask mode $
|
||||||
openFd file ReadOnly (Just mode) defaultFileFlags
|
openFd file ReadOnly (Just mode) defaultFileFlags
|
||||||
|
@ -37,7 +37,7 @@ unlockFile :: FilePath -> Annex ()
|
||||||
unlockFile file = maybe noop go =<< fromPool file
|
unlockFile file = maybe noop go =<< fromPool file
|
||||||
where
|
where
|
||||||
go fd = do
|
go fd = do
|
||||||
#ifndef __WINDOWS__
|
#ifndef mingw32_HOST_OS
|
||||||
liftIO $ closeFd fd
|
liftIO $ closeFd fd
|
||||||
#endif
|
#endif
|
||||||
changePool $ M.delete file
|
changePool $ M.delete file
|
||||||
|
|
|
@ -9,27 +9,31 @@ module Annex.ReplaceFile where
|
||||||
|
|
||||||
import Common.Annex
|
import Common.Annex
|
||||||
import Annex.Perms
|
import Annex.Perms
|
||||||
|
import Annex.Exception
|
||||||
|
|
||||||
{- Replaces a possibly already existing file with a new version,
|
{- Replaces a possibly already existing file with a new version,
|
||||||
- atomically, by running an action.
|
- atomically, by running an action.
|
||||||
-
|
-
|
||||||
- The action is passed a temp file, which it can write to, and once
|
- The action is passed a temp file, which it can write to, and once
|
||||||
- done the temp file is moved into place.
|
- done the temp file is moved into place.
|
||||||
|
-
|
||||||
|
- The action can throw an IO exception, in which case the temp file
|
||||||
|
- will be deleted, and the existing file will be preserved.
|
||||||
|
-
|
||||||
|
- Throws an IO exception when it was unable to replace the file.
|
||||||
-}
|
-}
|
||||||
replaceFile :: FilePath -> (FilePath -> Annex ()) -> Annex ()
|
replaceFile :: FilePath -> (FilePath -> Annex ()) -> Annex ()
|
||||||
replaceFile file a = do
|
replaceFile file a = do
|
||||||
tmpdir <- fromRepo gitAnnexTmpDir
|
tmpdir <- fromRepo gitAnnexTmpDir
|
||||||
createAnnexDirectory tmpdir
|
void $ createAnnexDirectory tmpdir
|
||||||
tmpfile <- liftIO $ do
|
bracketIO (setup tmpdir) nukeFile $ \tmpfile -> do
|
||||||
(tmpfile, h) <- openTempFileWithDefaultPermissions tmpdir $
|
a tmpfile
|
||||||
takeFileName file
|
liftIO $ catchIO (rename tmpfile file) (fallback tmpfile)
|
||||||
|
where
|
||||||
|
setup tmpdir = do
|
||||||
|
(tmpfile, h) <- openTempFileWithDefaultPermissions tmpdir "tmp"
|
||||||
hClose h
|
hClose h
|
||||||
return tmpfile
|
return tmpfile
|
||||||
a tmpfile
|
fallback tmpfile _ = do
|
||||||
liftIO $ do
|
createDirectoryIfMissing True $ parentDir file
|
||||||
r <- tryIO $ rename tmpfile file
|
rename tmpfile file
|
||||||
case r of
|
|
||||||
Left _ -> do
|
|
||||||
createDirectoryIfMissing True $ parentDir file
|
|
||||||
rename tmpfile file
|
|
||||||
_ -> noop
|
|
||||||
|
|
42
Annex/Ssh.hs
42
Annex/Ssh.hs
|
@ -15,6 +15,7 @@ module Annex.Ssh (
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
|
import Data.Hash.MD5
|
||||||
|
|
||||||
import Common.Annex
|
import Common.Annex
|
||||||
import Annex.LockPool
|
import Annex.LockPool
|
||||||
|
@ -51,17 +52,18 @@ sshInfo (host, port) = go =<< sshCacheDir
|
||||||
go (Just dir) = do
|
go (Just dir) = do
|
||||||
let socketfile = dir </> hostport2socket host port
|
let socketfile = dir </> hostport2socket host port
|
||||||
if valid_unix_socket_path socketfile
|
if valid_unix_socket_path socketfile
|
||||||
then return (Just socketfile, cacheparams socketfile)
|
then return (Just socketfile, sshConnectionCachingParams socketfile)
|
||||||
else do
|
else do
|
||||||
socketfile' <- liftIO $ relPathCwdToFile socketfile
|
socketfile' <- liftIO $ relPathCwdToFile socketfile
|
||||||
if valid_unix_socket_path socketfile'
|
if valid_unix_socket_path socketfile'
|
||||||
then return (Just socketfile', cacheparams socketfile')
|
then return (Just socketfile', sshConnectionCachingParams socketfile')
|
||||||
else return (Nothing, [])
|
else return (Nothing, [])
|
||||||
cacheparams :: FilePath -> [CommandParam]
|
|
||||||
cacheparams socketfile =
|
sshConnectionCachingParams :: FilePath -> [CommandParam]
|
||||||
[ Param "-S", Param socketfile
|
sshConnectionCachingParams socketfile =
|
||||||
, Params "-o ControlMaster=auto -o ControlPersist=yes"
|
[ Param "-S", Param socketfile
|
||||||
]
|
, Params "-o ControlMaster=auto -o ControlPersist=yes"
|
||||||
|
]
|
||||||
|
|
||||||
{- ssh connection caching creates sockets, so will not work on a
|
{- ssh connection caching creates sockets, so will not work on a
|
||||||
- crippled filesystem. A GIT_ANNEX_TMP_DIR can be provided to use
|
- crippled filesystem. A GIT_ANNEX_TMP_DIR can be provided to use
|
||||||
|
@ -96,7 +98,7 @@ sshCleanup = go =<< sshCacheDir
|
||||||
liftIO (catchDefaultIO [] $ dirContents dir)
|
liftIO (catchDefaultIO [] $ dirContents dir)
|
||||||
forM_ sockets cleanup
|
forM_ sockets cleanup
|
||||||
cleanup socketfile = do
|
cleanup socketfile = do
|
||||||
#ifndef __WINDOWS__
|
#ifndef mingw32_HOST_OS
|
||||||
-- Drop any shared lock we have, and take an
|
-- Drop any shared lock we have, and take an
|
||||||
-- exclusive lock, without blocking. If the lock
|
-- exclusive lock, without blocking. If the lock
|
||||||
-- succeeds, nothing is using this ssh, and it can
|
-- succeeds, nothing is using this ssh, and it can
|
||||||
|
@ -116,27 +118,27 @@ sshCleanup = go =<< sshCacheDir
|
||||||
stopssh socketfile
|
stopssh socketfile
|
||||||
#endif
|
#endif
|
||||||
stopssh socketfile = do
|
stopssh socketfile = do
|
||||||
let (host, port) = socket2hostport socketfile
|
let params = sshConnectionCachingParams socketfile
|
||||||
(_, params) <- sshInfo (host, port)
|
|
||||||
-- "ssh -O stop" is noisy on stderr even with -q
|
-- "ssh -O stop" is noisy on stderr even with -q
|
||||||
void $ liftIO $ catchMaybeIO $
|
void $ liftIO $ catchMaybeIO $
|
||||||
withQuietOutput createProcessSuccess $
|
withQuietOutput createProcessSuccess $
|
||||||
proc "ssh" $ toCommand $
|
proc "ssh" $ toCommand $
|
||||||
[ Params "-O stop"
|
[ Params "-O stop"
|
||||||
] ++ params ++ [Param host]
|
] ++ params ++ [Param "any"]
|
||||||
-- Cannot remove the lock file; other processes may
|
-- Cannot remove the lock file; other processes may
|
||||||
-- be waiting on our exclusive lock to use it.
|
-- be waiting on our exclusive lock to use it.
|
||||||
|
|
||||||
|
{- This needs to be as short as possible, due to limitations on the length
|
||||||
|
- of the path to a socket file. At the same time, it needs to be unique
|
||||||
|
- for each host.
|
||||||
|
-}
|
||||||
hostport2socket :: String -> Maybe Integer -> FilePath
|
hostport2socket :: String -> Maybe Integer -> FilePath
|
||||||
hostport2socket host Nothing = host
|
hostport2socket host Nothing = hostport2socket' host
|
||||||
hostport2socket host (Just port) = host ++ "!" ++ show port
|
hostport2socket host (Just port) = hostport2socket' $ host ++ "!" ++ show port
|
||||||
|
hostport2socket' :: String -> FilePath
|
||||||
socket2hostport :: FilePath -> (String, Maybe Integer)
|
hostport2socket' s
|
||||||
socket2hostport socket
|
| length s > 32 = md5s (Str s)
|
||||||
| null p = (h, Nothing)
|
| otherwise = s
|
||||||
| otherwise = (h, readish p)
|
|
||||||
where
|
|
||||||
(h, p) = separate (== '!') $ takeFileName socket
|
|
||||||
|
|
||||||
socket2lock :: FilePath -> FilePath
|
socket2lock :: FilePath -> FilePath
|
||||||
socket2lock socket = socket ++ lockExt
|
socket2lock socket = socket ++ lockExt
|
||||||
|
|
|
@ -25,7 +25,7 @@ supportedVersions :: [Version]
|
||||||
supportedVersions = [defaultVersion, directModeVersion]
|
supportedVersions = [defaultVersion, directModeVersion]
|
||||||
|
|
||||||
upgradableVersions :: [Version]
|
upgradableVersions :: [Version]
|
||||||
#ifndef __WINDOWS__
|
#ifndef mingw32_HOST_OS
|
||||||
upgradableVersions = ["0", "1", "2"]
|
upgradableVersions = ["0", "1", "2"]
|
||||||
#else
|
#else
|
||||||
upgradableVersions = ["2"]
|
upgradableVersions = ["2"]
|
||||||
|
|
|
@ -10,7 +10,6 @@ module Annex.Wanted where
|
||||||
import Common.Annex
|
import Common.Annex
|
||||||
import Logs.PreferredContent
|
import Logs.PreferredContent
|
||||||
import Annex.UUID
|
import Annex.UUID
|
||||||
import Types.Remote
|
|
||||||
|
|
||||||
import qualified Data.Set as S
|
import qualified Data.Set as S
|
||||||
|
|
||||||
|
|
119
Assistant.hs
119
Assistant.hs
|
@ -1,126 +1,15 @@
|
||||||
{- git-annex assistant daemon
|
{- git-annex assistant daemon
|
||||||
-
|
-
|
||||||
- Copyright 2012 Joey Hess <joey@kitenet.net>
|
- Copyright 2012-2013 Joey Hess <joey@kitenet.net>
|
||||||
-
|
-
|
||||||
- Licensed under the GNU GPL version 3 or higher.
|
- Licensed under the GNU GPL version 3 or higher.
|
||||||
-
|
|
||||||
- Overview of threads and MVars, etc:
|
|
||||||
-
|
|
||||||
- Thread 1: parent
|
|
||||||
- The initial thread run, double forks to background, starts other
|
|
||||||
- threads, and then stops, waiting for them to terminate,
|
|
||||||
- or for a ctrl-c.
|
|
||||||
- Thread 2: Watcher
|
|
||||||
- Notices new files, and calls handlers for events, queuing changes.
|
|
||||||
- Thread 3: inotify internal
|
|
||||||
- Used by haskell inotify library to ensure inotify event buffer is
|
|
||||||
- kept drained.
|
|
||||||
- Thread 4: inotify startup scanner
|
|
||||||
- Scans the tree and registers inotify watches for each directory.
|
|
||||||
- A MVar lock is used to prevent other inotify handlers from running
|
|
||||||
- until this is complete.
|
|
||||||
- Thread 5: Committer
|
|
||||||
- Waits for changes to occur, and runs the git queue to update its
|
|
||||||
- index, then commits. Also queues Transfer events to send added
|
|
||||||
- files to other remotes.
|
|
||||||
- Thread 6: Pusher
|
|
||||||
- Waits for commits to be made, and pushes updated branches to remotes,
|
|
||||||
- in parallel. (Forks a process for each git push.)
|
|
||||||
- Thread 7: PushRetryer
|
|
||||||
- Runs every 30 minutes when there are failed pushes, and retries
|
|
||||||
- them.
|
|
||||||
- Thread 8: Merger
|
|
||||||
- Waits for pushes to be received from remotes, and merges the
|
|
||||||
- updated branches into the current branch.
|
|
||||||
- (This uses inotify on .git/refs/heads, so there are additional
|
|
||||||
- inotify threads associated with it, too.)
|
|
||||||
- Thread 9: TransferWatcher
|
|
||||||
- Watches for transfer information files being created and removed,
|
|
||||||
- and maintains the DaemonStatus currentTransfers map.
|
|
||||||
- (This uses inotify on .git/annex/transfer/, so there are
|
|
||||||
- additional inotify threads associated with it, too.)
|
|
||||||
- Thread 10: TransferPoller
|
|
||||||
- Polls to determine how much of each ongoing transfer is complete.
|
|
||||||
- Thread 11: Transferrer
|
|
||||||
- Waits for Transfers to be queued and does them.
|
|
||||||
- Thread 12: StatusLogger
|
|
||||||
- Wakes up periodically and records the daemon's status to disk.
|
|
||||||
- Thread 13: SanityChecker
|
|
||||||
- Wakes up periodically (rarely) and does sanity checks.
|
|
||||||
- Thread 14: MountWatcher
|
|
||||||
- Either uses dbus to watch for drive mount events, or, when
|
|
||||||
- there's no dbus, polls to find newly mounted filesystems.
|
|
||||||
- Once a filesystem that contains a remote is mounted, updates
|
|
||||||
- state about that remote, pulls from it, and queues a push to it,
|
|
||||||
- as well as an update, and queues it onto the
|
|
||||||
- ConnectedRemoteChan
|
|
||||||
- Thread 15: NetWatcher
|
|
||||||
- Deals with network connection interruptions, which would cause
|
|
||||||
- transfers to fail, and can be recovered from by waiting for a
|
|
||||||
- network connection, and syncing with all network remotes.
|
|
||||||
- Uses dbus to watch for network connections, or when dbus
|
|
||||||
- cannot be used, assumes there's been one every 30 minutes.
|
|
||||||
- Thread 16: TransferScanner
|
|
||||||
- Does potentially expensive checks to find data that needs to be
|
|
||||||
- transferred from or to remotes, and queues Transfers.
|
|
||||||
- Uses the ScanRemotes map.a
|
|
||||||
- Thread 17: PairListener
|
|
||||||
- Listens for incoming pairing traffic, and takes action.
|
|
||||||
- Thread 18: ConfigMonitor
|
|
||||||
- Triggered by changes to the git-annex branch, checks for changed
|
|
||||||
- config files, and reloads configs.
|
|
||||||
- Thread 19: XMPPClient
|
|
||||||
- Built-in XMPP client.
|
|
||||||
- Thread 20: WebApp
|
|
||||||
- Spawns more threads as necessary to handle clients.
|
|
||||||
- Displays the DaemonStatus.
|
|
||||||
- Thread 21: Glacier
|
|
||||||
- Deals with retrieving files from Amazon Glacier.
|
|
||||||
-
|
|
||||||
- ThreadState: (MVar)
|
|
||||||
- The Annex state is stored here, which allows resuscitating the
|
|
||||||
- Annex monad in IO actions run by the watcher and committer
|
|
||||||
- threads. Thus, a single state is shared amoung the threads, and
|
|
||||||
- only one at a time can access it.
|
|
||||||
- DaemonStatusHandle: (STM TMVar)
|
|
||||||
- The daemon's current status.
|
|
||||||
- ChangeChan: (STM TChan)
|
|
||||||
- Changes are indicated by writing to this channel. The committer
|
|
||||||
- reads from it.
|
|
||||||
- CommitChan: (STM TChan)
|
|
||||||
- Commits are indicated by writing to this channel. The pusher reads
|
|
||||||
- from it.
|
|
||||||
- FailedPushMap (STM TMVar)
|
|
||||||
- Failed pushes are indicated by writing to this TMVar. The push
|
|
||||||
- retrier blocks until they're available.
|
|
||||||
- TransferQueue (STM TChan)
|
|
||||||
- Transfers to make are indicated by writing to this channel.
|
|
||||||
- TransferSlots (QSemN)
|
|
||||||
- Count of the number of currently available transfer slots.
|
|
||||||
- Updated by the transfer watcher, this allows other threads
|
|
||||||
- to block until a slot is available.
|
|
||||||
- This MVar should only be manipulated from inside the Annex monad,
|
|
||||||
- which ensures it's accessed only after the ThreadState MVar.
|
|
||||||
- ScanRemotes (STM TMVar)
|
|
||||||
- Remotes that have been disconnected, and should be scanned
|
|
||||||
- are indicated by writing to this TMVar.
|
|
||||||
- BranchChanged (STM SampleVar)
|
|
||||||
- Changes to the git-annex branch are indicated by updating this
|
|
||||||
- SampleVar.
|
|
||||||
- NetMessager (STM TChan, TMVar, SampleVar)
|
|
||||||
- Used to feed messages to the built-in XMPP client, handle
|
|
||||||
- pushes, and signal it when it needs to restart due to configuration
|
|
||||||
- or networking changes.
|
|
||||||
- UrlRenderer (MVar)
|
|
||||||
- A Yesod route rendering function is stored here. This allows
|
|
||||||
- things that need to render Yesod routes to block until the webapp
|
|
||||||
- has started up and such rendering is possible.
|
|
||||||
-}
|
-}
|
||||||
|
|
||||||
{-# LANGUAGE CPP #-}
|
{-# LANGUAGE CPP #-}
|
||||||
|
|
||||||
module Assistant where
|
module Assistant where
|
||||||
|
|
||||||
|
import qualified Annex
|
||||||
import Assistant.Common
|
import Assistant.Common
|
||||||
import Assistant.DaemonStatus
|
import Assistant.DaemonStatus
|
||||||
import Assistant.NamedThread
|
import Assistant.NamedThread
|
||||||
|
@ -149,6 +38,7 @@ import Assistant.Threads.PairListener
|
||||||
#endif
|
#endif
|
||||||
#ifdef WITH_XMPP
|
#ifdef WITH_XMPP
|
||||||
import Assistant.Threads.XMPPClient
|
import Assistant.Threads.XMPPClient
|
||||||
|
import Assistant.Threads.XMPPPusher
|
||||||
#endif
|
#endif
|
||||||
#else
|
#else
|
||||||
#warning Building without the webapp. You probably need to install Yesod..
|
#warning Building without the webapp. You probably need to install Yesod..
|
||||||
|
@ -172,6 +62,7 @@ stopDaemon = liftIO . Utility.Daemon.stopDaemon =<< fromRepo gitAnnexPidFile
|
||||||
- stdout and stderr descriptors. -}
|
- stdout and stderr descriptors. -}
|
||||||
startDaemon :: Bool -> Bool -> Maybe HostName -> Maybe (Maybe Handle -> Maybe Handle -> String -> FilePath -> IO ()) -> Annex ()
|
startDaemon :: Bool -> Bool -> Maybe HostName -> Maybe (Maybe Handle -> Maybe Handle -> String -> FilePath -> IO ()) -> Annex ()
|
||||||
startDaemon assistant foreground listenhost startbrowser = do
|
startDaemon assistant foreground listenhost startbrowser = do
|
||||||
|
Annex.changeState $ \s -> s { Annex.daemon = True }
|
||||||
pidfile <- fromRepo gitAnnexPidFile
|
pidfile <- fromRepo gitAnnexPidFile
|
||||||
logfile <- fromRepo gitAnnexLogFile
|
logfile <- fromRepo gitAnnexLogFile
|
||||||
logfd <- liftIO $ openLog logfile
|
logfd <- liftIO $ openLog logfile
|
||||||
|
@ -223,6 +114,8 @@ startDaemon assistant foreground listenhost startbrowser = do
|
||||||
#endif
|
#endif
|
||||||
#ifdef WITH_XMPP
|
#ifdef WITH_XMPP
|
||||||
, assist $ xmppClientThread urlrenderer
|
, assist $ xmppClientThread urlrenderer
|
||||||
|
, assist $ xmppSendPackThread urlrenderer
|
||||||
|
, assist $ xmppReceivePackThread urlrenderer
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
, assist $ pushThread
|
, assist $ pushThread
|
||||||
|
|
|
@ -41,12 +41,16 @@ mkAlertButton label urlrenderer route = do
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
renderData :: Alert -> TenseText
|
||||||
|
renderData = tenseWords . alertData
|
||||||
|
|
||||||
baseActivityAlert :: Alert
|
baseActivityAlert :: Alert
|
||||||
baseActivityAlert = Alert
|
baseActivityAlert = Alert
|
||||||
{ alertClass = Activity
|
{ alertClass = Activity
|
||||||
, alertHeader = Nothing
|
, alertHeader = Nothing
|
||||||
, alertMessageRender = tenseWords
|
, alertMessageRender = renderData
|
||||||
, alertData = []
|
, alertData = []
|
||||||
|
, alertCounter = 0
|
||||||
, alertBlockDisplay = False
|
, alertBlockDisplay = False
|
||||||
, alertClosable = False
|
, alertClosable = False
|
||||||
, alertPriority = Medium
|
, alertPriority = Medium
|
||||||
|
@ -60,8 +64,9 @@ warningAlert :: String -> String -> Alert
|
||||||
warningAlert name msg = Alert
|
warningAlert name msg = Alert
|
||||||
{ alertClass = Warning
|
{ alertClass = Warning
|
||||||
, alertHeader = Just $ tenseWords ["warning"]
|
, alertHeader = Just $ tenseWords ["warning"]
|
||||||
, alertMessageRender = tenseWords
|
, alertMessageRender = renderData
|
||||||
, alertData = [UnTensed $ T.pack msg]
|
, alertData = [UnTensed $ T.pack msg]
|
||||||
|
, alertCounter = 0
|
||||||
, alertBlockDisplay = True
|
, alertBlockDisplay = True
|
||||||
, alertClosable = True
|
, alertClosable = True
|
||||||
, alertPriority = High
|
, alertPriority = High
|
||||||
|
@ -128,6 +133,7 @@ sanityCheckFixAlert msg = Alert
|
||||||
, alertHeader = Just $ tenseWords ["Fixed a problem"]
|
, alertHeader = Just $ tenseWords ["Fixed a problem"]
|
||||||
, alertMessageRender = render
|
, alertMessageRender = render
|
||||||
, alertData = [UnTensed $ T.pack msg]
|
, alertData = [UnTensed $ T.pack msg]
|
||||||
|
, alertCounter = 0
|
||||||
, alertBlockDisplay = True
|
, alertBlockDisplay = True
|
||||||
, alertPriority = High
|
, alertPriority = High
|
||||||
, alertClosable = True
|
, alertClosable = True
|
||||||
|
@ -137,7 +143,7 @@ sanityCheckFixAlert msg = Alert
|
||||||
, alertButton = Nothing
|
, alertButton = Nothing
|
||||||
}
|
}
|
||||||
where
|
where
|
||||||
render dta = tenseWords $ alerthead : dta ++ [alertfoot]
|
render alert = tenseWords $ alerthead : alertData alert ++ [alertfoot]
|
||||||
alerthead = "The daily sanity check found and fixed a problem:"
|
alerthead = "The daily sanity check found and fixed a problem:"
|
||||||
alertfoot = "If these problems persist, consider filing a bug report."
|
alertfoot = "If these problems persist, consider filing a bug report."
|
||||||
|
|
||||||
|
@ -152,8 +158,9 @@ pairRequestReceivedAlert :: String -> AlertButton -> Alert
|
||||||
pairRequestReceivedAlert who button = Alert
|
pairRequestReceivedAlert who button = Alert
|
||||||
{ alertClass = Message
|
{ alertClass = Message
|
||||||
, alertHeader = Nothing
|
, alertHeader = Nothing
|
||||||
, alertMessageRender = tenseWords
|
, alertMessageRender = renderData
|
||||||
, alertData = [UnTensed $ T.pack $ who ++ " is sending a pair request."]
|
, alertData = [UnTensed $ T.pack $ who ++ " is sending a pair request."]
|
||||||
|
, alertCounter = 0
|
||||||
, alertBlockDisplay = False
|
, alertBlockDisplay = False
|
||||||
, alertPriority = High
|
, alertPriority = High
|
||||||
, alertClosable = True
|
, alertClosable = True
|
||||||
|
@ -180,7 +187,8 @@ xmppNeededAlert button = Alert
|
||||||
, alertButton = Just button
|
, alertButton = Just button
|
||||||
, alertClosable = True
|
, alertClosable = True
|
||||||
, alertClass = Message
|
, alertClass = Message
|
||||||
, alertMessageRender = tenseWords
|
, alertMessageRender = renderData
|
||||||
|
, alertCounter = 0
|
||||||
, alertBlockDisplay = True
|
, alertBlockDisplay = True
|
||||||
, alertName = Just $ XMPPNeededAlert
|
, alertName = Just $ XMPPNeededAlert
|
||||||
, alertCombiner = Just $ dataCombiner $ \_old new -> new
|
, alertCombiner = Just $ dataCombiner $ \_old new -> new
|
||||||
|
@ -198,7 +206,8 @@ cloudRepoNeededAlert friendname button = Alert
|
||||||
, alertButton = Just button
|
, alertButton = Just button
|
||||||
, alertClosable = True
|
, alertClosable = True
|
||||||
, alertClass = Message
|
, alertClass = Message
|
||||||
, alertMessageRender = tenseWords
|
, alertMessageRender = renderData
|
||||||
|
, alertCounter = 0
|
||||||
, alertBlockDisplay = True
|
, alertBlockDisplay = True
|
||||||
, alertName = Just $ CloudRepoNeededAlert
|
, alertName = Just $ CloudRepoNeededAlert
|
||||||
, alertCombiner = Just $ dataCombiner $ \_old new -> new
|
, alertCombiner = Just $ dataCombiner $ \_old new -> new
|
||||||
|
@ -215,41 +224,80 @@ remoteRemovalAlert desc button = Alert
|
||||||
, alertButton = Just button
|
, alertButton = Just button
|
||||||
, alertClosable = True
|
, alertClosable = True
|
||||||
, alertClass = Message
|
, alertClass = Message
|
||||||
, alertMessageRender = tenseWords
|
, alertMessageRender = renderData
|
||||||
|
, alertCounter = 0
|
||||||
, alertBlockDisplay = True
|
, alertBlockDisplay = True
|
||||||
, alertName = Just $ RemoteRemovalAlert desc
|
, alertName = Just $ RemoteRemovalAlert desc
|
||||||
, alertCombiner = Just $ dataCombiner $ \_old new -> new
|
, alertCombiner = Just $ dataCombiner $ \_old new -> new
|
||||||
, alertData = []
|
, alertData = []
|
||||||
}
|
}
|
||||||
|
|
||||||
fileAlert :: TenseChunk -> FilePath -> Alert
|
{- Show a message that relates to a list of files.
|
||||||
fileAlert msg file = (activityAlert Nothing [f])
|
-
|
||||||
|
- The most recent several files are shown, and a count of any others. -}
|
||||||
|
fileAlert :: TenseChunk -> [FilePath] -> Alert
|
||||||
|
fileAlert msg files = (activityAlert Nothing shortfiles)
|
||||||
{ alertName = Just $ FileAlert msg
|
{ alertName = Just $ FileAlert msg
|
||||||
, alertMessageRender = render
|
, alertMessageRender = renderer
|
||||||
, alertCombiner = Just $ dataCombiner combiner
|
, alertCounter = counter
|
||||||
|
, alertCombiner = Just $ fullCombiner combiner
|
||||||
}
|
}
|
||||||
where
|
where
|
||||||
f = fromString $ shortFile $ takeFileName file
|
maxfilesshown = 10
|
||||||
render fs = tenseWords $ msg : fs
|
|
||||||
combiner new old = take 10 $ new ++ old
|
|
||||||
|
|
||||||
addFileAlert :: String -> Alert
|
(somefiles, counter) = splitcounter (dedupadjacent files)
|
||||||
|
shortfiles = map (fromString . shortFile . takeFileName) somefiles
|
||||||
|
|
||||||
|
renderer alert = tenseWords $ msg : alertData alert ++ showcounter
|
||||||
|
where
|
||||||
|
showcounter = case alertCounter alert of
|
||||||
|
0 -> []
|
||||||
|
_ -> [fromString $ "and " ++ show (alertCounter alert) ++ " other files"]
|
||||||
|
|
||||||
|
dedupadjacent (x:y:rest)
|
||||||
|
| x == y = dedupadjacent (y:rest)
|
||||||
|
| otherwise = x : dedupadjacent (y:rest)
|
||||||
|
dedupadjacent (x:[]) = [x]
|
||||||
|
dedupadjacent [] = []
|
||||||
|
|
||||||
|
{- Note that this ensures the counter is never 1; no need to say
|
||||||
|
- "1 file" when the filename could be shown. -}
|
||||||
|
splitcounter l
|
||||||
|
| length l <= maxfilesshown = (l, 0)
|
||||||
|
| otherwise =
|
||||||
|
let (keep, rest) = splitAt (maxfilesshown - 1) l
|
||||||
|
in (keep, length rest)
|
||||||
|
|
||||||
|
combiner new old =
|
||||||
|
let (fs, n) = splitcounter $
|
||||||
|
dedupadjacent $ alertData new ++ alertData old
|
||||||
|
cnt = n + alertCounter new + alertCounter old
|
||||||
|
in old
|
||||||
|
{ alertData = fs
|
||||||
|
, alertCounter = cnt
|
||||||
|
}
|
||||||
|
|
||||||
|
addFileAlert :: [FilePath] -> Alert
|
||||||
addFileAlert = fileAlert (Tensed "Adding" "Added")
|
addFileAlert = fileAlert (Tensed "Adding" "Added")
|
||||||
|
|
||||||
{- This is only used as a success alert after a transfer, not during it. -}
|
{- This is only used as a success alert after a transfer, not during it. -}
|
||||||
transferFileAlert :: Direction -> Bool -> FilePath -> Alert
|
transferFileAlert :: Direction -> Bool -> FilePath -> Alert
|
||||||
transferFileAlert direction True
|
transferFileAlert direction True file
|
||||||
| direction == Upload = fileAlert "Uploaded"
|
| direction == Upload = fileAlert "Uploaded" [file]
|
||||||
| otherwise = fileAlert "Downloaded"
|
| otherwise = fileAlert "Downloaded" [file]
|
||||||
transferFileAlert direction False
|
transferFileAlert direction False file
|
||||||
| direction == Upload = fileAlert "Upload failed"
|
| direction == Upload = fileAlert "Upload failed" [file]
|
||||||
| otherwise = fileAlert "Download failed"
|
| otherwise = fileAlert "Download failed" [file]
|
||||||
|
|
||||||
dataCombiner :: ([TenseChunk] -> [TenseChunk] -> [TenseChunk]) -> AlertCombiner
|
dataCombiner :: ([TenseChunk] -> [TenseChunk] -> [TenseChunk]) -> AlertCombiner
|
||||||
dataCombiner combiner new old
|
dataCombiner combiner = fullCombiner $
|
||||||
|
\new old -> old { alertData = alertData new `combiner` alertData old }
|
||||||
|
|
||||||
|
fullCombiner :: (Alert -> Alert -> Alert) -> AlertCombiner
|
||||||
|
fullCombiner combiner new old
|
||||||
| alertClass new /= alertClass old = Nothing
|
| alertClass new /= alertClass old = Nothing
|
||||||
| alertName new == alertName old =
|
| alertName new == alertName old =
|
||||||
Just $! old { alertData = alertData new `combiner` alertData old }
|
Just $! new `combiner` old
|
||||||
| otherwise = Nothing
|
| otherwise = Nothing
|
||||||
|
|
||||||
shortFile :: FilePath -> String
|
shortFile :: FilePath -> String
|
||||||
|
|
|
@ -56,7 +56,7 @@ renderAlertHeader alert = renderTense (alertTense alert) <$> alertHeader alert
|
||||||
{- Renders an alert's message for display. -}
|
{- Renders an alert's message for display. -}
|
||||||
renderAlertMessage :: Alert -> Text
|
renderAlertMessage :: Alert -> Text
|
||||||
renderAlertMessage alert = renderTense (alertTense alert) $
|
renderAlertMessage alert = renderTense (alertTense alert) $
|
||||||
(alertMessageRender alert) (alertData alert)
|
(alertMessageRender alert) alert
|
||||||
|
|
||||||
showAlert :: Alert -> String
|
showAlert :: Alert -> String
|
||||||
showAlert alert = T.unpack $ T.unwords $ catMaybes
|
showAlert alert = T.unpack $ T.unwords $ catMaybes
|
||||||
|
|
|
@ -11,13 +11,14 @@ import Assistant.Common
|
||||||
import Assistant.DaemonStatus
|
import Assistant.DaemonStatus
|
||||||
import Logs.Location
|
import Logs.Location
|
||||||
import Logs.Trust
|
import Logs.Trust
|
||||||
import Types.Remote (AssociatedFile, uuid)
|
import Types.Remote (uuid)
|
||||||
import qualified Remote
|
import qualified Remote
|
||||||
import qualified Command.Drop
|
import qualified Command.Drop
|
||||||
import Command
|
import Command
|
||||||
import Annex.Wanted
|
import Annex.Wanted
|
||||||
import Annex.Exception
|
import Annex.Exception
|
||||||
import Config
|
import Config
|
||||||
|
import Annex.Content.Direct
|
||||||
|
|
||||||
import qualified Data.Set as S
|
import qualified Data.Set as S
|
||||||
|
|
||||||
|
@ -35,20 +36,30 @@ handleDrops reason fromhere key f knownpresentremote = do
|
||||||
{- The UUIDs are ones where the content is believed to be present.
|
{- The UUIDs are ones where the content is believed to be present.
|
||||||
- The Remote list can include other remotes that do not have the content;
|
- The Remote list can include other remotes that do not have the content;
|
||||||
- only ones that match the UUIDs will be dropped from.
|
- only ones that match the UUIDs will be dropped from.
|
||||||
- If allows to drop fromhere, that drop will be tried first. -}
|
- If allowed to drop fromhere, that drop will be tried first.
|
||||||
|
-
|
||||||
|
- In direct mode, all associated files are checked, and only if all
|
||||||
|
- of them are unwanted are they dropped.
|
||||||
|
-}
|
||||||
handleDropsFrom :: [UUID] -> [Remote] -> Reason -> Bool -> Key -> AssociatedFile -> Maybe Remote -> Assistant ()
|
handleDropsFrom :: [UUID] -> [Remote] -> Reason -> Bool -> Key -> AssociatedFile -> Maybe Remote -> Assistant ()
|
||||||
handleDropsFrom _ _ _ _ _ Nothing _ = noop
|
handleDropsFrom _ _ _ _ _ Nothing _ = noop
|
||||||
handleDropsFrom locs rs reason fromhere key (Just f) knownpresentremote
|
handleDropsFrom locs rs reason fromhere key (Just afile) knownpresentremote = do
|
||||||
| fromhere = do
|
fs <- liftAnnex $ ifM isDirect
|
||||||
n <- getcopies
|
( do
|
||||||
if checkcopies n Nothing
|
l <- associatedFilesRelative key
|
||||||
then go rs =<< dropl n
|
if null l
|
||||||
else go rs n
|
then return [afile]
|
||||||
| otherwise = go rs =<< getcopies
|
else return l
|
||||||
|
, return [afile]
|
||||||
|
)
|
||||||
|
n <- getcopies fs
|
||||||
|
if fromhere && checkcopies n Nothing
|
||||||
|
then go fs rs =<< dropl fs n
|
||||||
|
else go fs rs n
|
||||||
where
|
where
|
||||||
getcopies = liftAnnex $ do
|
getcopies fs = liftAnnex $ do
|
||||||
(untrusted, have) <- trustPartition UnTrusted locs
|
(untrusted, have) <- trustPartition UnTrusted locs
|
||||||
numcopies <- getNumCopies =<< numCopies f
|
numcopies <- maximum <$> mapM (getNumCopies <=< numCopies) fs
|
||||||
return (length have, numcopies, S.fromList untrusted)
|
return (length have, numcopies, S.fromList untrusted)
|
||||||
|
|
||||||
{- Check that we have enough copies still to drop the content.
|
{- Check that we have enough copies still to drop the content.
|
||||||
|
@ -66,20 +77,20 @@ handleDropsFrom locs rs reason fromhere key (Just f) knownpresentremote
|
||||||
| S.member u untrusted = v
|
| S.member u untrusted = v
|
||||||
| otherwise = decrcopies v Nothing
|
| otherwise = decrcopies v Nothing
|
||||||
|
|
||||||
go [] _ = noop
|
go _ [] _ = noop
|
||||||
go (r:rest) n
|
go fs (r:rest) n
|
||||||
| uuid r `S.notMember` slocs = go rest n
|
| uuid r `S.notMember` slocs = go fs rest n
|
||||||
| checkcopies n (Just $ Remote.uuid r) =
|
| checkcopies n (Just $ Remote.uuid r) =
|
||||||
dropr r n >>= go rest
|
dropr fs r n >>= go fs rest
|
||||||
| otherwise = noop
|
| otherwise = noop
|
||||||
|
|
||||||
checkdrop n@(have, numcopies, _untrusted) u a =
|
checkdrop fs n@(have, numcopies, _untrusted) u a =
|
||||||
ifM (liftAnnex $ wantDrop True u (Just f))
|
ifM (liftAnnex $ allM (wantDrop True u . Just) fs)
|
||||||
( ifM (liftAnnex $ safely $ doCommand $ a (Just numcopies))
|
( ifM (liftAnnex $ safely $ doCommand $ a (Just numcopies))
|
||||||
( do
|
( do
|
||||||
debug
|
debug
|
||||||
[ "dropped"
|
[ "dropped"
|
||||||
, f
|
, afile
|
||||||
, "(from " ++ maybe "here" show u ++ ")"
|
, "(from " ++ maybe "here" show u ++ ")"
|
||||||
, "(copies now " ++ show (have - 1) ++ ")"
|
, "(copies now " ++ show (have - 1) ++ ")"
|
||||||
, ": " ++ reason
|
, ": " ++ reason
|
||||||
|
@ -90,11 +101,11 @@ handleDropsFrom locs rs reason fromhere key (Just f) knownpresentremote
|
||||||
, return n
|
, return n
|
||||||
)
|
)
|
||||||
|
|
||||||
dropl n = checkdrop n Nothing $ \numcopies ->
|
dropl fs n = checkdrop fs n Nothing $ \numcopies ->
|
||||||
Command.Drop.startLocal f numcopies key knownpresentremote
|
Command.Drop.startLocal afile numcopies key knownpresentremote
|
||||||
|
|
||||||
dropr r n = checkdrop n (Just $ Remote.uuid r) $ \numcopies ->
|
dropr fs r n = checkdrop fs n (Just $ Remote.uuid r) $ \numcopies ->
|
||||||
Command.Drop.startRemote f numcopies key r
|
Command.Drop.startRemote afile numcopies key r
|
||||||
|
|
||||||
safely a = either (const False) id <$> tryAnnex a
|
safely a = either (const False) id <$> tryAnnex a
|
||||||
|
|
||||||
|
|
|
@ -49,8 +49,9 @@ ensureInstalled = go =<< standaloneAppBase
|
||||||
#ifdef darwin_HOST_OS
|
#ifdef darwin_HOST_OS
|
||||||
autostartfile <- userAutoStart osxAutoStartLabel
|
autostartfile <- userAutoStart osxAutoStartLabel
|
||||||
#else
|
#else
|
||||||
installMenu program
|
menufile <- desktopMenuFilePath "git-annex" <$> userDataDir
|
||||||
=<< desktopMenuFilePath "git-annex" <$> userDataDir
|
icondir <- iconDir <$> userDataDir
|
||||||
|
installMenu program menufile base icondir
|
||||||
autostartfile <- autoStartPath "git-annex" <$> userConfigDir
|
autostartfile <- autoStartPath "git-annex" <$> userConfigDir
|
||||||
#endif
|
#endif
|
||||||
installAutoStart program autostartfile
|
installAutoStart program autostartfile
|
||||||
|
|
|
@ -35,4 +35,5 @@ fdoAutostart command = genDesktopEntry
|
||||||
"Autostart"
|
"Autostart"
|
||||||
False
|
False
|
||||||
(command ++ " assistant --autostart")
|
(command ++ " assistant --autostart")
|
||||||
|
Nothing
|
||||||
[]
|
[]
|
||||||
|
|
|
@ -9,14 +9,20 @@
|
||||||
|
|
||||||
module Assistant.Install.Menu where
|
module Assistant.Install.Menu where
|
||||||
|
|
||||||
|
import Common
|
||||||
|
|
||||||
import Utility.FreeDesktop
|
import Utility.FreeDesktop
|
||||||
|
|
||||||
installMenu :: FilePath -> FilePath -> IO ()
|
installMenu :: FilePath -> FilePath -> FilePath -> FilePath -> IO ()
|
||||||
installMenu command file =
|
installMenu command menufile iconsrcdir icondir = do
|
||||||
#ifdef darwin_HOST_OS
|
#ifdef darwin_HOST_OS
|
||||||
return ()
|
return ()
|
||||||
#else
|
#else
|
||||||
writeDesktopMenuFile (fdoDesktopMenu command) file
|
writeDesktopMenuFile (fdoDesktopMenu command) menufile
|
||||||
|
installIcon (iconsrcdir </> "logo.svg") $
|
||||||
|
iconFilePath (iconBaseName ++ ".svg") "scalable" icondir
|
||||||
|
installIcon (iconsrcdir </> "favicon.png") $
|
||||||
|
iconFilePath (iconBaseName ++ ".png") "16x16" icondir
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
{- The command can be either just "git-annex", or the full path to use
|
{- The command can be either just "git-annex", or the full path to use
|
||||||
|
@ -27,4 +33,15 @@ fdoDesktopMenu command = genDesktopEntry
|
||||||
"Track and sync the files in your Git Annex"
|
"Track and sync the files in your Git Annex"
|
||||||
False
|
False
|
||||||
(command ++ " webapp")
|
(command ++ " webapp")
|
||||||
|
(Just iconBaseName)
|
||||||
["Network", "FileTransfer"]
|
["Network", "FileTransfer"]
|
||||||
|
|
||||||
|
installIcon :: FilePath -> FilePath -> IO ()
|
||||||
|
installIcon src dest = do
|
||||||
|
createDirectoryIfMissing True (parentDir dest)
|
||||||
|
withBinaryFile src ReadMode $ \hin ->
|
||||||
|
withBinaryFile dest WriteMode $ \hout ->
|
||||||
|
hGetContents hin >>= hPutStr hout
|
||||||
|
|
||||||
|
iconBaseName :: String
|
||||||
|
iconBaseName = "git-annex"
|
||||||
|
|
|
@ -27,6 +27,8 @@ import Creds
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
|
|
||||||
|
type RemoteName = String
|
||||||
|
|
||||||
{- Sets up and begins syncing with a new ssh or rsync remote. -}
|
{- Sets up and begins syncing with a new ssh or rsync remote. -}
|
||||||
makeSshRemote :: Bool -> SshData -> Maybe Cost -> Assistant Remote
|
makeSshRemote :: Bool -> SshData -> Maybe Cost -> Assistant Remote
|
||||||
makeSshRemote forcersync sshdata mcost = do
|
makeSshRemote forcersync sshdata mcost = do
|
||||||
|
@ -49,10 +51,11 @@ makeSshRemote forcersync sshdata mcost = do
|
||||||
h = sshHostName sshdata
|
h = sshHostName sshdata
|
||||||
d
|
d
|
||||||
| T.pack "/" `T.isPrefixOf` sshDirectory sshdata = sshDirectory sshdata
|
| T.pack "/" `T.isPrefixOf` sshDirectory sshdata = sshDirectory sshdata
|
||||||
|
| T.pack "~/" `T.isPrefixOf` sshDirectory sshdata = T.concat [T.pack "/", sshDirectory sshdata]
|
||||||
| otherwise = T.concat [T.pack "/~/", sshDirectory sshdata]
|
| otherwise = T.concat [T.pack "/~/", sshDirectory sshdata]
|
||||||
|
|
||||||
{- Runs an action that returns a name of the remote, and finishes adding it. -}
|
{- Runs an action that returns a name of the remote, and finishes adding it. -}
|
||||||
addRemote :: Annex String -> Annex Remote
|
addRemote :: Annex RemoteName -> Annex Remote
|
||||||
addRemote a = do
|
addRemote a = do
|
||||||
name <- a
|
name <- a
|
||||||
void remoteListRefresh
|
void remoteListRefresh
|
||||||
|
@ -60,36 +63,58 @@ addRemote a = do
|
||||||
=<< Remote.byName (Just name)
|
=<< Remote.byName (Just name)
|
||||||
|
|
||||||
{- Inits a rsync special remote, and returns its name. -}
|
{- Inits a rsync special remote, and returns its name. -}
|
||||||
makeRsyncRemote :: String -> String -> Annex String
|
makeRsyncRemote :: RemoteName -> String -> Annex String
|
||||||
makeRsyncRemote name location = makeRemote name location $
|
makeRsyncRemote name location = makeRemote name location $ const $ void $
|
||||||
const $ makeSpecialRemote name Rsync.remote config
|
go =<< Command.InitRemote.findExisting name
|
||||||
where
|
where
|
||||||
|
go Nothing = setupSpecialRemote name Rsync.remote config
|
||||||
|
=<< Command.InitRemote.generateNew name
|
||||||
|
go (Just v) = setupSpecialRemote name Rsync.remote config v
|
||||||
config = M.fromList
|
config = M.fromList
|
||||||
[ ("encryption", "shared")
|
[ ("encryption", "shared")
|
||||||
, ("rsyncurl", location)
|
, ("rsyncurl", location)
|
||||||
, ("type", "rsync")
|
, ("type", "rsync")
|
||||||
]
|
]
|
||||||
|
|
||||||
{- Inits a new special remote, or enables an existing one.
|
type SpecialRemoteMaker = RemoteName -> RemoteType -> R.RemoteConfig -> Annex RemoteName
|
||||||
-
|
|
||||||
- Currently, only 'weak' ciphers can be generated from the assistant,
|
{- Inits a new special remote. The name is used as a suggestion, but
|
||||||
- because otherwise GnuPG may block once the entropy pool is drained,
|
- will be changed if there is already a special remote with that name. -}
|
||||||
- and as of now there's no way to tell the user to perform IO actions
|
initSpecialRemote :: SpecialRemoteMaker
|
||||||
- to refill the pool. -}
|
initSpecialRemote name remotetype config = go 0
|
||||||
makeSpecialRemote :: String -> RemoteType -> R.RemoteConfig -> Annex ()
|
|
||||||
makeSpecialRemote name remotetype config =
|
|
||||||
go =<< Command.InitRemote.findExisting name
|
|
||||||
where
|
where
|
||||||
go Nothing = go =<< Just <$> Command.InitRemote.generateNew name
|
go :: Int -> Annex RemoteName
|
||||||
go (Just (u, c)) = do
|
go n = do
|
||||||
c' <- R.setup remotetype u $
|
let fullname = if n == 0 then name else name ++ show n
|
||||||
M.insert "highRandomQuality" "false" $ M.union config c
|
r <- Command.InitRemote.findExisting fullname
|
||||||
describeUUID u name
|
case r of
|
||||||
configSet u c'
|
Nothing -> setupSpecialRemote fullname remotetype config
|
||||||
|
=<< Command.InitRemote.generateNew fullname
|
||||||
|
Just _ -> go (n + 1)
|
||||||
|
|
||||||
|
{- Enables an existing special remote. -}
|
||||||
|
enableSpecialRemote :: SpecialRemoteMaker
|
||||||
|
enableSpecialRemote name remotetype config = do
|
||||||
|
r <- Command.InitRemote.findExisting name
|
||||||
|
case r of
|
||||||
|
Nothing -> error $ "Cannot find a special remote named " ++ name
|
||||||
|
Just v -> setupSpecialRemote name remotetype config v
|
||||||
|
|
||||||
|
setupSpecialRemote :: RemoteName -> RemoteType -> R.RemoteConfig -> (UUID, R.RemoteConfig) -> Annex RemoteName
|
||||||
|
setupSpecialRemote name remotetype config (u, c) = do
|
||||||
|
{- Currently, only 'weak' ciphers can be generated from the
|
||||||
|
- assistant, because otherwise GnuPG may block once the entropy
|
||||||
|
- pool is drained, and as of now there's no way to tell the user
|
||||||
|
- to perform IO actions to refill the pool. -}
|
||||||
|
c' <- R.setup remotetype u $
|
||||||
|
M.insert "highRandomQuality" "false" $ M.union config c
|
||||||
|
describeUUID u name
|
||||||
|
configSet u c'
|
||||||
|
return name
|
||||||
|
|
||||||
{- Returns the name of the git remote it created. If there's already a
|
{- Returns the name of the git remote it created. If there's already a
|
||||||
- remote at the location, returns its name. -}
|
- remote at the location, returns its name. -}
|
||||||
makeGitRemote :: String -> String -> Annex String
|
makeGitRemote :: String -> String -> Annex RemoteName
|
||||||
makeGitRemote basename location = makeRemote basename location $ \name ->
|
makeGitRemote basename location = makeRemote basename location $ \name ->
|
||||||
void $ inRepo $ Git.Command.runBool
|
void $ inRepo $ Git.Command.runBool
|
||||||
[Param "remote", Param "add", Param name, Param location]
|
[Param "remote", Param "add", Param name, Param location]
|
||||||
|
@ -98,7 +123,7 @@ makeGitRemote basename location = makeRemote basename location $ \name ->
|
||||||
- action, which is passed the name of the remote to make.
|
- action, which is passed the name of the remote to make.
|
||||||
-
|
-
|
||||||
- Returns the name of the remote. -}
|
- Returns the name of the remote. -}
|
||||||
makeRemote :: String -> String -> (String -> Annex ()) -> Annex String
|
makeRemote :: String -> String -> (RemoteName -> Annex ()) -> Annex RemoteName
|
||||||
makeRemote basename location a = do
|
makeRemote basename location a = do
|
||||||
g <- gitRepo
|
g <- gitRepo
|
||||||
if not (any samelocation $ Git.remotes g)
|
if not (any samelocation $ Git.remotes g)
|
||||||
|
@ -115,7 +140,7 @@ makeRemote basename location a = do
|
||||||
- necessary.
|
- necessary.
|
||||||
-
|
-
|
||||||
- Ensures that the returned name is a legal git remote name. -}
|
- Ensures that the returned name is a legal git remote name. -}
|
||||||
uniqueRemoteName :: String -> Int -> Git.Repo -> String
|
uniqueRemoteName :: String -> Int -> Git.Repo -> RemoteName
|
||||||
uniqueRemoteName basename n r
|
uniqueRemoteName basename n r
|
||||||
| null namecollision = name
|
| null namecollision = name
|
||||||
| otherwise = uniqueRemoteName legalbasename (succ n) r
|
| otherwise = uniqueRemoteName legalbasename (succ n) r
|
||||||
|
|
|
@ -1,21 +1,22 @@
|
||||||
{- git-annex assistant out of band network messager interface
|
{- git-annex assistant out of band network messager interface
|
||||||
-
|
-
|
||||||
- Copyright 2012 Joey Hess <joey@kitenet.net>
|
- Copyright 2012-2013 Joey Hess <joey@kitenet.net>
|
||||||
-
|
-
|
||||||
- Licensed under the GNU GPL version 3 or higher.
|
- Licensed under the GNU GPL version 3 or higher.
|
||||||
-}
|
-}
|
||||||
|
|
||||||
|
{-# LANGUAGE BangPatterns #-}
|
||||||
|
|
||||||
module Assistant.NetMessager where
|
module Assistant.NetMessager where
|
||||||
|
|
||||||
import Assistant.Common
|
import Assistant.Common
|
||||||
import Assistant.Types.NetMessager
|
import Assistant.Types.NetMessager
|
||||||
|
|
||||||
import Control.Concurrent
|
|
||||||
import Control.Concurrent.STM
|
import Control.Concurrent.STM
|
||||||
import Control.Concurrent.MSampleVar
|
import Control.Concurrent.MSampleVar
|
||||||
import Control.Exception as E
|
|
||||||
import qualified Data.Set as S
|
import qualified Data.Set as S
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
|
import qualified Data.DList as D
|
||||||
|
|
||||||
sendNetMessage :: NetMessage -> Assistant ()
|
sendNetMessage :: NetMessage -> Assistant ()
|
||||||
sendNetMessage m =
|
sendNetMessage m =
|
||||||
|
@ -31,8 +32,9 @@ notifyNetMessagerRestart =
|
||||||
waitNetMessagerRestart :: Assistant ()
|
waitNetMessagerRestart :: Assistant ()
|
||||||
waitNetMessagerRestart = readSV <<~ (netMessagerRestart . netMessager)
|
waitNetMessagerRestart = readSV <<~ (netMessagerRestart . netMessager)
|
||||||
|
|
||||||
{- Store an important NetMessage for a client, and if the same message was
|
{- Store a new important NetMessage for a client, and if an equivilant
|
||||||
- already sent, remove it from sentImportantNetMessages. -}
|
- older message is already stored, remove it from both importantNetMessages
|
||||||
|
- and sentImportantNetMessages. -}
|
||||||
storeImportantNetMessage :: NetMessage -> ClientID -> (ClientID -> Bool) -> Assistant ()
|
storeImportantNetMessage :: NetMessage -> ClientID -> (ClientID -> Bool) -> Assistant ()
|
||||||
storeImportantNetMessage m client matchingclient = go <<~ netMessager
|
storeImportantNetMessage m client matchingclient = go <<~ netMessager
|
||||||
where
|
where
|
||||||
|
@ -40,11 +42,12 @@ storeImportantNetMessage m client matchingclient = go <<~ netMessager
|
||||||
q <- takeTMVar $ importantNetMessages nm
|
q <- takeTMVar $ importantNetMessages nm
|
||||||
sent <- takeTMVar $ sentImportantNetMessages nm
|
sent <- takeTMVar $ sentImportantNetMessages nm
|
||||||
putTMVar (importantNetMessages nm) $
|
putTMVar (importantNetMessages nm) $
|
||||||
M.alter (Just . maybe (S.singleton m) (S.insert m)) client q
|
M.alter (Just . maybe (S.singleton m) (S.insert m)) client $
|
||||||
|
M.mapWithKey removematching q
|
||||||
putTMVar (sentImportantNetMessages nm) $
|
putTMVar (sentImportantNetMessages nm) $
|
||||||
M.mapWithKey removematching sent
|
M.mapWithKey removematching sent
|
||||||
removematching someclient s
|
removematching someclient s
|
||||||
| matchingclient someclient = S.delete m s
|
| matchingclient someclient = S.filter (not . equivilantImportantNetMessages m) s
|
||||||
| otherwise = s
|
| otherwise = s
|
||||||
|
|
||||||
{- Indicates that an important NetMessage has been sent to a client. -}
|
{- Indicates that an important NetMessage has been sent to a client. -}
|
||||||
|
@ -67,66 +70,107 @@ checkImportantNetMessages (storedclient, sentclient) = go <<~ netMessager
|
||||||
sent <- M.lookup sentclient <$> (readTMVar $ sentImportantNetMessages nm)
|
sent <- M.lookup sentclient <$> (readTMVar $ sentImportantNetMessages nm)
|
||||||
return (fromMaybe S.empty stored, fromMaybe S.empty sent)
|
return (fromMaybe S.empty stored, fromMaybe S.empty sent)
|
||||||
|
|
||||||
{- Runs an action that runs either the send or receive side of a push.
|
{- Queues a push initiation message in the queue for the appropriate
|
||||||
-
|
- side of the push but only if there is not already an initiation message
|
||||||
- While the push is running, netMessagesPush will get messages put into it
|
- from the same client in the queue. -}
|
||||||
- relating to this push, while any messages relating to other pushes
|
queuePushInitiation :: NetMessage -> Assistant ()
|
||||||
- on the same side go to netMessagesDeferred. Once the push finishes,
|
queuePushInitiation msg@(Pushing clientid stage) = do
|
||||||
- those deferred messages will be fed to handledeferred for processing.
|
tv <- getPushInitiationQueue side
|
||||||
-}
|
liftIO $ atomically $ do
|
||||||
runPush :: PushSide -> ClientID -> (NetMessage -> Assistant ()) -> Assistant a -> Assistant a
|
r <- tryTakeTMVar tv
|
||||||
runPush side clientid handledeferred a = do
|
case r of
|
||||||
nm <- getAssistant netMessager
|
Nothing -> putTMVar tv [msg]
|
||||||
let runningv = getSide side $ netMessagerPushRunning nm
|
Just l -> do
|
||||||
let setup = void $ atomically $ swapTMVar runningv $ Just clientid
|
let !l' = msg : filter differentclient l
|
||||||
let cleanup = atomically $ do
|
putTMVar tv l'
|
||||||
void $ swapTMVar runningv Nothing
|
|
||||||
emptytchan (getSide side $ netMessagesPush nm)
|
|
||||||
r <- E.bracket_ setup cleanup <~> a
|
|
||||||
(void . forkIO) <~> processdeferred nm
|
|
||||||
return r
|
|
||||||
where
|
where
|
||||||
emptytchan c = maybe noop (const $ emptytchan c) =<< tryReadTChan c
|
side = pushDestinationSide stage
|
||||||
processdeferred nm = do
|
differentclient (Pushing cid _) = cid /= clientid
|
||||||
s <- liftIO $ atomically $ swapTMVar (getSide side $ netMessagesPushDeferred nm) S.empty
|
differentclient _ = True
|
||||||
mapM_ rundeferred (S.toList s)
|
queuePushInitiation _ = noop
|
||||||
rundeferred m = (void . (E.try :: (IO () -> IO (Either SomeException ()))))
|
|
||||||
<~> handledeferred m
|
|
||||||
|
|
||||||
{- While a push is running, matching push messages are put into
|
{- Waits for a push inititation message to be received, and runs
|
||||||
- netMessagesPush, while others that involve the same side go to
|
- function to select a message from the queue. -}
|
||||||
- netMessagesPushDeferred.
|
waitPushInitiation :: PushSide -> ([NetMessage] -> (NetMessage, [NetMessage])) -> Assistant NetMessage
|
||||||
-
|
waitPushInitiation side selector = do
|
||||||
- When no push is running involving the same side, returns False.
|
tv <- getPushInitiationQueue side
|
||||||
-
|
|
||||||
- To avoid bloating memory, only messages that initiate pushes are
|
|
||||||
- deferred.
|
|
||||||
-}
|
|
||||||
queueNetPushMessage :: NetMessage -> Assistant Bool
|
|
||||||
queueNetPushMessage m@(Pushing clientid stage) = do
|
|
||||||
nm <- getAssistant netMessager
|
|
||||||
liftIO $ atomically $ do
|
liftIO $ atomically $ do
|
||||||
v <- readTMVar (getSide side $ netMessagerPushRunning nm)
|
q <- takeTMVar tv
|
||||||
case v of
|
if null q
|
||||||
Nothing -> return False
|
then retry
|
||||||
(Just runningclientid)
|
else do
|
||||||
| runningclientid == clientid -> queue nm
|
let (msg, !q') = selector q
|
||||||
| isPushInitiation stage -> defer nm
|
unless (null q') $
|
||||||
| otherwise -> discard
|
putTMVar tv q'
|
||||||
|
return msg
|
||||||
|
|
||||||
|
{- Stores messages for a push into the appropriate inbox.
|
||||||
|
-
|
||||||
|
- To avoid overflow, only 1000 messages max are stored in any
|
||||||
|
- inbox, which should be far more than necessary.
|
||||||
|
-
|
||||||
|
- TODO: If we have more than 100 inboxes for different clients,
|
||||||
|
- discard old ones that are not currently being used by any push.
|
||||||
|
-}
|
||||||
|
storeInbox :: NetMessage -> Assistant ()
|
||||||
|
storeInbox msg@(Pushing clientid stage) = do
|
||||||
|
inboxes <- getInboxes side
|
||||||
|
stored <- liftIO $ atomically $ do
|
||||||
|
m <- readTVar inboxes
|
||||||
|
let update = \v -> do
|
||||||
|
writeTVar inboxes $
|
||||||
|
M.insertWith' const clientid v m
|
||||||
|
return True
|
||||||
|
case M.lookup clientid m of
|
||||||
|
Nothing -> update (1, tostore)
|
||||||
|
Just (sz, l)
|
||||||
|
| sz > 1000 -> return False
|
||||||
|
| otherwise ->
|
||||||
|
let !sz' = sz + 1
|
||||||
|
!l' = D.append l tostore
|
||||||
|
in update (sz', l')
|
||||||
|
if stored
|
||||||
|
then netMessagerDebug clientid ["stored", logNetMessage msg, "in", show side, "inbox"]
|
||||||
|
else netMessagerDebug clientid ["discarded", logNetMessage msg, "; ", show side, "inbox is full"]
|
||||||
where
|
where
|
||||||
side = pushDestinationSide stage
|
side = pushDestinationSide stage
|
||||||
queue nm = do
|
tostore = D.singleton msg
|
||||||
writeTChan (getSide side $ netMessagesPush nm) m
|
storeInbox _ = noop
|
||||||
return True
|
|
||||||
defer nm = do
|
|
||||||
let mv = getSide side $ netMessagesPushDeferred nm
|
|
||||||
s <- takeTMVar mv
|
|
||||||
putTMVar mv $ S.insert m s
|
|
||||||
return True
|
|
||||||
discard = return True
|
|
||||||
queueNetPushMessage _ = return False
|
|
||||||
|
|
||||||
waitNetPushMessage :: PushSide -> Assistant (NetMessage)
|
{- Gets the new message for a push from its inbox.
|
||||||
waitNetPushMessage side = (atomically . readTChan)
|
- Blocks until a message has been received. -}
|
||||||
<<~ (getSide side . netMessagesPush . netMessager)
|
waitInbox :: ClientID -> PushSide -> Assistant (NetMessage)
|
||||||
|
waitInbox clientid side = do
|
||||||
|
inboxes <- getInboxes side
|
||||||
|
liftIO $ atomically $ do
|
||||||
|
m <- readTVar inboxes
|
||||||
|
case M.lookup clientid m of
|
||||||
|
Nothing -> retry
|
||||||
|
Just (sz, dl)
|
||||||
|
| sz < 1 -> retry
|
||||||
|
| otherwise -> do
|
||||||
|
let msg = D.head dl
|
||||||
|
let dl' = D.tail dl
|
||||||
|
let !sz' = sz - 1
|
||||||
|
writeTVar inboxes $
|
||||||
|
M.insertWith' const clientid (sz', dl') m
|
||||||
|
return msg
|
||||||
|
|
||||||
|
emptyInbox :: ClientID -> PushSide -> Assistant ()
|
||||||
|
emptyInbox clientid side = do
|
||||||
|
inboxes <- getInboxes side
|
||||||
|
liftIO $ atomically $
|
||||||
|
modifyTVar' inboxes $
|
||||||
|
M.delete clientid
|
||||||
|
|
||||||
|
getInboxes :: PushSide -> Assistant Inboxes
|
||||||
|
getInboxes side =
|
||||||
|
getSide side . netMessagerInboxes <$> getAssistant netMessager
|
||||||
|
|
||||||
|
getPushInitiationQueue :: PushSide -> Assistant (TMVar [NetMessage])
|
||||||
|
getPushInitiationQueue side =
|
||||||
|
getSide side . netMessagerPushInitiations <$> getAssistant netMessager
|
||||||
|
|
||||||
|
netMessagerDebug :: ClientID -> [String] -> Assistant ()
|
||||||
|
netMessagerDebug clientid l = debug $
|
||||||
|
"NetMessager" : l ++ [show $ logClientID clientid]
|
||||||
|
|
|
@ -16,6 +16,7 @@ import Git.Remote
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import Data.Char
|
import Data.Char
|
||||||
|
import Network.URI
|
||||||
|
|
||||||
data SshData = SshData
|
data SshData = SshData
|
||||||
{ sshHostName :: Text
|
{ sshHostName :: Text
|
||||||
|
@ -64,7 +65,10 @@ sshTranscript opts input = processTranscript "ssh" opts input
|
||||||
{- Ensure that the ssh public key doesn't include any ssh options, like
|
{- Ensure that the ssh public key doesn't include any ssh options, like
|
||||||
- command=foo, or other weirdness -}
|
- command=foo, or other weirdness -}
|
||||||
validateSshPubKey :: SshPubKey -> IO ()
|
validateSshPubKey :: SshPubKey -> IO ()
|
||||||
validateSshPubKey pubkey = either error return $ check $ words pubkey
|
validateSshPubKey pubkey
|
||||||
|
| length (lines pubkey) == 1 =
|
||||||
|
either error return $ check $ words pubkey
|
||||||
|
| otherwise = error "too many lines in ssh public key"
|
||||||
where
|
where
|
||||||
check [prefix, _key, comment] = do
|
check [prefix, _key, comment] = do
|
||||||
checkprefix prefix
|
checkprefix prefix
|
||||||
|
@ -82,9 +86,10 @@ validateSshPubKey pubkey = either error return $ check $ words pubkey
|
||||||
where
|
where
|
||||||
(ssh, keytype) = separate (== '-') prefix
|
(ssh, keytype) = separate (== '-') prefix
|
||||||
|
|
||||||
checkcomment comment
|
checkcomment comment = case filter (not . safeincomment) comment of
|
||||||
| all (\c -> isAlphaNum c || c == '@' || c == '-' || c == '_' || c == '.') comment = ok
|
[] -> ok
|
||||||
| otherwise = err "bad comment in ssh public key"
|
badstuff -> err $ "bad comment in ssh public key (contains: \"" ++ badstuff ++ "\")"
|
||||||
|
safeincomment c = isAlphaNum c || c == '@' || c == '-' || c == '_' || c == '.'
|
||||||
|
|
||||||
addAuthorizedKeys :: Bool -> FilePath -> SshPubKey -> IO Bool
|
addAuthorizedKeys :: Bool -> FilePath -> SshPubKey -> IO Bool
|
||||||
addAuthorizedKeys rsynconly dir pubkey = boolSystem "sh"
|
addAuthorizedKeys rsynconly dir pubkey = boolSystem "sh"
|
||||||
|
@ -164,9 +169,12 @@ genSshKeyPair = withTmpDir "git-annex-keygen" $ \dir -> do
|
||||||
-
|
-
|
||||||
- Note that the key files are put in ~/.ssh/git-annex/, rather than directly
|
- Note that the key files are put in ~/.ssh/git-annex/, rather than directly
|
||||||
- in ssh because of an **INSANE** behavior of gnome-keyring: It loads
|
- in ssh because of an **INSANE** behavior of gnome-keyring: It loads
|
||||||
- ~/.ssh/*.pub, and uses them indiscriminately. But using this key
|
- ~/.ssh/ANYTHING.pub, and uses them indiscriminately. But using this key
|
||||||
- for a normal login to the server will force git-annex-shell to run,
|
- for a normal login to the server will force git-annex-shell to run,
|
||||||
- and locks the user out. Luckily, it does not recurse into subdirectories.
|
- and locks the user out. Luckily, it does not recurse into subdirectories.
|
||||||
|
-
|
||||||
|
- Similarly, IdentitiesOnly is set in the ssh config to prevent the
|
||||||
|
- ssh-agent from forcing use of a different key.
|
||||||
-}
|
-}
|
||||||
setupSshKeyPair :: SshKeyPair -> SshData -> IO SshData
|
setupSshKeyPair :: SshKeyPair -> SshData -> IO SshData
|
||||||
setupSshKeyPair sshkeypair sshdata = do
|
setupSshKeyPair sshkeypair sshdata = do
|
||||||
|
@ -183,11 +191,43 @@ setupSshKeyPair sshkeypair sshdata = do
|
||||||
writeFile (sshdir </> sshpubkeyfile) (sshPubKey sshkeypair)
|
writeFile (sshdir </> sshpubkeyfile) (sshPubKey sshkeypair)
|
||||||
|
|
||||||
setSshConfig sshdata
|
setSshConfig sshdata
|
||||||
[ ("IdentityFile", "~/.ssh/" ++ sshprivkeyfile) ]
|
[ ("IdentityFile", "~/.ssh/" ++ sshprivkeyfile)
|
||||||
|
, ("IdentitiesOnly", "yes")
|
||||||
|
]
|
||||||
where
|
where
|
||||||
sshprivkeyfile = "git-annex" </> "key." ++ mangleSshHostName sshdata
|
sshprivkeyfile = "git-annex" </> "key." ++ mangleSshHostName sshdata
|
||||||
sshpubkeyfile = sshprivkeyfile ++ ".pub"
|
sshpubkeyfile = sshprivkeyfile ++ ".pub"
|
||||||
|
|
||||||
|
{- Fixes git-annex ssh key pairs configured in .ssh/config
|
||||||
|
- by old versions to set IdentitiesOnly. -}
|
||||||
|
fixSshKeyPair :: IO ()
|
||||||
|
fixSshKeyPair = do
|
||||||
|
sshdir <- sshDir
|
||||||
|
let configfile = sshdir </> "config"
|
||||||
|
whenM (doesFileExist configfile) $ do
|
||||||
|
ls <- lines <$> readFileStrict configfile
|
||||||
|
let ls' = fixSshKeyPair' ls
|
||||||
|
when (ls /= ls') $
|
||||||
|
viaTmp writeFile configfile $ unlines ls'
|
||||||
|
|
||||||
|
{- Strategy: Search for IdentityFile lines in for files with key.git-annex
|
||||||
|
- in their names. These are for git-annex ssh key pairs.
|
||||||
|
- Add the IdentitiesOnly line immediately after them, if not already
|
||||||
|
- present. -}
|
||||||
|
fixSshKeyPair' :: [String] -> [String]
|
||||||
|
fixSshKeyPair' = go []
|
||||||
|
where
|
||||||
|
go c [] = reverse c
|
||||||
|
go c (l:[])
|
||||||
|
| all (`isInfixOf` l) indicators = go (fixedline l:l:c) []
|
||||||
|
| otherwise = go (l:c) []
|
||||||
|
go c (l:next:rest)
|
||||||
|
| all (`isInfixOf` l) indicators && not ("IdentitiesOnly" `isInfixOf` next) =
|
||||||
|
go (fixedline l:l:c) (next:rest)
|
||||||
|
| otherwise = go (l:c) (next:rest)
|
||||||
|
indicators = ["IdentityFile", "key.git-annex"]
|
||||||
|
fixedline tmpl = takeWhile isSpace tmpl ++ "IdentitiesOnly yes"
|
||||||
|
|
||||||
{- Setups up a ssh config with a mangled hostname.
|
{- Setups up a ssh config with a mangled hostname.
|
||||||
- Returns a modified SshData containing the mangled hostname. -}
|
- Returns a modified SshData containing the mangled hostname. -}
|
||||||
setSshConfig :: SshData -> [(String, String)] -> IO SshData
|
setSshConfig :: SshData -> [(String, String)] -> IO SshData
|
||||||
|
@ -212,10 +252,16 @@ setSshConfig sshdata config = do
|
||||||
|
|
||||||
{- This hostname is specific to a given repository on the ssh host,
|
{- This hostname is specific to a given repository on the ssh host,
|
||||||
- so it is based on the real hostname, the username, and the directory.
|
- so it is based on the real hostname, the username, and the directory.
|
||||||
|
-
|
||||||
|
- The mangled hostname has the form "git-annex-realhostname-username_dir".
|
||||||
|
- The only use of "-" is to separate the parts shown; this is necessary
|
||||||
|
- to allow unMangleSshHostName to work. Any unusual characters in the
|
||||||
|
- username or directory are url encoded, except using "." rather than "%"
|
||||||
|
- (the latter has special meaning to ssh).
|
||||||
-}
|
-}
|
||||||
mangleSshHostName :: SshData -> String
|
mangleSshHostName :: SshData -> String
|
||||||
mangleSshHostName sshdata = "git-annex-" ++ T.unpack (sshHostName sshdata)
|
mangleSshHostName sshdata = "git-annex-" ++ T.unpack (sshHostName sshdata)
|
||||||
++ "-" ++ filter safe extra
|
++ "-" ++ escape extra
|
||||||
where
|
where
|
||||||
extra = intercalate "_" $ map T.unpack $ catMaybes
|
extra = intercalate "_" $ map T.unpack $ catMaybes
|
||||||
[ sshUserName sshdata
|
[ sshUserName sshdata
|
||||||
|
@ -225,6 +271,7 @@ mangleSshHostName sshdata = "git-annex-" ++ T.unpack (sshHostName sshdata)
|
||||||
| isAlphaNum c = True
|
| isAlphaNum c = True
|
||||||
| c == '_' = True
|
| c == '_' = True
|
||||||
| otherwise = False
|
| otherwise = False
|
||||||
|
escape s = replace "%" "." $ escapeURIString safe s
|
||||||
|
|
||||||
{- Extracts the real hostname from a mangled ssh hostname. -}
|
{- Extracts the real hostname from a mangled ssh hostname. -}
|
||||||
unMangleSshHostName :: String -> String
|
unMangleSshHostName :: String -> String
|
||||||
|
|
|
@ -20,6 +20,7 @@ import Utility.Parallel
|
||||||
import qualified Git
|
import qualified Git
|
||||||
import qualified Git.Branch
|
import qualified Git.Branch
|
||||||
import qualified Git.Command
|
import qualified Git.Command
|
||||||
|
import qualified Git.Ref
|
||||||
import qualified Remote
|
import qualified Remote
|
||||||
import qualified Types.Remote as Remote
|
import qualified Types.Remote as Remote
|
||||||
import qualified Annex.Branch
|
import qualified Annex.Branch
|
||||||
|
@ -112,8 +113,12 @@ pushToRemotes' now notifypushes remotes = do
|
||||||
<*> getUUID
|
<*> getUUID
|
||||||
let (xmppremotes, normalremotes) = partition isXMPPRemote remotes
|
let (xmppremotes, normalremotes) = partition isXMPPRemote remotes
|
||||||
ret <- go True branch g u normalremotes
|
ret <- go True branch g u normalremotes
|
||||||
forM_ xmppremotes $ \r ->
|
unless (null xmppremotes) $ do
|
||||||
sendNetMessage $ Pushing (getXMPPClientID r) (CanPush u)
|
shas <- liftAnnex $ map fst <$>
|
||||||
|
inRepo (Git.Ref.matchingWithHEAD
|
||||||
|
[Annex.Branch.fullname, Git.Ref.headRef])
|
||||||
|
forM_ xmppremotes $ \r -> sendNetMessage $
|
||||||
|
Pushing (getXMPPClientID r) (CanPush u shas)
|
||||||
return ret
|
return ret
|
||||||
where
|
where
|
||||||
go _ Nothing _ _ _ = return [] -- no branch, so nothing to do
|
go _ Nothing _ _ _ = return [] -- no branch, so nothing to do
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
- Licensed under the GNU GPL version 3 or higher.
|
- Licensed under the GNU GPL version 3 or higher.
|
||||||
-}
|
-}
|
||||||
|
|
||||||
{-# LANGUAGE CPP, BangPatterns #-}
|
{-# LANGUAGE CPP #-}
|
||||||
|
|
||||||
module Assistant.Threads.Committer where
|
module Assistant.Threads.Committer where
|
||||||
|
|
||||||
|
@ -75,33 +75,38 @@ refill cs = do
|
||||||
debug ["delaying commit of", show (length cs), "changes"]
|
debug ["delaying commit of", show (length cs), "changes"]
|
||||||
refillChanges cs
|
refillChanges cs
|
||||||
|
|
||||||
{- Wait for one or more changes to arrive to be committed. -}
|
{- Wait for one or more changes to arrive to be committed, and then
|
||||||
|
- runs an action to commit them. If more changes arrive while this is
|
||||||
|
- going on, they're handled intelligently, batching up changes into
|
||||||
|
- large commits where possible, doing rename detection, and
|
||||||
|
- commiting immediately otherwise. -}
|
||||||
waitChangeTime :: (([Change], UTCTime) -> Assistant Int) -> Assistant ()
|
waitChangeTime :: (([Change], UTCTime) -> Assistant Int) -> Assistant ()
|
||||||
waitChangeTime a = go [] 0
|
waitChangeTime a = waitchanges 0
|
||||||
where
|
where
|
||||||
go unhandled lastcommitsize = do
|
waitchanges lastcommitsize = do
|
||||||
-- Wait one one second as a simple rate limiter.
|
-- Wait one one second as a simple rate limiter.
|
||||||
liftIO $ threadDelaySeconds (Seconds 1)
|
liftIO $ threadDelaySeconds (Seconds 1)
|
||||||
-- Now, wait until at least one change is available for
|
-- Now, wait until at least one change is available for
|
||||||
-- processing.
|
-- processing.
|
||||||
cs <- getChanges
|
cs <- getChanges
|
||||||
let changes = unhandled ++ cs
|
handlechanges cs lastcommitsize
|
||||||
|
handlechanges changes lastcommitsize = do
|
||||||
let len = length changes
|
let len = length changes
|
||||||
-- See if now's a good time to commit.
|
-- See if now's a good time to commit.
|
||||||
now <- liftIO getCurrentTime
|
now <- liftIO getCurrentTime
|
||||||
case (lastcommitsize >= maxCommitSize, shouldCommit now len changes, possiblyrename changes) of
|
case (lastcommitsize >= maxCommitSize, shouldCommit now len changes, possiblyrename changes) of
|
||||||
(True, True, _)
|
(True, True, _)
|
||||||
| len > maxCommitSize ->
|
| len > maxCommitSize ->
|
||||||
go [] =<< a (changes, now)
|
waitchanges =<< a (changes, now)
|
||||||
| otherwise -> aftermaxcommit changes
|
| otherwise -> aftermaxcommit changes
|
||||||
(_, True, False) ->
|
(_, True, False) ->
|
||||||
go [] =<< a (changes, now)
|
waitchanges =<< a (changes, now)
|
||||||
(_, True, True) -> do
|
(_, True, True) -> do
|
||||||
morechanges <- getrelatedchanges changes
|
morechanges <- getrelatedchanges changes
|
||||||
go [] =<< a (changes ++ morechanges, now)
|
waitchanges =<< a (changes ++ morechanges, now)
|
||||||
_ -> do
|
_ -> do
|
||||||
refill changes
|
refill changes
|
||||||
go [] lastcommitsize
|
waitchanges lastcommitsize
|
||||||
|
|
||||||
{- Did we perhaps only get one of the AddChange and RmChange pair
|
{- Did we perhaps only get one of the AddChange and RmChange pair
|
||||||
- that make up a file rename? Or some of the pairs that make up
|
- that make up a file rename? Or some of the pairs that make up
|
||||||
|
@ -158,14 +163,17 @@ waitChangeTime a = go [] 0
|
||||||
-}
|
-}
|
||||||
aftermaxcommit oldchanges = loop (30 :: Int)
|
aftermaxcommit oldchanges = loop (30 :: Int)
|
||||||
where
|
where
|
||||||
loop 0 = go oldchanges 0
|
loop 0 = continue oldchanges
|
||||||
loop n = do
|
loop n = do
|
||||||
liftAnnex noop -- ensure Annex state is free
|
liftAnnex noop -- ensure Annex state is free
|
||||||
liftIO $ threadDelaySeconds (Seconds 1)
|
liftIO $ threadDelaySeconds (Seconds 1)
|
||||||
changes <- getAnyChanges
|
changes <- getAnyChanges
|
||||||
if null changes
|
if null changes
|
||||||
then loop (n - 1)
|
then loop (n - 1)
|
||||||
else go (oldchanges ++ changes) 0
|
else continue (oldchanges ++ changes)
|
||||||
|
continue cs
|
||||||
|
| null cs = waitchanges 0
|
||||||
|
| otherwise = handlechanges cs 0
|
||||||
|
|
||||||
isRmChange :: Change -> Bool
|
isRmChange :: Change -> Bool
|
||||||
isRmChange (Change { changeInfo = i }) | i == RmChange = True
|
isRmChange (Change { changeInfo = i }) | i == RmChange = True
|
||||||
|
@ -273,10 +281,11 @@ handleAdds :: Maybe Seconds -> [Change] -> Assistant [Change]
|
||||||
handleAdds delayadd cs = returnWhen (null incomplete) $ do
|
handleAdds delayadd cs = returnWhen (null incomplete) $ do
|
||||||
let (pending, inprocess) = partition isPendingAddChange incomplete
|
let (pending, inprocess) = partition isPendingAddChange incomplete
|
||||||
direct <- liftAnnex isDirect
|
direct <- liftAnnex isDirect
|
||||||
pending' <- if direct
|
(pending', cleanup) <- if direct
|
||||||
then return pending
|
then return (pending, noop)
|
||||||
else findnew pending
|
else findnew pending
|
||||||
(postponed, toadd) <- partitionEithers <$> safeToAdd delayadd pending' inprocess
|
(postponed, toadd) <- partitionEithers <$> safeToAdd delayadd pending' inprocess
|
||||||
|
cleanup
|
||||||
|
|
||||||
unless (null postponed) $
|
unless (null postponed) $
|
||||||
refillChanges postponed
|
refillChanges postponed
|
||||||
|
@ -294,14 +303,13 @@ handleAdds delayadd cs = returnWhen (null incomplete) $ do
|
||||||
where
|
where
|
||||||
(incomplete, otherchanges) = partition (\c -> isPendingAddChange c || isInProcessAddChange c) cs
|
(incomplete, otherchanges) = partition (\c -> isPendingAddChange c || isInProcessAddChange c) cs
|
||||||
|
|
||||||
findnew [] = return []
|
findnew [] = return ([], noop)
|
||||||
findnew pending@(exemplar:_) = do
|
findnew pending@(exemplar:_) = do
|
||||||
(!newfiles, cleanup) <- liftAnnex $
|
(newfiles, cleanup) <- liftAnnex $
|
||||||
inRepo (Git.LsFiles.notInRepo False $ map changeFile pending)
|
inRepo (Git.LsFiles.notInRepo False $ map changeFile pending)
|
||||||
void $ liftIO cleanup
|
|
||||||
-- note: timestamp info is lost here
|
-- note: timestamp info is lost here
|
||||||
let ts = changeTime exemplar
|
let ts = changeTime exemplar
|
||||||
return $ map (PendingAddChange ts) newfiles
|
return (map (PendingAddChange ts) newfiles, void $ liftIO $ cleanup)
|
||||||
|
|
||||||
returnWhen c a
|
returnWhen c a
|
||||||
| c = return otherchanges
|
| c = return otherchanges
|
||||||
|
@ -383,7 +391,7 @@ handleAdds delayadd cs = returnWhen (null incomplete) $ do
|
||||||
return Nothing
|
return Nothing
|
||||||
|
|
||||||
{- Shown an alert while performing an action to add a file or
|
{- Shown an alert while performing an action to add a file or
|
||||||
- files. When only one file is added, its name is shown
|
- files. When only a few files are added, their names are shown
|
||||||
- in the alert. When it's a batch add, the number of files added
|
- in the alert. When it's a batch add, the number of files added
|
||||||
- is shown.
|
- is shown.
|
||||||
-
|
-
|
||||||
|
@ -392,15 +400,10 @@ handleAdds delayadd cs = returnWhen (null incomplete) $ do
|
||||||
- the add succeeded.
|
- the add succeeded.
|
||||||
-}
|
-}
|
||||||
addaction [] a = a
|
addaction [] a = a
|
||||||
addaction toadd a = alertWhile' (addFileAlert msg) $
|
addaction toadd a = alertWhile' (addFileAlert $ map changeFile toadd) $
|
||||||
(,)
|
(,)
|
||||||
<$> pure True
|
<$> pure True
|
||||||
<*> a
|
<*> a
|
||||||
where
|
|
||||||
msg = case toadd of
|
|
||||||
(InProcessAddChange { keySource = ks }:[]) ->
|
|
||||||
keyFilename ks
|
|
||||||
_ -> show (length toadd) ++ " files"
|
|
||||||
|
|
||||||
{- Files can Either be Right to be added now,
|
{- Files can Either be Right to be added now,
|
||||||
- or are unsafe, and must be Left for later.
|
- or are unsafe, and must be Left for later.
|
||||||
|
|
|
@ -13,8 +13,8 @@ module Assistant.Threads.NetWatcher where
|
||||||
import Assistant.Common
|
import Assistant.Common
|
||||||
import Assistant.Sync
|
import Assistant.Sync
|
||||||
import Utility.ThreadScheduler
|
import Utility.ThreadScheduler
|
||||||
import Remote.List
|
|
||||||
import qualified Types.Remote as Remote
|
import qualified Types.Remote as Remote
|
||||||
|
import Assistant.DaemonStatus
|
||||||
|
|
||||||
#if WITH_DBUS
|
#if WITH_DBUS
|
||||||
import Utility.DBus
|
import Utility.DBus
|
||||||
|
@ -125,7 +125,7 @@ listenWicdConnections client callback =
|
||||||
handleConnection :: Assistant ()
|
handleConnection :: Assistant ()
|
||||||
handleConnection = reconnectRemotes True =<< networkRemotes
|
handleConnection = reconnectRemotes True =<< networkRemotes
|
||||||
|
|
||||||
{- Finds network remotes. -}
|
{- Network remotes to sync with. -}
|
||||||
networkRemotes :: Assistant [Remote]
|
networkRemotes :: Assistant [Remote]
|
||||||
networkRemotes = liftAnnex $
|
networkRemotes = filter (isNothing . Remote.localpath) . syncRemotes
|
||||||
filter (isNothing . Remote.localpath) <$> remoteList
|
<$> getDaemonStatus
|
||||||
|
|
|
@ -37,6 +37,7 @@ pairListenerThread urlrenderer = namedThread "PairListener" $ do
|
||||||
go reqs cache sock = liftIO (getmsg sock []) >>= \msg -> case readish msg of
|
go reqs cache sock = liftIO (getmsg sock []) >>= \msg -> case readish msg of
|
||||||
Nothing -> go reqs cache sock
|
Nothing -> go reqs cache sock
|
||||||
Just m -> do
|
Just m -> do
|
||||||
|
debug ["received", show msg]
|
||||||
sane <- checkSane msg
|
sane <- checkSane msg
|
||||||
(pip, verified) <- verificationCheck m
|
(pip, verified) <- verificationCheck m
|
||||||
=<< (pairingInProgress <$> getDaemonStatus)
|
=<< (pairingInProgress <$> getDaemonStatus)
|
||||||
|
|
|
@ -19,6 +19,7 @@ import qualified Git.Config
|
||||||
import Utility.ThreadScheduler
|
import Utility.ThreadScheduler
|
||||||
import qualified Assistant.Threads.Watcher as Watcher
|
import qualified Assistant.Threads.Watcher as Watcher
|
||||||
import Utility.LogFile
|
import Utility.LogFile
|
||||||
|
import Utility.Batch
|
||||||
import Config
|
import Config
|
||||||
|
|
||||||
import Data.Time.Clock.POSIX
|
import Data.Time.Clock.POSIX
|
||||||
|
@ -42,7 +43,7 @@ sanityCheckerDailyThread = namedThread "SanityCheckerDaily" $ forever $ do
|
||||||
modifyDaemonStatus_ $ \s -> s { sanityCheckRunning = True }
|
modifyDaemonStatus_ $ \s -> s { sanityCheckRunning = True }
|
||||||
|
|
||||||
now <- liftIO $ getPOSIXTime -- before check started
|
now <- liftIO $ getPOSIXTime -- before check started
|
||||||
r <- either showerr return =<< tryIO <~> dailyCheck
|
r <- either showerr return =<< (tryIO . batch) <~> dailyCheck
|
||||||
|
|
||||||
modifyDaemonStatus_ $ \s -> s
|
modifyDaemonStatus_ $ \s -> s
|
||||||
{ sanityCheckRunning = False
|
{ sanityCheckRunning = False
|
||||||
|
|
|
@ -24,6 +24,7 @@ import qualified Remote
|
||||||
import qualified Types.Remote as Remote
|
import qualified Types.Remote as Remote
|
||||||
import Utility.ThreadScheduler
|
import Utility.ThreadScheduler
|
||||||
import Utility.NotificationBroadcaster
|
import Utility.NotificationBroadcaster
|
||||||
|
import Utility.Batch
|
||||||
import qualified Git.LsFiles as LsFiles
|
import qualified Git.LsFiles as LsFiles
|
||||||
import qualified Backend
|
import qualified Backend
|
||||||
import Annex.Content
|
import Annex.Content
|
||||||
|
@ -114,7 +115,7 @@ failedTransferScan r = do
|
||||||
- since we need to look at the locations of all keys anyway.
|
- since we need to look at the locations of all keys anyway.
|
||||||
-}
|
-}
|
||||||
expensiveScan :: UrlRenderer -> [Remote] -> Assistant ()
|
expensiveScan :: UrlRenderer -> [Remote] -> Assistant ()
|
||||||
expensiveScan urlrenderer rs = unless onlyweb $ do
|
expensiveScan urlrenderer rs = unless onlyweb $ batch <~> do
|
||||||
debug ["starting scan of", show visiblers]
|
debug ["starting scan of", show visiblers]
|
||||||
|
|
||||||
unwantedrs <- liftAnnex $ S.fromList
|
unwantedrs <- liftAnnex $ S.fromList
|
||||||
|
|
|
@ -226,7 +226,6 @@ onAddDirect symlinkssupported matcher file fs = do
|
||||||
| symlinkssupported = a
|
| symlinkssupported = a
|
||||||
| otherwise = do
|
| otherwise = do
|
||||||
linktarget <- liftAnnex $ getAnnexLinkTarget file
|
linktarget <- liftAnnex $ getAnnexLinkTarget file
|
||||||
liftIO $ print (file, linktarget)
|
|
||||||
case linktarget of
|
case linktarget of
|
||||||
Nothing -> a
|
Nothing -> a
|
||||||
Just lt -> do
|
Just lt -> do
|
||||||
|
|
|
@ -5,7 +5,8 @@
|
||||||
- Licensed under the GNU GPL version 3 or higher.
|
- Licensed under the GNU GPL version 3 or higher.
|
||||||
-}
|
-}
|
||||||
|
|
||||||
{-# LANGUAGE TypeFamilies, QuasiQuotes, MultiParamTypeClasses, TemplateHaskell, OverloadedStrings, RankNTypes, CPP #-}
|
{-# LANGUAGE TemplateHaskell, MultiParamTypeClasses #-}
|
||||||
|
{-# LANGUAGE CPP #-}
|
||||||
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||||
|
|
||||||
module Assistant.Threads.WebApp where
|
module Assistant.Threads.WebApp where
|
||||||
|
@ -50,7 +51,7 @@ webAppThread
|
||||||
-> UrlRenderer
|
-> UrlRenderer
|
||||||
-> Bool
|
-> Bool
|
||||||
-> Maybe HostName
|
-> Maybe HostName
|
||||||
-> Maybe (IO String)
|
-> Maybe (IO Url)
|
||||||
-> Maybe (Url -> FilePath -> IO ())
|
-> Maybe (Url -> FilePath -> IO ())
|
||||||
-> NamedThread
|
-> NamedThread
|
||||||
webAppThread assistantdata urlrenderer noannex listenhost postfirstrun onstartup = thread $ liftIO $ do
|
webAppThread assistantdata urlrenderer noannex listenhost postfirstrun onstartup = thread $ liftIO $ do
|
||||||
|
|
|
@ -20,7 +20,6 @@ import qualified Remote
|
||||||
import Utility.ThreadScheduler
|
import Utility.ThreadScheduler
|
||||||
import Assistant.WebApp (UrlRenderer)
|
import Assistant.WebApp (UrlRenderer)
|
||||||
import Assistant.WebApp.Types hiding (liftAssistant)
|
import Assistant.WebApp.Types hiding (liftAssistant)
|
||||||
import Assistant.WebApp.Configurators.XMPP (checkCloudRepos)
|
|
||||||
import Assistant.Alert
|
import Assistant.Alert
|
||||||
import Assistant.Pairing
|
import Assistant.Pairing
|
||||||
import Assistant.XMPP.Git
|
import Assistant.XMPP.Git
|
||||||
|
@ -29,11 +28,14 @@ import Logs.UUID
|
||||||
|
|
||||||
import Network.Protocol.XMPP
|
import Network.Protocol.XMPP
|
||||||
import Control.Concurrent
|
import Control.Concurrent
|
||||||
|
import Control.Concurrent.STM.TMVar
|
||||||
|
import Control.Concurrent.STM (atomically)
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import qualified Data.Set as S
|
import qualified Data.Set as S
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
import qualified Git.Branch
|
import qualified Git.Branch
|
||||||
import Data.Time.Clock
|
import Data.Time.Clock
|
||||||
|
import Control.Concurrent.Async
|
||||||
|
|
||||||
xmppClientThread :: UrlRenderer -> NamedThread
|
xmppClientThread :: UrlRenderer -> NamedThread
|
||||||
xmppClientThread urlrenderer = namedThread "XMPPClient" $
|
xmppClientThread urlrenderer = namedThread "XMPPClient" $
|
||||||
|
@ -65,16 +67,16 @@ xmppClient urlrenderer d creds =
|
||||||
- is not retained. -}
|
- is not retained. -}
|
||||||
liftAssistant $
|
liftAssistant $
|
||||||
updateBuddyList (const noBuddies) <<~ buddyList
|
updateBuddyList (const noBuddies) <<~ buddyList
|
||||||
e <- client
|
void client
|
||||||
liftAssistant $ modifyDaemonStatus_ $ \s -> s
|
liftAssistant $ modifyDaemonStatus_ $ \s -> s
|
||||||
{ xmppClientID = Nothing }
|
{ xmppClientID = Nothing }
|
||||||
now <- getCurrentTime
|
now <- getCurrentTime
|
||||||
if diffUTCTime now starttime > 300
|
if diffUTCTime now starttime > 300
|
||||||
then do
|
then do
|
||||||
liftAssistant $ debug ["connection lost; reconnecting", show e]
|
liftAssistant $ debug ["connection lost; reconnecting"]
|
||||||
retry client now
|
retry client now
|
||||||
else do
|
else do
|
||||||
liftAssistant $ debug ["connection failed; will retry", show e]
|
liftAssistant $ debug ["connection failed; will retry"]
|
||||||
threadDelaySeconds (Seconds 300)
|
threadDelaySeconds (Seconds 300)
|
||||||
retry client =<< getCurrentTime
|
retry client =<< getCurrentTime
|
||||||
|
|
||||||
|
@ -87,16 +89,43 @@ xmppClient urlrenderer d creds =
|
||||||
{ xmppClientID = Just $ xmppJID creds }
|
{ xmppClientID = Just $ xmppJID creds }
|
||||||
debug ["connected", logJid selfjid]
|
debug ["connected", logJid selfjid]
|
||||||
|
|
||||||
xmppThread $ receivenotifications selfjid
|
lasttraffic <- liftIO $ atomically . newTMVar =<< getCurrentTime
|
||||||
forever $ do
|
|
||||||
a <- inAssistant $ relayNetMessage selfjid
|
|
||||||
a
|
|
||||||
|
|
||||||
receivenotifications selfjid = forever $ do
|
sender <- xmppSession $ sendnotifications selfjid
|
||||||
|
receiver <- xmppSession $ receivenotifications selfjid lasttraffic
|
||||||
|
pinger <- xmppSession $ sendpings selfjid lasttraffic
|
||||||
|
{- Run all 3 threads concurrently, until
|
||||||
|
- any of them throw an exception.
|
||||||
|
- Then kill all 3 threads, and rethrow the
|
||||||
|
- exception.
|
||||||
|
-
|
||||||
|
- If this thread gets an exception, the 3 threads
|
||||||
|
- will also be killed. -}
|
||||||
|
liftIO $ pinger `concurrently` sender `concurrently` receiver
|
||||||
|
|
||||||
|
sendnotifications selfjid = forever $ do
|
||||||
|
a <- inAssistant $ relayNetMessage selfjid
|
||||||
|
a
|
||||||
|
receivenotifications selfjid lasttraffic = forever $ do
|
||||||
l <- decodeStanza selfjid <$> getStanza
|
l <- decodeStanza selfjid <$> getStanza
|
||||||
|
void $ liftIO $ atomically . swapTMVar lasttraffic =<< getCurrentTime
|
||||||
inAssistant $ debug
|
inAssistant $ debug
|
||||||
["received:", show $ map logXMPPEvent l]
|
["received:", show $ map logXMPPEvent l]
|
||||||
mapM_ (handle selfjid) l
|
mapM_ (handle selfjid) l
|
||||||
|
sendpings selfjid lasttraffic = forever $ do
|
||||||
|
putStanza pingstanza
|
||||||
|
|
||||||
|
startping <- liftIO $ getCurrentTime
|
||||||
|
liftIO $ threadDelaySeconds (Seconds 120)
|
||||||
|
t <- liftIO $ atomically $ readTMVar lasttraffic
|
||||||
|
when (t < startping) $ do
|
||||||
|
inAssistant $ debug ["ping timeout"]
|
||||||
|
error "ping timeout"
|
||||||
|
where
|
||||||
|
{- XEP-0199 says that the server will respond with either
|
||||||
|
- a ping response or an error message. Either will
|
||||||
|
- cause traffic, so good enough. -}
|
||||||
|
pingstanza = xmppPing selfjid
|
||||||
|
|
||||||
handle selfjid (PresenceMessage p) = do
|
handle selfjid (PresenceMessage p) = do
|
||||||
void $ inAssistant $
|
void $ inAssistant $
|
||||||
|
@ -107,11 +136,9 @@ xmppClient urlrenderer d creds =
|
||||||
handle selfjid (GotNetMessage (PairingNotification stage c u)) =
|
handle selfjid (GotNetMessage (PairingNotification stage c u)) =
|
||||||
maybe noop (inAssistant . pairMsgReceived urlrenderer stage u selfjid) (parseJID c)
|
maybe noop (inAssistant . pairMsgReceived urlrenderer stage u selfjid) (parseJID c)
|
||||||
handle _ (GotNetMessage m@(Pushing _ pushstage))
|
handle _ (GotNetMessage m@(Pushing _ pushstage))
|
||||||
| isPushInitiation pushstage = inAssistant $
|
| isPushNotice pushstage = inAssistant $ handlePushNotice m
|
||||||
unlessM (queueNetPushMessage m) $ do
|
| isPushInitiation pushstage = inAssistant $ queuePushInitiation m
|
||||||
let checker = checkCloudRepos urlrenderer
|
| otherwise = inAssistant $ storeInbox m
|
||||||
void $ forkIO <~> handlePushInitiation checker m
|
|
||||||
| otherwise = void $ inAssistant $ queueNetPushMessage m
|
|
||||||
handle _ (Ignorable _) = noop
|
handle _ (Ignorable _) = noop
|
||||||
handle _ (Unknown _) = noop
|
handle _ (Unknown _) = noop
|
||||||
handle _ (ProtocolError _) = noop
|
handle _ (ProtocolError _) = noop
|
||||||
|
@ -144,7 +171,9 @@ logXMPPEvent :: XMPPEvent -> String
|
||||||
logXMPPEvent (GotNetMessage m) = logNetMessage m
|
logXMPPEvent (GotNetMessage m) = logNetMessage m
|
||||||
logXMPPEvent (PresenceMessage p) = logPresence p
|
logXMPPEvent (PresenceMessage p) = logPresence p
|
||||||
logXMPPEvent (Ignorable (ReceivedPresence p)) = "Ignorable " ++ logPresence p
|
logXMPPEvent (Ignorable (ReceivedPresence p)) = "Ignorable " ++ logPresence p
|
||||||
logXMPPEvent v = show v
|
logXMPPEvent (Ignorable _) = "Ignorable message"
|
||||||
|
logXMPPEvent (Unknown _) = "Unknown message"
|
||||||
|
logXMPPEvent (ProtocolError _) = "Protocol error message"
|
||||||
|
|
||||||
logPresence :: Presence -> String
|
logPresence :: Presence -> String
|
||||||
logPresence (p@Presence { presenceFrom = Just jid }) = unwords
|
logPresence (p@Presence { presenceFrom = Just jid }) = unwords
|
||||||
|
@ -247,13 +276,12 @@ withOtherClient selfjid c a = case parseJID c of
|
||||||
withClient :: ClientID -> (JID -> XMPP ()) -> XMPP ()
|
withClient :: ClientID -> (JID -> XMPP ()) -> XMPP ()
|
||||||
withClient c a = maybe noop a $ parseJID c
|
withClient c a = maybe noop a $ parseJID c
|
||||||
|
|
||||||
{- Runs a XMPP action in a separate thread, using a session to allow it
|
{- Returns an IO action that runs a XMPP action in a separate thread,
|
||||||
- to access the same XMPP client. -}
|
- using a session to allow it to access the same XMPP client. -}
|
||||||
xmppThread :: XMPP () -> XMPP ()
|
xmppSession :: XMPP () -> XMPP (IO ())
|
||||||
xmppThread a = do
|
xmppSession a = do
|
||||||
s <- getSession
|
s <- getSession
|
||||||
void $ liftIO $ forkIO $
|
return $ void $ runXMPP s a
|
||||||
void $ runXMPP s a
|
|
||||||
|
|
||||||
{- We only pull from one remote out of the set listed in the push
|
{- We only pull from one remote out of the set listed in the push
|
||||||
- notification, as an optimisation.
|
- notification, as an optimisation.
|
||||||
|
|
81
Assistant/Threads/XMPPPusher.hs
Normal file
81
Assistant/Threads/XMPPPusher.hs
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
{- git-annex XMPP pusher threads
|
||||||
|
-
|
||||||
|
- This is a pair of threads. One handles git send-pack,
|
||||||
|
- and the other git receive-pack. Each thread can be running at most
|
||||||
|
- one such operation at a time.
|
||||||
|
-
|
||||||
|
- Why not use a single thread? Consider two clients A and B.
|
||||||
|
- If both decide to run a receive-pack at the same time to the other,
|
||||||
|
- they would deadlock with only one thread. For larger numbers of
|
||||||
|
- clients, the two threads are also sufficient.
|
||||||
|
-
|
||||||
|
- Copyright 2013 Joey Hess <joey@kitenet.net>
|
||||||
|
-
|
||||||
|
- Licensed under the GNU GPL version 3 or higher.
|
||||||
|
-}
|
||||||
|
|
||||||
|
module Assistant.Threads.XMPPPusher where
|
||||||
|
|
||||||
|
import Assistant.Common
|
||||||
|
import Assistant.NetMessager
|
||||||
|
import Assistant.Types.NetMessager
|
||||||
|
import Assistant.WebApp (UrlRenderer)
|
||||||
|
import Assistant.WebApp.Configurators.XMPP (checkCloudRepos)
|
||||||
|
import Assistant.XMPP.Git
|
||||||
|
|
||||||
|
import Control.Exception as E
|
||||||
|
|
||||||
|
xmppSendPackThread :: UrlRenderer -> NamedThread
|
||||||
|
xmppSendPackThread = pusherThread "XMPPSendPack" SendPack
|
||||||
|
|
||||||
|
xmppReceivePackThread :: UrlRenderer -> NamedThread
|
||||||
|
xmppReceivePackThread = pusherThread "XMPPReceivePack" ReceivePack
|
||||||
|
|
||||||
|
pusherThread :: String -> PushSide -> UrlRenderer -> NamedThread
|
||||||
|
pusherThread threadname side urlrenderer = namedThread threadname $ go Nothing
|
||||||
|
where
|
||||||
|
go lastpushedto = do
|
||||||
|
msg <- waitPushInitiation side $ selectNextPush lastpushedto
|
||||||
|
debug ["started running push", logNetMessage msg]
|
||||||
|
|
||||||
|
runpush <- asIO $ runPush checker msg
|
||||||
|
r <- liftIO (E.try runpush :: IO (Either SomeException (Maybe ClientID)))
|
||||||
|
let successful = case r of
|
||||||
|
Right (Just _) -> True
|
||||||
|
_ -> False
|
||||||
|
|
||||||
|
{- Empty the inbox, because stuff may have
|
||||||
|
- been left in it if the push failed. -}
|
||||||
|
let justpushedto = getclient msg
|
||||||
|
maybe noop (`emptyInbox` side) justpushedto
|
||||||
|
|
||||||
|
debug ["finished running push", logNetMessage msg, show successful]
|
||||||
|
go $ if successful then justpushedto else lastpushedto
|
||||||
|
|
||||||
|
checker = checkCloudRepos urlrenderer
|
||||||
|
|
||||||
|
getclient (Pushing cid _) = Just cid
|
||||||
|
getclient _ = Nothing
|
||||||
|
|
||||||
|
{- Select the next push to run from the queue.
|
||||||
|
- The queue cannot be empty!
|
||||||
|
-
|
||||||
|
- We prefer to select the most recently added push, because its requestor
|
||||||
|
- is more likely to still be connected.
|
||||||
|
-
|
||||||
|
- When passed the ID of a client we just pushed to, we prefer to not
|
||||||
|
- immediately push again to that same client. This avoids one client
|
||||||
|
- drowing out others. So pushes from the client we just pushed to are
|
||||||
|
- relocated to the beginning of the list, to be processed later.
|
||||||
|
-}
|
||||||
|
selectNextPush :: Maybe ClientID -> [NetMessage] -> (NetMessage, [NetMessage])
|
||||||
|
selectNextPush _ (m:[]) = (m, []) -- common case
|
||||||
|
selectNextPush _ [] = error "selectNextPush: empty list"
|
||||||
|
selectNextPush lastpushedto l = go [] l
|
||||||
|
where
|
||||||
|
go (r:ejected) [] = (r, ejected)
|
||||||
|
go rejected (m:ms) = case m of
|
||||||
|
(Pushing clientid _)
|
||||||
|
| Just clientid /= lastpushedto -> (m, rejected ++ ms)
|
||||||
|
_ -> go (m:rejected) ms
|
||||||
|
go [] [] = undefined
|
|
@ -16,7 +16,6 @@ import Control.Concurrent.STM
|
||||||
import System.Process (create_group)
|
import System.Process (create_group)
|
||||||
import Control.Exception (throw)
|
import Control.Exception (throw)
|
||||||
import Control.Concurrent
|
import Control.Concurrent
|
||||||
import Types.Remote (AssociatedFile)
|
|
||||||
|
|
||||||
{- Runs an action with a Transferrer from the pool. -}
|
{- Runs an action with a Transferrer from the pool. -}
|
||||||
withTransferrer :: FilePath -> TransferrerPool -> (Transferrer -> IO a) -> IO a
|
withTransferrer :: FilePath -> TransferrerPool -> (Transferrer -> IO a) -> IO a
|
||||||
|
|
|
@ -39,8 +39,9 @@ type AlertCombiner = Alert -> Alert -> Maybe Alert
|
||||||
data Alert = Alert
|
data Alert = Alert
|
||||||
{ alertClass :: AlertClass
|
{ alertClass :: AlertClass
|
||||||
, alertHeader :: Maybe TenseText
|
, alertHeader :: Maybe TenseText
|
||||||
, alertMessageRender :: [TenseChunk] -> TenseText
|
, alertMessageRender :: Alert -> TenseText
|
||||||
, alertData :: [TenseChunk]
|
, alertData :: [TenseChunk]
|
||||||
|
, alertCounter :: Int
|
||||||
, alertBlockDisplay :: Bool
|
, alertBlockDisplay :: Bool
|
||||||
, alertClosable :: Bool
|
, alertClosable :: Bool
|
||||||
, alertPriority :: AlertPriority
|
, alertPriority :: AlertPriority
|
||||||
|
|
|
@ -9,15 +9,17 @@ module Assistant.Types.NetMessager where
|
||||||
|
|
||||||
import Common.Annex
|
import Common.Annex
|
||||||
import Assistant.Pairing
|
import Assistant.Pairing
|
||||||
|
import Git.Types
|
||||||
|
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import qualified Data.Set as S
|
||||||
|
import qualified Data.Map as M
|
||||||
|
import qualified Data.DList as D
|
||||||
import Control.Concurrent.STM
|
import Control.Concurrent.STM
|
||||||
import Control.Concurrent.MSampleVar
|
import Control.Concurrent.MSampleVar
|
||||||
import Data.ByteString (ByteString)
|
import Data.ByteString (ByteString)
|
||||||
import qualified Data.ByteString.Char8 as B8
|
import qualified Data.ByteString.Char8 as B8
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import qualified Data.Text as T
|
|
||||||
import qualified Data.Set as S
|
|
||||||
import qualified Data.Map as M
|
|
||||||
|
|
||||||
{- Messages that can be sent out of band by a network messager. -}
|
{- Messages that can be sent out of band by a network messager. -}
|
||||||
data NetMessage
|
data NetMessage
|
||||||
|
@ -37,7 +39,7 @@ type ClientID = Text
|
||||||
|
|
||||||
data PushStage
|
data PushStage
|
||||||
-- indicates that we have data to push over the out of band network
|
-- indicates that we have data to push over the out of band network
|
||||||
= CanPush UUID
|
= CanPush UUID [Sha]
|
||||||
-- request that a git push be sent over the out of band network
|
-- request that a git push be sent over the out of band network
|
||||||
| PushRequest UUID
|
| PushRequest UUID
|
||||||
-- indicates that a push is starting
|
-- indicates that a push is starting
|
||||||
|
@ -58,10 +60,18 @@ type SequenceNum = Int
|
||||||
{- NetMessages that are important (and small), and should be stored to be
|
{- NetMessages that are important (and small), and should be stored to be
|
||||||
- resent when new clients are seen. -}
|
- resent when new clients are seen. -}
|
||||||
isImportantNetMessage :: NetMessage -> Maybe ClientID
|
isImportantNetMessage :: NetMessage -> Maybe ClientID
|
||||||
isImportantNetMessage (Pushing c (CanPush _)) = Just c
|
isImportantNetMessage (Pushing c (CanPush _ _)) = Just c
|
||||||
isImportantNetMessage (Pushing c (PushRequest _)) = Just c
|
isImportantNetMessage (Pushing c (PushRequest _)) = Just c
|
||||||
isImportantNetMessage _ = Nothing
|
isImportantNetMessage _ = Nothing
|
||||||
|
|
||||||
|
{- Checks if two important NetMessages are equivilant.
|
||||||
|
- That is to say, assuming they were sent to the same client,
|
||||||
|
- would it do the same thing for one as for the other? -}
|
||||||
|
equivilantImportantNetMessages :: NetMessage -> NetMessage -> Bool
|
||||||
|
equivilantImportantNetMessages (Pushing _ (CanPush _ _)) (Pushing _ (CanPush _ _)) = True
|
||||||
|
equivilantImportantNetMessages (Pushing _ (PushRequest _)) (Pushing _ (PushRequest _)) = True
|
||||||
|
equivilantImportantNetMessages _ _ = False
|
||||||
|
|
||||||
readdressNetMessage :: NetMessage -> ClientID -> NetMessage
|
readdressNetMessage :: NetMessage -> ClientID -> NetMessage
|
||||||
readdressNetMessage (PairingNotification stage _ uuid) c = PairingNotification stage c uuid
|
readdressNetMessage (PairingNotification stage _ uuid) c = PairingNotification stage c uuid
|
||||||
readdressNetMessage (Pushing _ stage) c = Pushing c stage
|
readdressNetMessage (Pushing _ stage) c = Pushing c stage
|
||||||
|
@ -85,16 +95,19 @@ logClientID c = T.concat [T.take 1 c, T.pack $ show $ T.length c]
|
||||||
|
|
||||||
{- Things that initiate either side of a push, but do not actually send data. -}
|
{- Things that initiate either side of a push, but do not actually send data. -}
|
||||||
isPushInitiation :: PushStage -> Bool
|
isPushInitiation :: PushStage -> Bool
|
||||||
isPushInitiation (CanPush _) = True
|
|
||||||
isPushInitiation (PushRequest _) = True
|
isPushInitiation (PushRequest _) = True
|
||||||
isPushInitiation (StartingPush _) = True
|
isPushInitiation (StartingPush _) = True
|
||||||
isPushInitiation _ = False
|
isPushInitiation _ = False
|
||||||
|
|
||||||
|
isPushNotice :: PushStage -> Bool
|
||||||
|
isPushNotice (CanPush _ _) = True
|
||||||
|
isPushNotice _ = False
|
||||||
|
|
||||||
data PushSide = SendPack | ReceivePack
|
data PushSide = SendPack | ReceivePack
|
||||||
deriving (Eq, Ord)
|
deriving (Eq, Ord, Show)
|
||||||
|
|
||||||
pushDestinationSide :: PushStage -> PushSide
|
pushDestinationSide :: PushStage -> PushSide
|
||||||
pushDestinationSide (CanPush _) = ReceivePack
|
pushDestinationSide (CanPush _ _) = ReceivePack
|
||||||
pushDestinationSide (PushRequest _) = SendPack
|
pushDestinationSide (PushRequest _) = SendPack
|
||||||
pushDestinationSide (StartingPush _) = ReceivePack
|
pushDestinationSide (StartingPush _) = ReceivePack
|
||||||
pushDestinationSide (ReceivePackOutput _ _) = SendPack
|
pushDestinationSide (ReceivePackOutput _ _) = SendPack
|
||||||
|
@ -114,6 +127,8 @@ mkSideMap gen = do
|
||||||
getSide :: PushSide -> SideMap a -> a
|
getSide :: PushSide -> SideMap a -> a
|
||||||
getSide side m = m side
|
getSide side m = m side
|
||||||
|
|
||||||
|
type Inboxes = TVar (M.Map ClientID (Int, D.DList NetMessage))
|
||||||
|
|
||||||
data NetMessager = NetMessager
|
data NetMessager = NetMessager
|
||||||
-- outgoing messages
|
-- outgoing messages
|
||||||
{ netMessages :: TChan NetMessage
|
{ netMessages :: TChan NetMessage
|
||||||
|
@ -123,12 +138,11 @@ data NetMessager = NetMessager
|
||||||
, sentImportantNetMessages :: TMVar (M.Map ClientID (S.Set NetMessage))
|
, sentImportantNetMessages :: TMVar (M.Map ClientID (S.Set NetMessage))
|
||||||
-- write to this to restart the net messager
|
-- write to this to restart the net messager
|
||||||
, netMessagerRestart :: MSampleVar ()
|
, netMessagerRestart :: MSampleVar ()
|
||||||
-- only one side of a push can be running at a time
|
-- queue of incoming messages that request the initiation of pushes
|
||||||
, netMessagerPushRunning :: SideMap (TMVar (Maybe ClientID))
|
, netMessagerPushInitiations :: SideMap (TMVar [NetMessage])
|
||||||
-- incoming messages related to a running push
|
-- incoming messages containing data for a running
|
||||||
, netMessagesPush :: SideMap (TChan NetMessage)
|
-- (or not yet started) push
|
||||||
-- incoming push messages, deferred to be processed later
|
, netMessagerInboxes :: SideMap Inboxes
|
||||||
, netMessagesPushDeferred :: SideMap (TMVar (S.Set NetMessage))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
newNetMessager :: IO NetMessager
|
newNetMessager :: IO NetMessager
|
||||||
|
@ -137,6 +151,5 @@ newNetMessager = NetMessager
|
||||||
<*> atomically (newTMVar M.empty)
|
<*> atomically (newTMVar M.empty)
|
||||||
<*> atomically (newTMVar M.empty)
|
<*> atomically (newTMVar M.empty)
|
||||||
<*> newEmptySV
|
<*> newEmptySV
|
||||||
<*> mkSideMap (newTMVar Nothing)
|
<*> mkSideMap newEmptyTMVar
|
||||||
<*> mkSideMap newTChan
|
<*> mkSideMap (newTVar M.empty)
|
||||||
<*> mkSideMap (newTMVar S.empty)
|
|
||||||
|
|
|
@ -9,7 +9,6 @@ module Assistant.Types.TransferQueue where
|
||||||
|
|
||||||
import Common.Annex
|
import Common.Annex
|
||||||
import Logs.Transfer
|
import Logs.Transfer
|
||||||
import Types.Remote
|
|
||||||
|
|
||||||
import Control.Concurrent.STM
|
import Control.Concurrent.STM
|
||||||
import Utility.TList
|
import Utility.TList
|
||||||
|
|
|
@ -15,19 +15,18 @@ import Assistant.Common
|
||||||
import Utility.NotificationBroadcaster
|
import Utility.NotificationBroadcaster
|
||||||
import Utility.Yesod
|
import Utility.Yesod
|
||||||
|
|
||||||
import Yesod
|
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import Control.Concurrent
|
import Control.Concurrent
|
||||||
import qualified Network.Wai as W
|
import qualified Network.Wai as W
|
||||||
import qualified Data.ByteString.Char8 as S8
|
import qualified Data.ByteString.Char8 as S8
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
|
|
||||||
waitNotifier :: forall sub. (Assistant NotificationBroadcaster) -> NotificationId -> GHandler sub WebApp ()
|
waitNotifier :: Assistant NotificationBroadcaster -> NotificationId -> Handler ()
|
||||||
waitNotifier getbroadcaster nid = liftAssistant $ do
|
waitNotifier getbroadcaster nid = liftAssistant $ do
|
||||||
b <- getbroadcaster
|
b <- getbroadcaster
|
||||||
liftIO $ waitNotification $ notificationHandleFromId b nid
|
liftIO $ waitNotification $ notificationHandleFromId b nid
|
||||||
|
|
||||||
newNotifier :: forall sub. (Assistant NotificationBroadcaster) -> GHandler sub WebApp NotificationId
|
newNotifier :: Assistant NotificationBroadcaster -> Handler NotificationId
|
||||||
newNotifier getbroadcaster = liftAssistant $ do
|
newNotifier getbroadcaster = liftAssistant $ do
|
||||||
b <- getbroadcaster
|
b <- getbroadcaster
|
||||||
liftIO $ notificationHandleToId <$> newNotificationHandle True b
|
liftIO $ notificationHandleToId <$> newNotificationHandle True b
|
||||||
|
@ -36,7 +35,7 @@ newNotifier getbroadcaster = liftAssistant $ do
|
||||||
- every form. -}
|
- every form. -}
|
||||||
webAppFormAuthToken :: Widget
|
webAppFormAuthToken :: Widget
|
||||||
webAppFormAuthToken = do
|
webAppFormAuthToken = do
|
||||||
webapp <- lift getYesod
|
webapp <- liftH getYesod
|
||||||
[whamlet|<input type="hidden" name="auth" value="#{secretToken webapp}">|]
|
[whamlet|<input type="hidden" name="auth" value="#{secretToken webapp}">|]
|
||||||
|
|
||||||
{- A button with an icon, and maybe label or tooltip, that can be
|
{- A button with an icon, and maybe label or tooltip, that can be
|
||||||
|
|
|
@ -12,7 +12,6 @@ import Assistant.WebApp as X
|
||||||
import Assistant.WebApp.Page as X
|
import Assistant.WebApp.Page as X
|
||||||
import Assistant.WebApp.Form as X
|
import Assistant.WebApp.Form as X
|
||||||
import Assistant.WebApp.Types as X
|
import Assistant.WebApp.Types as X
|
||||||
import Utility.Yesod as X
|
import Utility.Yesod as X hiding (textField, passwordField, insertBy, replace, joinPath, deleteBy, delete, insert, Key, Option)
|
||||||
|
|
||||||
import Data.Text as X (Text)
|
import Data.Text as X (Text)
|
||||||
import Yesod as X hiding (textField, passwordField, insertBy, replace, joinPath, deleteBy, delete, insert, Key, Option)
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
- Licensed under the GNU AGPL version 3 or higher.
|
- Licensed under the GNU AGPL version 3 or higher.
|
||||||
-}
|
-}
|
||||||
|
|
||||||
{-# LANGUAGE TypeFamilies, QuasiQuotes, MultiParamTypeClasses, TemplateHaskell, OverloadedStrings, RankNTypes, CPP #-}
|
{-# LANGUAGE QuasiQuotes, TemplateHaskell, OverloadedStrings, CPP #-}
|
||||||
|
|
||||||
module Assistant.WebApp.Configurators where
|
module Assistant.WebApp.Configurators where
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ import Assistant.XMPP.Client
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
{- The main configuration screen. -}
|
{- The main configuration screen. -}
|
||||||
getConfigurationR :: Handler RepHtml
|
getConfigurationR :: Handler Html
|
||||||
getConfigurationR = ifM (inFirstRun)
|
getConfigurationR = ifM (inFirstRun)
|
||||||
( redirect FirstRepositoryR
|
( redirect FirstRepositoryR
|
||||||
, page "Configuration" (Just Configuration) $ do
|
, page "Configuration" (Just Configuration) $ do
|
||||||
|
@ -28,7 +28,7 @@ getConfigurationR = ifM (inFirstRun)
|
||||||
$(widgetFile "configurators/main")
|
$(widgetFile "configurators/main")
|
||||||
)
|
)
|
||||||
|
|
||||||
getAddRepositoryR :: Handler RepHtml
|
getAddRepositoryR :: Handler Html
|
||||||
getAddRepositoryR = page "Add Repository" (Just Configuration) $ do
|
getAddRepositoryR = page "Add Repository" (Just Configuration) $ do
|
||||||
let repolist = repoListDisplay mainRepoSelector
|
let repolist = repoListDisplay mainRepoSelector
|
||||||
$(widgetFile "configurators/addrepository")
|
$(widgetFile "configurators/addrepository")
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
- Licensed under the GNU AGPL version 3 or higher.
|
- Licensed under the GNU AGPL version 3 or higher.
|
||||||
-}
|
-}
|
||||||
|
|
||||||
{-# LANGUAGE CPP, FlexibleContexts, TypeFamilies, QuasiQuotes, MultiParamTypeClasses, TemplateHaskell, OverloadedStrings, RankNTypes #-}
|
{-# LANGUAGE CPP, QuasiQuotes, TemplateHaskell, OverloadedStrings #-}
|
||||||
|
|
||||||
module Assistant.WebApp.Configurators.AWS where
|
module Assistant.WebApp.Configurators.AWS where
|
||||||
|
|
||||||
|
@ -29,10 +29,10 @@ import qualified Data.Text as T
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
import Data.Char
|
import Data.Char
|
||||||
|
|
||||||
awsConfigurator :: Widget -> Handler RepHtml
|
awsConfigurator :: Widget -> Handler Html
|
||||||
awsConfigurator = page "Add an Amazon repository" (Just Configuration)
|
awsConfigurator = page "Add an Amazon repository" (Just Configuration)
|
||||||
|
|
||||||
glacierConfigurator :: Widget -> Handler RepHtml
|
glacierConfigurator :: Widget -> Handler Html
|
||||||
glacierConfigurator a = do
|
glacierConfigurator a = do
|
||||||
ifM (liftIO $ inPath "glacier")
|
ifM (liftIO $ inPath "glacier")
|
||||||
( awsConfigurator a
|
( awsConfigurator a
|
||||||
|
@ -63,7 +63,7 @@ data AWSCreds = AWSCreds Text Text
|
||||||
extractCreds :: AWSInput -> AWSCreds
|
extractCreds :: AWSInput -> AWSCreds
|
||||||
extractCreds i = AWSCreds (accessKeyID i) (secretAccessKey i)
|
extractCreds i = AWSCreds (accessKeyID i) (secretAccessKey i)
|
||||||
|
|
||||||
s3InputAForm :: Maybe CredPair -> AForm WebApp WebApp AWSInput
|
s3InputAForm :: Maybe CredPair -> MkAForm AWSInput
|
||||||
s3InputAForm defcreds = AWSInput
|
s3InputAForm defcreds = AWSInput
|
||||||
<$> accessKeyIDFieldWithHelp (T.pack . fst <$> defcreds)
|
<$> accessKeyIDFieldWithHelp (T.pack . fst <$> defcreds)
|
||||||
<*> secretAccessKeyField (T.pack . snd <$> defcreds)
|
<*> secretAccessKeyField (T.pack . snd <$> defcreds)
|
||||||
|
@ -78,7 +78,7 @@ s3InputAForm defcreds = AWSInput
|
||||||
, ("Reduced redundancy (costs less)", ReducedRedundancy)
|
, ("Reduced redundancy (costs less)", ReducedRedundancy)
|
||||||
]
|
]
|
||||||
|
|
||||||
glacierInputAForm :: Maybe CredPair -> AForm WebApp WebApp AWSInput
|
glacierInputAForm :: Maybe CredPair -> MkAForm AWSInput
|
||||||
glacierInputAForm defcreds = AWSInput
|
glacierInputAForm defcreds = AWSInput
|
||||||
<$> accessKeyIDFieldWithHelp (T.pack . fst <$> defcreds)
|
<$> accessKeyIDFieldWithHelp (T.pack . fst <$> defcreds)
|
||||||
<*> secretAccessKeyField (T.pack . snd <$> defcreds)
|
<*> secretAccessKeyField (T.pack . snd <$> defcreds)
|
||||||
|
@ -87,15 +87,15 @@ glacierInputAForm defcreds = AWSInput
|
||||||
<*> areq textField "Repository name" (Just "glacier")
|
<*> areq textField "Repository name" (Just "glacier")
|
||||||
<*> enableEncryptionField
|
<*> enableEncryptionField
|
||||||
|
|
||||||
awsCredsAForm :: Maybe CredPair -> AForm WebApp WebApp AWSCreds
|
awsCredsAForm :: Maybe CredPair -> MkAForm AWSCreds
|
||||||
awsCredsAForm defcreds = AWSCreds
|
awsCredsAForm defcreds = AWSCreds
|
||||||
<$> accessKeyIDFieldWithHelp (T.pack . fst <$> defcreds)
|
<$> accessKeyIDFieldWithHelp (T.pack . fst <$> defcreds)
|
||||||
<*> secretAccessKeyField (T.pack . snd <$> defcreds)
|
<*> secretAccessKeyField (T.pack . snd <$> defcreds)
|
||||||
|
|
||||||
accessKeyIDField :: Widget -> Maybe Text -> AForm WebApp WebApp Text
|
accessKeyIDField :: Widget -> Maybe Text -> MkAForm Text
|
||||||
accessKeyIDField help def = areq (textField `withNote` help) "Access Key ID" def
|
accessKeyIDField help def = areq (textField `withNote` help) "Access Key ID" def
|
||||||
|
|
||||||
accessKeyIDFieldWithHelp :: Maybe Text -> AForm WebApp WebApp Text
|
accessKeyIDFieldWithHelp :: Maybe Text -> MkAForm Text
|
||||||
accessKeyIDFieldWithHelp def = accessKeyIDField help def
|
accessKeyIDFieldWithHelp def = accessKeyIDField help def
|
||||||
where
|
where
|
||||||
help = [whamlet|
|
help = [whamlet|
|
||||||
|
@ -103,28 +103,28 @@ accessKeyIDFieldWithHelp def = accessKeyIDField help def
|
||||||
Get Amazon access keys
|
Get Amazon access keys
|
||||||
|]
|
|]
|
||||||
|
|
||||||
secretAccessKeyField :: Maybe Text -> AForm WebApp WebApp Text
|
secretAccessKeyField :: Maybe Text -> MkAForm Text
|
||||||
secretAccessKeyField def = areq passwordField "Secret Access Key" def
|
secretAccessKeyField def = areq passwordField "Secret Access Key" def
|
||||||
|
|
||||||
datacenterField :: AWS.Service -> AForm WebApp WebApp Text
|
datacenterField :: AWS.Service -> MkAForm Text
|
||||||
datacenterField service = areq (selectFieldList list) "Datacenter" defregion
|
datacenterField service = areq (selectFieldList list) "Datacenter" defregion
|
||||||
where
|
where
|
||||||
list = M.toList $ AWS.regionMap service
|
list = M.toList $ AWS.regionMap service
|
||||||
defregion = Just $ AWS.defaultRegion service
|
defregion = Just $ AWS.defaultRegion service
|
||||||
|
|
||||||
getAddS3R :: Handler RepHtml
|
getAddS3R :: Handler Html
|
||||||
getAddS3R = postAddS3R
|
getAddS3R = postAddS3R
|
||||||
|
|
||||||
postAddS3R :: Handler RepHtml
|
postAddS3R :: Handler Html
|
||||||
#ifdef WITH_S3
|
#ifdef WITH_S3
|
||||||
postAddS3R = awsConfigurator $ do
|
postAddS3R = awsConfigurator $ do
|
||||||
defcreds <- liftAnnex previouslyUsedAWSCreds
|
defcreds <- liftAnnex previouslyUsedAWSCreds
|
||||||
((result, form), enctype) <- lift $
|
((result, form), enctype) <- liftH $
|
||||||
runFormPost $ renderBootstrap $ s3InputAForm defcreds
|
runFormPost $ renderBootstrap $ s3InputAForm defcreds
|
||||||
case result of
|
case result of
|
||||||
FormSuccess input -> lift $ do
|
FormSuccess input -> liftH $ do
|
||||||
let name = T.unpack $ repoName input
|
let name = T.unpack $ repoName input
|
||||||
makeAWSRemote S3.remote (extractCreds input) name setgroup $ M.fromList
|
makeAWSRemote initSpecialRemote S3.remote (extractCreds input) name setgroup $ M.fromList
|
||||||
[ configureEncryption $ enableEncryption input
|
[ configureEncryption $ enableEncryption input
|
||||||
, ("type", "S3")
|
, ("type", "S3")
|
||||||
, ("datacenter", T.unpack $ datacenter input)
|
, ("datacenter", T.unpack $ datacenter input)
|
||||||
|
@ -138,19 +138,19 @@ postAddS3R = awsConfigurator $ do
|
||||||
postAddS3R = error "S3 not supported by this build"
|
postAddS3R = error "S3 not supported by this build"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
getAddGlacierR :: Handler RepHtml
|
getAddGlacierR :: Handler Html
|
||||||
getAddGlacierR = postAddGlacierR
|
getAddGlacierR = postAddGlacierR
|
||||||
|
|
||||||
postAddGlacierR :: Handler RepHtml
|
postAddGlacierR :: Handler Html
|
||||||
#ifdef WITH_S3
|
#ifdef WITH_S3
|
||||||
postAddGlacierR = glacierConfigurator $ do
|
postAddGlacierR = glacierConfigurator $ do
|
||||||
defcreds <- liftAnnex previouslyUsedAWSCreds
|
defcreds <- liftAnnex previouslyUsedAWSCreds
|
||||||
((result, form), enctype) <- lift $
|
((result, form), enctype) <- liftH $
|
||||||
runFormPost $ renderBootstrap $ glacierInputAForm defcreds
|
runFormPost $ renderBootstrap $ glacierInputAForm defcreds
|
||||||
case result of
|
case result of
|
||||||
FormSuccess input -> lift $ do
|
FormSuccess input -> liftH $ do
|
||||||
let name = T.unpack $ repoName input
|
let name = T.unpack $ repoName input
|
||||||
makeAWSRemote Glacier.remote (extractCreds input) name setgroup $ M.fromList
|
makeAWSRemote initSpecialRemote Glacier.remote (extractCreds input) name setgroup $ M.fromList
|
||||||
[ configureEncryption $ enableEncryption input
|
[ configureEncryption $ enableEncryption input
|
||||||
, ("type", "glacier")
|
, ("type", "glacier")
|
||||||
, ("datacenter", T.unpack $ datacenter input)
|
, ("datacenter", T.unpack $ datacenter input)
|
||||||
|
@ -163,7 +163,7 @@ postAddGlacierR = glacierConfigurator $ do
|
||||||
postAddGlacierR = error "S3 not supported by this build"
|
postAddGlacierR = error "S3 not supported by this build"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
getEnableS3R :: UUID -> Handler RepHtml
|
getEnableS3R :: UUID -> Handler Html
|
||||||
#ifdef WITH_S3
|
#ifdef WITH_S3
|
||||||
getEnableS3R uuid = do
|
getEnableS3R uuid = do
|
||||||
m <- liftAnnex readRemoteLog
|
m <- liftAnnex readRemoteLog
|
||||||
|
@ -174,31 +174,31 @@ getEnableS3R uuid = do
|
||||||
getEnableS3R = postEnableS3R
|
getEnableS3R = postEnableS3R
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
postEnableS3R :: UUID -> Handler RepHtml
|
postEnableS3R :: UUID -> Handler Html
|
||||||
#ifdef WITH_S3
|
#ifdef WITH_S3
|
||||||
postEnableS3R uuid = awsConfigurator $ enableAWSRemote S3.remote uuid
|
postEnableS3R uuid = awsConfigurator $ enableAWSRemote S3.remote uuid
|
||||||
#else
|
#else
|
||||||
postEnableS3R _ = error "S3 not supported by this build"
|
postEnableS3R _ = error "S3 not supported by this build"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
getEnableGlacierR :: UUID -> Handler RepHtml
|
getEnableGlacierR :: UUID -> Handler Html
|
||||||
getEnableGlacierR = postEnableGlacierR
|
getEnableGlacierR = postEnableGlacierR
|
||||||
|
|
||||||
postEnableGlacierR :: UUID -> Handler RepHtml
|
postEnableGlacierR :: UUID -> Handler Html
|
||||||
postEnableGlacierR = glacierConfigurator . enableAWSRemote Glacier.remote
|
postEnableGlacierR = glacierConfigurator . enableAWSRemote Glacier.remote
|
||||||
|
|
||||||
enableAWSRemote :: RemoteType -> UUID -> Widget
|
enableAWSRemote :: RemoteType -> UUID -> Widget
|
||||||
#ifdef WITH_S3
|
#ifdef WITH_S3
|
||||||
enableAWSRemote remotetype uuid = do
|
enableAWSRemote remotetype uuid = do
|
||||||
defcreds <- liftAnnex previouslyUsedAWSCreds
|
defcreds <- liftAnnex previouslyUsedAWSCreds
|
||||||
((result, form), enctype) <- lift $
|
((result, form), enctype) <- liftH $
|
||||||
runFormPost $ renderBootstrap $ awsCredsAForm defcreds
|
runFormPost $ renderBootstrap $ awsCredsAForm defcreds
|
||||||
case result of
|
case result of
|
||||||
FormSuccess creds -> lift $ do
|
FormSuccess creds -> liftH $ do
|
||||||
m <- liftAnnex readRemoteLog
|
m <- liftAnnex readRemoteLog
|
||||||
let name = fromJust $ M.lookup "name" $
|
let name = fromJust $ M.lookup "name" $
|
||||||
fromJust $ M.lookup uuid m
|
fromJust $ M.lookup uuid m
|
||||||
makeAWSRemote remotetype creds name (const noop) M.empty
|
makeAWSRemote enableSpecialRemote remotetype creds name (const noop) M.empty
|
||||||
_ -> do
|
_ -> do
|
||||||
description <- liftAnnex $
|
description <- liftAnnex $
|
||||||
T.pack <$> Remote.prettyUUID uuid
|
T.pack <$> Remote.prettyUUID uuid
|
||||||
|
@ -207,13 +207,11 @@ enableAWSRemote remotetype uuid = do
|
||||||
enableAWSRemote _ _ = error "S3 not supported by this build"
|
enableAWSRemote _ _ = error "S3 not supported by this build"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
makeAWSRemote :: RemoteType -> AWSCreds -> String -> (Remote -> Handler ()) -> RemoteConfig -> Handler ()
|
makeAWSRemote :: SpecialRemoteMaker -> RemoteType -> AWSCreds -> String -> (Remote -> Handler ()) -> RemoteConfig -> Handler ()
|
||||||
makeAWSRemote remotetype (AWSCreds ak sk) name setup config = do
|
makeAWSRemote maker remotetype (AWSCreds ak sk) name setup config = do
|
||||||
remotename <- liftAnnex $ fromRepo $ uniqueRemoteName name 0
|
|
||||||
liftIO $ AWS.setCredsEnv (T.unpack ak, T.unpack sk)
|
liftIO $ AWS.setCredsEnv (T.unpack ak, T.unpack sk)
|
||||||
r <- liftAnnex $ addRemote $ do
|
r <- liftAnnex $ addRemote $ do
|
||||||
makeSpecialRemote hostname remotetype config
|
maker hostname remotetype config
|
||||||
return remotename
|
|
||||||
setup r
|
setup r
|
||||||
liftAssistant $ syncRemote r
|
liftAssistant $ syncRemote r
|
||||||
redirect $ EditNewCloudRepositoryR $ Remote.uuid r
|
redirect $ EditNewCloudRepositoryR $ Remote.uuid r
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
- Licensed under the GNU AGPL version 3 or higher.
|
- Licensed under the GNU AGPL version 3 or higher.
|
||||||
-}
|
-}
|
||||||
|
|
||||||
{-# LANGUAGE TypeFamilies, QuasiQuotes, MultiParamTypeClasses, TemplateHaskell, OverloadedStrings, RankNTypes #-}
|
{-# LANGUAGE QuasiQuotes, TemplateHaskell, OverloadedStrings #-}
|
||||||
|
|
||||||
module Assistant.WebApp.Configurators.Delete where
|
module Assistant.WebApp.Configurators.Delete where
|
||||||
|
|
||||||
|
@ -28,24 +28,24 @@ import qualified Data.Text as T
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
import System.Path
|
import System.Path
|
||||||
|
|
||||||
notCurrentRepo :: UUID -> Handler RepHtml -> Handler RepHtml
|
notCurrentRepo :: UUID -> Handler Html -> Handler Html
|
||||||
notCurrentRepo uuid a = go =<< liftAnnex (Remote.remoteFromUUID uuid)
|
notCurrentRepo uuid a = go =<< liftAnnex (Remote.remoteFromUUID uuid)
|
||||||
where
|
where
|
||||||
go Nothing = redirect DeleteCurrentRepositoryR
|
go Nothing = redirect DeleteCurrentRepositoryR
|
||||||
go (Just _) = a
|
go (Just _) = a
|
||||||
|
|
||||||
getDisableRepositoryR :: UUID -> Handler RepHtml
|
getDisableRepositoryR :: UUID -> Handler Html
|
||||||
getDisableRepositoryR uuid = notCurrentRepo uuid $ do
|
getDisableRepositoryR uuid = notCurrentRepo uuid $ do
|
||||||
void $ liftAssistant $ disableRemote uuid
|
void $ liftAssistant $ disableRemote uuid
|
||||||
redirect DashboardR
|
redirect DashboardR
|
||||||
|
|
||||||
getDeleteRepositoryR :: UUID -> Handler RepHtml
|
getDeleteRepositoryR :: UUID -> Handler Html
|
||||||
getDeleteRepositoryR uuid = notCurrentRepo uuid $
|
getDeleteRepositoryR uuid = notCurrentRepo uuid $
|
||||||
deletionPage $ do
|
deletionPage $ do
|
||||||
reponame <- liftAnnex $ Remote.prettyUUID uuid
|
reponame <- liftAnnex $ Remote.prettyUUID uuid
|
||||||
$(widgetFile "configurators/delete/start")
|
$(widgetFile "configurators/delete/start")
|
||||||
|
|
||||||
getStartDeleteRepositoryR :: UUID -> Handler RepHtml
|
getStartDeleteRepositoryR :: UUID -> Handler Html
|
||||||
getStartDeleteRepositoryR uuid = do
|
getStartDeleteRepositoryR uuid = do
|
||||||
remote <- fromMaybe (error "unknown remote")
|
remote <- fromMaybe (error "unknown remote")
|
||||||
<$> liftAnnex (Remote.remoteFromUUID uuid)
|
<$> liftAnnex (Remote.remoteFromUUID uuid)
|
||||||
|
@ -55,7 +55,7 @@ getStartDeleteRepositoryR uuid = do
|
||||||
liftAssistant $ addScanRemotes True [remote]
|
liftAssistant $ addScanRemotes True [remote]
|
||||||
redirect DashboardR
|
redirect DashboardR
|
||||||
|
|
||||||
getFinishDeleteRepositoryR :: UUID -> Handler RepHtml
|
getFinishDeleteRepositoryR :: UUID -> Handler Html
|
||||||
getFinishDeleteRepositoryR uuid = deletionPage $ do
|
getFinishDeleteRepositoryR uuid = deletionPage $ do
|
||||||
void $ liftAssistant $ removeRemote uuid
|
void $ liftAssistant $ removeRemote uuid
|
||||||
|
|
||||||
|
@ -64,22 +64,22 @@ getFinishDeleteRepositoryR uuid = deletionPage $ do
|
||||||
gitrepo <- liftAnnex $ M.notMember uuid <$> readRemoteLog
|
gitrepo <- liftAnnex $ M.notMember uuid <$> readRemoteLog
|
||||||
$(widgetFile "configurators/delete/finished")
|
$(widgetFile "configurators/delete/finished")
|
||||||
|
|
||||||
getDeleteCurrentRepositoryR :: Handler RepHtml
|
getDeleteCurrentRepositoryR :: Handler Html
|
||||||
getDeleteCurrentRepositoryR = deleteCurrentRepository
|
getDeleteCurrentRepositoryR = deleteCurrentRepository
|
||||||
|
|
||||||
postDeleteCurrentRepositoryR :: Handler RepHtml
|
postDeleteCurrentRepositoryR :: Handler Html
|
||||||
postDeleteCurrentRepositoryR = deleteCurrentRepository
|
postDeleteCurrentRepositoryR = deleteCurrentRepository
|
||||||
|
|
||||||
deleteCurrentRepository :: Handler RepHtml
|
deleteCurrentRepository :: Handler Html
|
||||||
deleteCurrentRepository = dangerPage $ do
|
deleteCurrentRepository = dangerPage $ do
|
||||||
reldir <- fromJust . relDir <$> lift getYesod
|
reldir <- fromJust . relDir <$> liftH getYesod
|
||||||
havegitremotes <- haveremotes syncGitRemotes
|
havegitremotes <- haveremotes syncGitRemotes
|
||||||
havedataremotes <- haveremotes syncDataRemotes
|
havedataremotes <- haveremotes syncDataRemotes
|
||||||
((result, form), enctype) <- lift $
|
((result, form), enctype) <- liftH $
|
||||||
runFormPost $ renderBootstrap $ sanityVerifierAForm $
|
runFormPost $ renderBootstrap $ sanityVerifierAForm $
|
||||||
SanityVerifier magicphrase
|
SanityVerifier magicphrase
|
||||||
case result of
|
case result of
|
||||||
FormSuccess _ -> lift $ do
|
FormSuccess _ -> liftH $ do
|
||||||
dir <- liftAnnex $ fromRepo Git.repoPath
|
dir <- liftAnnex $ fromRepo Git.repoPath
|
||||||
liftIO $ removeAutoStartFile dir
|
liftIO $ removeAutoStartFile dir
|
||||||
|
|
||||||
|
@ -107,7 +107,7 @@ deleteCurrentRepository = dangerPage $ do
|
||||||
data SanityVerifier = SanityVerifier T.Text
|
data SanityVerifier = SanityVerifier T.Text
|
||||||
deriving (Eq)
|
deriving (Eq)
|
||||||
|
|
||||||
sanityVerifierAForm :: SanityVerifier -> AForm WebApp WebApp SanityVerifier
|
sanityVerifierAForm :: SanityVerifier -> MkAForm SanityVerifier
|
||||||
sanityVerifierAForm template = SanityVerifier
|
sanityVerifierAForm template = SanityVerifier
|
||||||
<$> areq checksanity "Confirm deletion?" Nothing
|
<$> areq checksanity "Confirm deletion?" Nothing
|
||||||
where
|
where
|
||||||
|
@ -116,10 +116,10 @@ sanityVerifierAForm template = SanityVerifier
|
||||||
|
|
||||||
insane = "Maybe this is not a good idea..." :: Text
|
insane = "Maybe this is not a good idea..." :: Text
|
||||||
|
|
||||||
deletionPage :: Widget -> Handler RepHtml
|
deletionPage :: Widget -> Handler Html
|
||||||
deletionPage = page "Delete repository" (Just Configuration)
|
deletionPage = page "Delete repository" (Just Configuration)
|
||||||
|
|
||||||
dangerPage :: Widget -> Handler RepHtml
|
dangerPage :: Widget -> Handler Html
|
||||||
dangerPage = page "Danger danger danger" (Just Configuration)
|
dangerPage = page "Danger danger danger" (Just Configuration)
|
||||||
|
|
||||||
magicphrase :: Text
|
magicphrase :: Text
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
- Licensed under the GNU AGPL version 3 or higher.
|
- Licensed under the GNU AGPL version 3 or higher.
|
||||||
-}
|
-}
|
||||||
|
|
||||||
{-# LANGUAGE CPP, TypeFamilies, QuasiQuotes, MultiParamTypeClasses, TemplateHaskell, OverloadedStrings, RankNTypes #-}
|
{-# LANGUAGE CPP, QuasiQuotes, TemplateHaskell, OverloadedStrings #-}
|
||||||
|
|
||||||
module Assistant.WebApp.Configurators.Edit where
|
module Assistant.WebApp.Configurators.Edit where
|
||||||
|
|
||||||
|
@ -132,9 +132,10 @@ setRepoConfig uuid mremote oldc newc = do
|
||||||
|
|
||||||
legalName = makeLegalName . T.unpack . repoName
|
legalName = makeLegalName . T.unpack . repoName
|
||||||
|
|
||||||
editRepositoryAForm :: RepoConfig -> AForm WebApp WebApp RepoConfig
|
editRepositoryAForm :: Bool -> RepoConfig -> MkAForm RepoConfig
|
||||||
editRepositoryAForm def = RepoConfig
|
editRepositoryAForm ishere def = RepoConfig
|
||||||
<$> areq textField "Name" (Just $ repoName def)
|
<$> areq (if ishere then readonlyTextField else textField)
|
||||||
|
"Name" (Just $ repoName def)
|
||||||
<*> aopt textField "Description" (Just $ repoDescription def)
|
<*> aopt textField "Description" (Just $ repoDescription def)
|
||||||
<*> areq (selectFieldList groups `withNote` help) "Repository group" (Just $ repoGroup def)
|
<*> areq (selectFieldList groups `withNote` help) "Repository group" (Just $ repoGroup def)
|
||||||
<*> associateddirectory
|
<*> associateddirectory
|
||||||
|
@ -154,33 +155,33 @@ editRepositoryAForm def = RepoConfig
|
||||||
Nothing -> aopt hiddenField "" Nothing
|
Nothing -> aopt hiddenField "" Nothing
|
||||||
Just d -> aopt textField "Associated directory" (Just $ Just d)
|
Just d -> aopt textField "Associated directory" (Just $ Just d)
|
||||||
|
|
||||||
getEditRepositoryR :: UUID -> Handler RepHtml
|
getEditRepositoryR :: UUID -> Handler Html
|
||||||
getEditRepositoryR = postEditRepositoryR
|
getEditRepositoryR = postEditRepositoryR
|
||||||
|
|
||||||
postEditRepositoryR :: UUID -> Handler RepHtml
|
postEditRepositoryR :: UUID -> Handler Html
|
||||||
postEditRepositoryR = editForm False
|
postEditRepositoryR = editForm False
|
||||||
|
|
||||||
getEditNewRepositoryR :: UUID -> Handler RepHtml
|
getEditNewRepositoryR :: UUID -> Handler Html
|
||||||
getEditNewRepositoryR = postEditNewRepositoryR
|
getEditNewRepositoryR = postEditNewRepositoryR
|
||||||
|
|
||||||
postEditNewRepositoryR :: UUID -> Handler RepHtml
|
postEditNewRepositoryR :: UUID -> Handler Html
|
||||||
postEditNewRepositoryR = editForm True
|
postEditNewRepositoryR = editForm True
|
||||||
|
|
||||||
getEditNewCloudRepositoryR :: UUID -> Handler RepHtml
|
getEditNewCloudRepositoryR :: UUID -> Handler Html
|
||||||
getEditNewCloudRepositoryR = postEditNewCloudRepositoryR
|
getEditNewCloudRepositoryR = postEditNewCloudRepositoryR
|
||||||
|
|
||||||
postEditNewCloudRepositoryR :: UUID -> Handler RepHtml
|
postEditNewCloudRepositoryR :: UUID -> Handler Html
|
||||||
postEditNewCloudRepositoryR uuid = xmppNeeded >> editForm True uuid
|
postEditNewCloudRepositoryR uuid = xmppNeeded >> editForm True uuid
|
||||||
|
|
||||||
editForm :: Bool -> UUID -> Handler RepHtml
|
editForm :: Bool -> UUID -> Handler Html
|
||||||
editForm new uuid = page "Configure repository" (Just Configuration) $ do
|
editForm new uuid = page "Edit repository" (Just Configuration) $ do
|
||||||
mremote <- liftAnnex $ Remote.remoteFromUUID uuid
|
mremote <- liftAnnex $ Remote.remoteFromUUID uuid
|
||||||
curr <- liftAnnex $ getRepoConfig uuid mremote
|
curr <- liftAnnex $ getRepoConfig uuid mremote
|
||||||
liftAnnex $ checkAssociatedDirectory curr mremote
|
liftAnnex $ checkAssociatedDirectory curr mremote
|
||||||
((result, form), enctype) <- lift $
|
((result, form), enctype) <- liftH $
|
||||||
runFormPost $ renderBootstrap $ editRepositoryAForm curr
|
runFormPost $ renderBootstrap $ editRepositoryAForm (isNothing mremote) curr
|
||||||
case result of
|
case result of
|
||||||
FormSuccess input -> lift $ do
|
FormSuccess input -> liftH $ do
|
||||||
setRepoConfig uuid mremote curr input
|
setRepoConfig uuid mremote curr input
|
||||||
liftAnnex $ checkAssociatedDirectory input mremote
|
liftAnnex $ checkAssociatedDirectory input mremote
|
||||||
redirect DashboardR
|
redirect DashboardR
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
- Licensed under the GNU AGPL version 3 or higher.
|
- Licensed under the GNU AGPL version 3 or higher.
|
||||||
-}
|
-}
|
||||||
|
|
||||||
{-# LANGUAGE CPP, FlexibleContexts, TypeFamilies, QuasiQuotes, MultiParamTypeClasses, TemplateHaskell, OverloadedStrings, RankNTypes #-}
|
{-# LANGUAGE CPP, QuasiQuotes, TemplateHaskell, OverloadedStrings #-}
|
||||||
|
|
||||||
module Assistant.WebApp.Configurators.IA where
|
module Assistant.WebApp.Configurators.IA where
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ import qualified Data.Map as M
|
||||||
import Data.Char
|
import Data.Char
|
||||||
import Network.URI
|
import Network.URI
|
||||||
|
|
||||||
iaConfigurator :: Widget -> Handler RepHtml
|
iaConfigurator :: Widget -> Handler Html
|
||||||
iaConfigurator = page "Add an Internet Archive repository" (Just Configuration)
|
iaConfigurator = page "Add an Internet Archive repository" (Just Configuration)
|
||||||
|
|
||||||
data IAInput = IAInput
|
data IAInput = IAInput
|
||||||
|
@ -79,7 +79,7 @@ showMediaType MediaVideo = "videos & movies"
|
||||||
showMediaType MediaAudio = "audio & music"
|
showMediaType MediaAudio = "audio & music"
|
||||||
showMediaType MediaOmitted = "other"
|
showMediaType MediaOmitted = "other"
|
||||||
|
|
||||||
iaInputAForm :: Maybe CredPair -> AForm WebApp WebApp IAInput
|
iaInputAForm :: Maybe CredPair -> MkAForm IAInput
|
||||||
iaInputAForm defcreds = IAInput
|
iaInputAForm defcreds = IAInput
|
||||||
<$> accessKeyIDFieldWithHelp (T.pack . fst <$> defcreds)
|
<$> accessKeyIDFieldWithHelp (T.pack . fst <$> defcreds)
|
||||||
<*> AWS.secretAccessKeyField (T.pack . snd <$> defcreds)
|
<*> AWS.secretAccessKeyField (T.pack . snd <$> defcreds)
|
||||||
|
@ -99,7 +99,7 @@ itemNameHelp = [whamlet|
|
||||||
will be uploaded to your Internet Archive item.
|
will be uploaded to your Internet Archive item.
|
||||||
|]
|
|]
|
||||||
|
|
||||||
iaCredsAForm :: Maybe CredPair -> AForm WebApp WebApp AWS.AWSCreds
|
iaCredsAForm :: Maybe CredPair -> MkAForm AWS.AWSCreds
|
||||||
iaCredsAForm defcreds = AWS.AWSCreds
|
iaCredsAForm defcreds = AWS.AWSCreds
|
||||||
<$> accessKeyIDFieldWithHelp (T.pack . fst <$> defcreds)
|
<$> accessKeyIDFieldWithHelp (T.pack . fst <$> defcreds)
|
||||||
<*> AWS.secretAccessKeyField (T.pack . snd <$> defcreds)
|
<*> AWS.secretAccessKeyField (T.pack . snd <$> defcreds)
|
||||||
|
@ -110,7 +110,7 @@ previouslyUsedIACreds = previouslyUsedCredPair AWS.creds S3.remote $
|
||||||
AWS.isIARemoteConfig . Remote.config
|
AWS.isIARemoteConfig . Remote.config
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
accessKeyIDFieldWithHelp :: Maybe Text -> AForm WebApp WebApp Text
|
accessKeyIDFieldWithHelp :: Maybe Text -> MkAForm Text
|
||||||
accessKeyIDFieldWithHelp def = AWS.accessKeyIDField help def
|
accessKeyIDFieldWithHelp def = AWS.accessKeyIDField help def
|
||||||
where
|
where
|
||||||
help = [whamlet|
|
help = [whamlet|
|
||||||
|
@ -118,19 +118,19 @@ accessKeyIDFieldWithHelp def = AWS.accessKeyIDField help def
|
||||||
Get Internet Archive access keys
|
Get Internet Archive access keys
|
||||||
|]
|
|]
|
||||||
|
|
||||||
getAddIAR :: Handler RepHtml
|
getAddIAR :: Handler Html
|
||||||
getAddIAR = postAddIAR
|
getAddIAR = postAddIAR
|
||||||
|
|
||||||
postAddIAR :: Handler RepHtml
|
postAddIAR :: Handler Html
|
||||||
#ifdef WITH_S3
|
#ifdef WITH_S3
|
||||||
postAddIAR = iaConfigurator $ do
|
postAddIAR = iaConfigurator $ do
|
||||||
defcreds <- liftAnnex previouslyUsedIACreds
|
defcreds <- liftAnnex previouslyUsedIACreds
|
||||||
((result, form), enctype) <- lift $
|
((result, form), enctype) <- liftH $
|
||||||
runFormPost $ renderBootstrap $ iaInputAForm defcreds
|
runFormPost $ renderBootstrap $ iaInputAForm defcreds
|
||||||
case result of
|
case result of
|
||||||
FormSuccess input -> lift $ do
|
FormSuccess input -> liftH $ do
|
||||||
let name = escapeBucket $ T.unpack $ itemName input
|
let name = escapeBucket $ T.unpack $ itemName input
|
||||||
AWS.makeAWSRemote S3.remote (extractCreds input) name setgroup $
|
AWS.makeAWSRemote initSpecialRemote S3.remote (extractCreds input) name setgroup $
|
||||||
M.fromList $ catMaybes
|
M.fromList $ catMaybes
|
||||||
[ Just $ configureEncryption NoEncryption
|
[ Just $ configureEncryption NoEncryption
|
||||||
, Just ("type", "S3")
|
, Just ("type", "S3")
|
||||||
|
@ -153,10 +153,10 @@ postAddIAR = iaConfigurator $ do
|
||||||
postAddIAR = error "S3 not supported by this build"
|
postAddIAR = error "S3 not supported by this build"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
getEnableIAR :: UUID -> Handler RepHtml
|
getEnableIAR :: UUID -> Handler Html
|
||||||
getEnableIAR = postEnableIAR
|
getEnableIAR = postEnableIAR
|
||||||
|
|
||||||
postEnableIAR :: UUID -> Handler RepHtml
|
postEnableIAR :: UUID -> Handler Html
|
||||||
#ifdef WITH_S3
|
#ifdef WITH_S3
|
||||||
postEnableIAR = iaConfigurator . enableIARemote
|
postEnableIAR = iaConfigurator . enableIARemote
|
||||||
#else
|
#else
|
||||||
|
@ -167,14 +167,14 @@ postEnableIAR _ = error "S3 not supported by this build"
|
||||||
enableIARemote :: UUID -> Widget
|
enableIARemote :: UUID -> Widget
|
||||||
enableIARemote uuid = do
|
enableIARemote uuid = do
|
||||||
defcreds <- liftAnnex previouslyUsedIACreds
|
defcreds <- liftAnnex previouslyUsedIACreds
|
||||||
((result, form), enctype) <- lift $
|
((result, form), enctype) <- liftH $
|
||||||
runFormPost $ renderBootstrap $ iaCredsAForm defcreds
|
runFormPost $ renderBootstrap $ iaCredsAForm defcreds
|
||||||
case result of
|
case result of
|
||||||
FormSuccess creds -> lift $ do
|
FormSuccess creds -> liftH $ do
|
||||||
m <- liftAnnex readRemoteLog
|
m <- liftAnnex readRemoteLog
|
||||||
let name = fromJust $ M.lookup "name" $
|
let name = fromJust $ M.lookup "name" $
|
||||||
fromJust $ M.lookup uuid m
|
fromJust $ M.lookup uuid m
|
||||||
AWS.makeAWSRemote S3.remote creds name (const noop) M.empty
|
AWS.makeAWSRemote enableSpecialRemote S3.remote creds name (const noop) M.empty
|
||||||
_ -> do
|
_ -> do
|
||||||
description <- liftAnnex $
|
description <- liftAnnex $
|
||||||
T.pack <$> Remote.prettyUUID uuid
|
T.pack <$> Remote.prettyUUID uuid
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
- Licensed under the GNU AGPL version 3 or higher.
|
- Licensed under the GNU AGPL version 3 or higher.
|
||||||
-}
|
-}
|
||||||
|
|
||||||
{-# LANGUAGE CPP, TypeFamilies, QuasiQuotes, MultiParamTypeClasses, TemplateHaskell, OverloadedStrings, RankNTypes #-}
|
{-# LANGUAGE CPP, QuasiQuotes, TemplateHaskell, OverloadedStrings, RankNTypes, KindSignatures, TypeFamilies #-}
|
||||||
|
|
||||||
module Assistant.WebApp.Configurators.Local where
|
module Assistant.WebApp.Configurators.Local where
|
||||||
|
|
||||||
|
@ -38,6 +38,7 @@ import Config
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
import Data.Char
|
import Data.Char
|
||||||
|
import qualified Text.Hamlet as Hamlet
|
||||||
|
|
||||||
data RepositoryPath = RepositoryPath Text
|
data RepositoryPath = RepositoryPath Text
|
||||||
deriving Show
|
deriving Show
|
||||||
|
@ -46,7 +47,11 @@ data RepositoryPath = RepositoryPath Text
|
||||||
-
|
-
|
||||||
- Validates that the path entered is not empty, and is a safe value
|
- Validates that the path entered is not empty, and is a safe value
|
||||||
- to use as a repository. -}
|
- to use as a repository. -}
|
||||||
|
#if MIN_VERSION_yesod(1,2,0)
|
||||||
|
repositoryPathField :: forall (m :: * -> *). (MonadIO m, HandlerSite m ~ WebApp) => Bool -> Field m Text
|
||||||
|
#else
|
||||||
repositoryPathField :: forall sub. Bool -> Field sub WebApp Text
|
repositoryPathField :: forall sub. Bool -> Field sub WebApp Text
|
||||||
|
#endif
|
||||||
repositoryPathField autofocus = Field
|
repositoryPathField autofocus = Field
|
||||||
#if ! MIN_VERSION_yesod_form(1,2,0)
|
#if ! MIN_VERSION_yesod_form(1,2,0)
|
||||||
{ fieldParse = parse
|
{ fieldParse = parse
|
||||||
|
@ -119,7 +124,7 @@ defaultRepositoryPath firstrun = do
|
||||||
)
|
)
|
||||||
legit d = not <$> doesFileExist (d </> "git-annex")
|
legit d = not <$> doesFileExist (d </> "git-annex")
|
||||||
|
|
||||||
newRepositoryForm :: FilePath -> Form RepositoryPath
|
newRepositoryForm :: FilePath -> Hamlet.Html -> MkMForm RepositoryPath
|
||||||
newRepositoryForm defpath msg = do
|
newRepositoryForm defpath msg = do
|
||||||
(pathRes, pathView) <- mreq (repositoryPathField True) ""
|
(pathRes, pathView) <- mreq (repositoryPathField True) ""
|
||||||
(Just $ T.pack $ addTrailingPathSeparator defpath)
|
(Just $ T.pack $ addTrailingPathSeparator defpath)
|
||||||
|
@ -133,40 +138,47 @@ newRepositoryForm defpath msg = do
|
||||||
return (RepositoryPath <$> pathRes, form)
|
return (RepositoryPath <$> pathRes, form)
|
||||||
|
|
||||||
{- Making the first repository, when starting the webapp for the first time. -}
|
{- Making the first repository, when starting the webapp for the first time. -}
|
||||||
getFirstRepositoryR :: Handler RepHtml
|
getFirstRepositoryR :: Handler Html
|
||||||
getFirstRepositoryR = postFirstRepositoryR
|
getFirstRepositoryR = postFirstRepositoryR
|
||||||
postFirstRepositoryR :: Handler RepHtml
|
postFirstRepositoryR :: Handler Html
|
||||||
postFirstRepositoryR = page "Getting started" (Just Configuration) $ do
|
postFirstRepositoryR = page "Getting started" (Just Configuration) $ do
|
||||||
#ifdef __ANDROID__
|
#ifdef __ANDROID__
|
||||||
androidspecial <- liftIO $ doesDirectoryExist "/sdcard/DCIM"
|
androidspecial <- liftIO $ doesDirectoryExist "/sdcard/DCIM"
|
||||||
let path = "/sdcard/annex"
|
let path = "/sdcard/annex"
|
||||||
#else
|
#else
|
||||||
let androidspecial = False
|
let androidspecial = False
|
||||||
path <- liftIO . defaultRepositoryPath =<< lift inFirstRun
|
path <- liftIO . defaultRepositoryPath =<< liftH inFirstRun
|
||||||
#endif
|
#endif
|
||||||
((res, form), enctype) <- lift $ runFormPost $ newRepositoryForm path
|
((res, form), enctype) <- liftH $ runFormPost $ newRepositoryForm path
|
||||||
case res of
|
case res of
|
||||||
FormSuccess (RepositoryPath p) -> lift $
|
FormSuccess (RepositoryPath p) -> liftH $
|
||||||
startFullAssistant (T.unpack p) ClientGroup
|
startFullAssistant (T.unpack p) ClientGroup Nothing
|
||||||
_ -> $(widgetFile "configurators/newrepository/first")
|
_ -> $(widgetFile "configurators/newrepository/first")
|
||||||
|
|
||||||
getAndroidCameraRepositoryR :: Handler ()
|
getAndroidCameraRepositoryR :: Handler ()
|
||||||
getAndroidCameraRepositoryR = startFullAssistant "/sdcard/DCIM" SourceGroup
|
getAndroidCameraRepositoryR =
|
||||||
|
startFullAssistant "/sdcard/DCIM" SourceGroup $ Just addignore
|
||||||
|
where
|
||||||
|
addignore = do
|
||||||
|
liftIO $ unlessM (doesFileExist ".gitignore") $
|
||||||
|
writeFile ".gitignore" ".thumbnails/*"
|
||||||
|
void $ inRepo $
|
||||||
|
Git.Command.runBool [Param "add", File ".gitignore"]
|
||||||
|
|
||||||
{- Adding a new local repository, which may be entirely separate, or may
|
{- Adding a new local repository, which may be entirely separate, or may
|
||||||
- be connected to the current repository. -}
|
- be connected to the current repository. -}
|
||||||
getNewRepositoryR :: Handler RepHtml
|
getNewRepositoryR :: Handler Html
|
||||||
getNewRepositoryR = postNewRepositoryR
|
getNewRepositoryR = postNewRepositoryR
|
||||||
postNewRepositoryR :: Handler RepHtml
|
postNewRepositoryR :: Handler Html
|
||||||
postNewRepositoryR = page "Add another repository" (Just Configuration) $ do
|
postNewRepositoryR = page "Add another repository" (Just Configuration) $ do
|
||||||
home <- liftIO myHomeDir
|
home <- liftIO myHomeDir
|
||||||
((res, form), enctype) <- lift $ runFormPost $ newRepositoryForm home
|
((res, form), enctype) <- liftH $ runFormPost $ newRepositoryForm home
|
||||||
case res of
|
case res of
|
||||||
FormSuccess (RepositoryPath p) -> do
|
FormSuccess (RepositoryPath p) -> do
|
||||||
let path = T.unpack p
|
let path = T.unpack p
|
||||||
isnew <- liftIO $ makeRepo path False
|
isnew <- liftIO $ makeRepo path False
|
||||||
u <- liftIO $ initRepo isnew True path Nothing
|
u <- liftIO $ initRepo isnew True path Nothing
|
||||||
lift $ liftAnnexOr () $ setStandardGroup u ClientGroup
|
liftH $ liftAnnexOr () $ setStandardGroup u ClientGroup
|
||||||
liftIO $ addAutoStartFile path
|
liftIO $ addAutoStartFile path
|
||||||
liftIO $ startAssistant path
|
liftIO $ startAssistant path
|
||||||
askcombine u path
|
askcombine u path
|
||||||
|
@ -174,10 +186,10 @@ postNewRepositoryR = page "Add another repository" (Just Configuration) $ do
|
||||||
where
|
where
|
||||||
askcombine newrepouuid newrepopath = do
|
askcombine newrepouuid newrepopath = do
|
||||||
newrepo <- liftIO $ relHome newrepopath
|
newrepo <- liftIO $ relHome newrepopath
|
||||||
mainrepo <- fromJust . relDir <$> lift getYesod
|
mainrepo <- fromJust . relDir <$> liftH getYesod
|
||||||
$(widgetFile "configurators/newrepository/combine")
|
$(widgetFile "configurators/newrepository/combine")
|
||||||
|
|
||||||
getCombineRepositoryR :: FilePathAndUUID -> Handler RepHtml
|
getCombineRepositoryR :: FilePathAndUUID -> Handler Html
|
||||||
getCombineRepositoryR (FilePathAndUUID newrepopath newrepouuid) = do
|
getCombineRepositoryR (FilePathAndUUID newrepopath newrepouuid) = do
|
||||||
r <- combineRepos newrepopath remotename
|
r <- combineRepos newrepopath remotename
|
||||||
liftAssistant $ syncRemote r
|
liftAssistant $ syncRemote r
|
||||||
|
@ -185,7 +197,7 @@ getCombineRepositoryR (FilePathAndUUID newrepopath newrepouuid) = do
|
||||||
where
|
where
|
||||||
remotename = takeFileName newrepopath
|
remotename = takeFileName newrepopath
|
||||||
|
|
||||||
selectDriveForm :: [RemovableDrive] -> Form RemovableDrive
|
selectDriveForm :: [RemovableDrive] -> Hamlet.Html -> MkMForm RemovableDrive
|
||||||
selectDriveForm drives = renderBootstrap $ RemovableDrive
|
selectDriveForm drives = renderBootstrap $ RemovableDrive
|
||||||
<$> pure Nothing
|
<$> pure Nothing
|
||||||
<*> areq (selectFieldList pairs) "Select drive:" Nothing
|
<*> areq (selectFieldList pairs) "Select drive:" Nothing
|
||||||
|
@ -208,24 +220,24 @@ removableDriveRepository drive =
|
||||||
T.unpack (mountPoint drive) </> T.unpack (driveRepoPath drive)
|
T.unpack (mountPoint drive) </> T.unpack (driveRepoPath drive)
|
||||||
|
|
||||||
{- Adding a removable drive. -}
|
{- Adding a removable drive. -}
|
||||||
getAddDriveR :: Handler RepHtml
|
getAddDriveR :: Handler Html
|
||||||
getAddDriveR = postAddDriveR
|
getAddDriveR = postAddDriveR
|
||||||
postAddDriveR :: Handler RepHtml
|
postAddDriveR :: Handler Html
|
||||||
postAddDriveR = page "Add a removable drive" (Just Configuration) $ do
|
postAddDriveR = page "Add a removable drive" (Just Configuration) $ do
|
||||||
removabledrives <- liftIO $ driveList
|
removabledrives <- liftIO $ driveList
|
||||||
writabledrives <- liftIO $
|
writabledrives <- liftIO $
|
||||||
filterM (canWrite . T.unpack . mountPoint) removabledrives
|
filterM (canWrite . T.unpack . mountPoint) removabledrives
|
||||||
((res, form), enctype) <- lift $ runFormPost $
|
((res, form), enctype) <- liftH $ runFormPost $
|
||||||
selectDriveForm (sort writabledrives)
|
selectDriveForm (sort writabledrives)
|
||||||
case res of
|
case res of
|
||||||
FormSuccess drive -> lift $ redirect $ ConfirmAddDriveR drive
|
FormSuccess drive -> liftH $ redirect $ ConfirmAddDriveR drive
|
||||||
_ -> $(widgetFile "configurators/adddrive")
|
_ -> $(widgetFile "configurators/adddrive")
|
||||||
|
|
||||||
{- The repo may already exist, when adding removable media
|
{- The repo may already exist, when adding removable media
|
||||||
- that has already been used elsewhere. If so, check
|
- that has already been used elsewhere. If so, check
|
||||||
- the UUID of the repo and see if it's one we know. If not,
|
- the UUID of the repo and see if it's one we know. If not,
|
||||||
- the user must confirm the repository merge. -}
|
- the user must confirm the repository merge. -}
|
||||||
getConfirmAddDriveR :: RemovableDrive -> Handler RepHtml
|
getConfirmAddDriveR :: RemovableDrive -> Handler Html
|
||||||
getConfirmAddDriveR drive = do
|
getConfirmAddDriveR drive = do
|
||||||
ifM (needconfirm)
|
ifM (needconfirm)
|
||||||
( page "Combine repositories?" (Just Configuration) $
|
( page "Combine repositories?" (Just Configuration) $
|
||||||
|
@ -249,13 +261,17 @@ getConfirmAddDriveR drive = do
|
||||||
cloneModal :: Widget
|
cloneModal :: Widget
|
||||||
cloneModal = $(widgetFile "configurators/adddrive/clonemodal")
|
cloneModal = $(widgetFile "configurators/adddrive/clonemodal")
|
||||||
|
|
||||||
getFinishAddDriveR :: RemovableDrive -> Handler RepHtml
|
getFinishAddDriveR :: RemovableDrive -> Handler Html
|
||||||
getFinishAddDriveR drive = make >>= redirect . EditNewRepositoryR
|
getFinishAddDriveR drive = make >>= redirect . EditNewRepositoryR
|
||||||
where
|
where
|
||||||
make = do
|
make = do
|
||||||
liftIO $ createDirectoryIfMissing True dir
|
liftIO $ createDirectoryIfMissing True dir
|
||||||
isnew <- liftIO $ makeRepo dir True
|
isnew <- liftIO $ makeRepo dir True
|
||||||
u <- liftIO $ initRepo isnew False dir $ Just remotename
|
u <- liftIO $ initRepo isnew False dir $ Just remotename
|
||||||
|
{- Removable drives are not reliable media, so enable fsync. -}
|
||||||
|
liftIO $ inDir dir $
|
||||||
|
setConfig (ConfigKey "core.fsyncobjectfiles")
|
||||||
|
(Git.Config.boolConfig True)
|
||||||
r <- combineRepos dir remotename
|
r <- combineRepos dir remotename
|
||||||
liftAnnex $ setStandardGroup u TransferGroup
|
liftAnnex $ setStandardGroup u TransferGroup
|
||||||
liftAssistant $ syncRemote r
|
liftAssistant $ syncRemote r
|
||||||
|
@ -273,7 +289,7 @@ combineRepos dir name = liftAnnex $ do
|
||||||
liftIO $ inDir dir $ void $ makeGitRemote hostname hostlocation
|
liftIO $ inDir dir $ void $ makeGitRemote hostname hostlocation
|
||||||
addRemote $ makeGitRemote name dir
|
addRemote $ makeGitRemote name dir
|
||||||
|
|
||||||
getEnableDirectoryR :: UUID -> Handler RepHtml
|
getEnableDirectoryR :: UUID -> Handler Html
|
||||||
getEnableDirectoryR uuid = page "Enable a repository" (Just Configuration) $ do
|
getEnableDirectoryR uuid = page "Enable a repository" (Just Configuration) $ do
|
||||||
description <- liftAnnex $ T.pack <$> prettyUUID uuid
|
description <- liftAnnex $ T.pack <$> prettyUUID uuid
|
||||||
$(widgetFile "configurators/enabledirectory")
|
$(widgetFile "configurators/enabledirectory")
|
||||||
|
@ -311,13 +327,15 @@ driveList = return []
|
||||||
{- Bootstraps from first run mode to a fully running assistant in a
|
{- Bootstraps from first run mode to a fully running assistant in a
|
||||||
- repository, by running the postFirstRun callback, which returns the
|
- repository, by running the postFirstRun callback, which returns the
|
||||||
- url to the new webapp. -}
|
- url to the new webapp. -}
|
||||||
startFullAssistant :: FilePath -> StandardGroup -> Handler ()
|
startFullAssistant :: FilePath -> StandardGroup -> Maybe (Annex ())-> Handler ()
|
||||||
startFullAssistant path repogroup = do
|
startFullAssistant path repogroup setup = do
|
||||||
webapp <- getYesod
|
webapp <- getYesod
|
||||||
url <- liftIO $ do
|
url <- liftIO $ do
|
||||||
isnew <- makeRepo path False
|
isnew <- makeRepo path False
|
||||||
u <- initRepo isnew True path Nothing
|
u <- initRepo isnew True path Nothing
|
||||||
inDir path $ setStandardGroup u repogroup
|
inDir path $ do
|
||||||
|
setStandardGroup u repogroup
|
||||||
|
maybe noop id setup
|
||||||
addAutoStartFile path
|
addAutoStartFile path
|
||||||
setCurrentDirectory path
|
setCurrentDirectory path
|
||||||
fromJust $ postFirstRun webapp
|
fromJust $ postFirstRun webapp
|
||||||
|
@ -352,9 +370,7 @@ inDir dir a = do
|
||||||
{- Creates a new repository, and returns its UUID. -}
|
{- Creates a new repository, and returns its UUID. -}
|
||||||
initRepo :: Bool -> Bool -> FilePath -> Maybe String -> IO UUID
|
initRepo :: Bool -> Bool -> FilePath -> Maybe String -> IO UUID
|
||||||
initRepo True primary_assistant_repo dir desc = inDir dir $ do
|
initRepo True primary_assistant_repo dir desc = inDir dir $ do
|
||||||
{- Initialize a git-annex repository in a directory with a description. -}
|
initRepo' desc
|
||||||
unlessM isInitialized $
|
|
||||||
initialize desc
|
|
||||||
{- Initialize the master branch, so things that expect
|
{- Initialize the master branch, so things that expect
|
||||||
- to have it will work, before any files are added. -}
|
- to have it will work, before any files are added. -}
|
||||||
unlessM (Git.Config.isBare <$> gitRepo) $
|
unlessM (Git.Config.isBare <$> gitRepo) $
|
||||||
|
@ -377,9 +393,13 @@ initRepo True primary_assistant_repo dir desc = inDir dir $ do
|
||||||
getUUID
|
getUUID
|
||||||
{- Repo already exists, could be a non-git-annex repo though. -}
|
{- Repo already exists, could be a non-git-annex repo though. -}
|
||||||
initRepo False _ dir desc = inDir dir $ do
|
initRepo False _ dir desc = inDir dir $ do
|
||||||
|
initRepo' desc
|
||||||
|
getUUID
|
||||||
|
|
||||||
|
initRepo' :: Maybe String -> Annex ()
|
||||||
|
initRepo' desc = do
|
||||||
unlessM isInitialized $
|
unlessM isInitialized $
|
||||||
initialize desc
|
initialize desc
|
||||||
getUUID
|
|
||||||
|
|
||||||
{- Checks if the user can write to a directory.
|
{- Checks if the user can write to a directory.
|
||||||
-
|
-
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
- Licensed under the GNU AGPL version 3 or higher.
|
- Licensed under the GNU AGPL version 3 or higher.
|
||||||
-}
|
-}
|
||||||
|
|
||||||
{-# LANGUAGE TypeFamilies, QuasiQuotes, MultiParamTypeClasses, TemplateHaskell, OverloadedStrings, RankNTypes #-}
|
{-# LANGUAGE TypeFamilies, QuasiQuotes, TemplateHaskell, OverloadedStrings #-}
|
||||||
{-# LANGUAGE CPP #-}
|
{-# LANGUAGE CPP #-}
|
||||||
|
|
||||||
module Assistant.WebApp.Configurators.Pairing where
|
module Assistant.WebApp.Configurators.Pairing where
|
||||||
|
@ -49,7 +49,7 @@ import Control.Concurrent
|
||||||
import qualified Data.Set as S
|
import qualified Data.Set as S
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
getStartXMPPPairFriendR :: Handler RepHtml
|
getStartXMPPPairFriendR :: Handler Html
|
||||||
#ifdef WITH_XMPP
|
#ifdef WITH_XMPP
|
||||||
getStartXMPPPairFriendR = ifM (isJust <$> liftAnnex getXMPPCreds)
|
getStartXMPPPairFriendR = ifM (isJust <$> liftAnnex getXMPPCreds)
|
||||||
( do
|
( do
|
||||||
|
@ -65,11 +65,11 @@ getStartXMPPPairFriendR = ifM (isJust <$> liftAnnex getXMPPCreds)
|
||||||
#else
|
#else
|
||||||
getStartXMPPPairFriendR = noXMPPPairing
|
getStartXMPPPairFriendR = noXMPPPairing
|
||||||
|
|
||||||
noXMPPPairing :: Handler RepHtml
|
noXMPPPairing :: Handler Html
|
||||||
noXMPPPairing = noPairing "XMPP"
|
noXMPPPairing = noPairing "XMPP"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
getStartXMPPPairSelfR :: Handler RepHtml
|
getStartXMPPPairSelfR :: Handler Html
|
||||||
#ifdef WITH_XMPP
|
#ifdef WITH_XMPP
|
||||||
getStartXMPPPairSelfR = go =<< liftAnnex getXMPPCreds
|
getStartXMPPPairSelfR = go =<< liftAnnex getXMPPCreds
|
||||||
where
|
where
|
||||||
|
@ -87,14 +87,14 @@ getStartXMPPPairSelfR = go =<< liftAnnex getXMPPCreds
|
||||||
getStartXMPPPairSelfR = noXMPPPairing
|
getStartXMPPPairSelfR = noXMPPPairing
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
getRunningXMPPPairFriendR :: BuddyKey -> Handler RepHtml
|
getRunningXMPPPairFriendR :: BuddyKey -> Handler Html
|
||||||
getRunningXMPPPairFriendR = sendXMPPPairRequest . Just
|
getRunningXMPPPairFriendR = sendXMPPPairRequest . Just
|
||||||
|
|
||||||
getRunningXMPPPairSelfR :: Handler RepHtml
|
getRunningXMPPPairSelfR :: Handler Html
|
||||||
getRunningXMPPPairSelfR = sendXMPPPairRequest Nothing
|
getRunningXMPPPairSelfR = sendXMPPPairRequest Nothing
|
||||||
|
|
||||||
{- Sends a XMPP pair request, to a buddy or to self. -}
|
{- Sends a XMPP pair request, to a buddy or to self. -}
|
||||||
sendXMPPPairRequest :: Maybe BuddyKey -> Handler RepHtml
|
sendXMPPPairRequest :: Maybe BuddyKey -> Handler Html
|
||||||
#ifdef WITH_XMPP
|
#ifdef WITH_XMPP
|
||||||
sendXMPPPairRequest mbid = do
|
sendXMPPPairRequest mbid = do
|
||||||
bid <- maybe getself return mbid
|
bid <- maybe getself return mbid
|
||||||
|
@ -125,28 +125,28 @@ sendXMPPPairRequest _ = noXMPPPairing
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
{- Starts local pairing. -}
|
{- Starts local pairing. -}
|
||||||
getStartLocalPairR :: Handler RepHtml
|
getStartLocalPairR :: Handler Html
|
||||||
getStartLocalPairR = postStartLocalPairR
|
getStartLocalPairR = postStartLocalPairR
|
||||||
postStartLocalPairR :: Handler RepHtml
|
postStartLocalPairR :: Handler Html
|
||||||
#ifdef WITH_PAIRING
|
#ifdef WITH_PAIRING
|
||||||
postStartLocalPairR = promptSecret Nothing $
|
postStartLocalPairR = promptSecret Nothing $
|
||||||
startLocalPairing PairReq noop pairingAlert Nothing
|
startLocalPairing PairReq noop pairingAlert Nothing
|
||||||
#else
|
#else
|
||||||
postStartLocalPairR = noLocalPairing
|
postStartLocalPairR = noLocalPairing
|
||||||
|
|
||||||
noLocalPairing :: Handler RepHtml
|
noLocalPairing :: Handler Html
|
||||||
noLocalPairing = noPairing "local"
|
noLocalPairing = noPairing "local"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
{- Runs on the system that responds to a local pair request; sets up the ssh
|
{- Runs on the system that responds to a local pair request; sets up the ssh
|
||||||
- authorized key first so that the originating host can immediately sync
|
- authorized key first so that the originating host can immediately sync
|
||||||
- with us. -}
|
- with us. -}
|
||||||
getFinishLocalPairR :: PairMsg -> Handler RepHtml
|
getFinishLocalPairR :: PairMsg -> Handler Html
|
||||||
getFinishLocalPairR = postFinishLocalPairR
|
getFinishLocalPairR = postFinishLocalPairR
|
||||||
postFinishLocalPairR :: PairMsg -> Handler RepHtml
|
postFinishLocalPairR :: PairMsg -> Handler Html
|
||||||
#ifdef WITH_PAIRING
|
#ifdef WITH_PAIRING
|
||||||
postFinishLocalPairR msg = promptSecret (Just msg) $ \_ secret -> do
|
postFinishLocalPairR msg = promptSecret (Just msg) $ \_ secret -> do
|
||||||
repodir <- lift $ repoPath <$> liftAnnex gitRepo
|
repodir <- liftH $ repoPath <$> liftAnnex gitRepo
|
||||||
liftIO $ setup repodir
|
liftIO $ setup repodir
|
||||||
startLocalPairing PairAck (cleanup repodir) alert uuid "" secret
|
startLocalPairing PairAck (cleanup repodir) alert uuid "" secret
|
||||||
where
|
where
|
||||||
|
@ -159,7 +159,7 @@ postFinishLocalPairR msg = promptSecret (Just msg) $ \_ secret -> do
|
||||||
postFinishLocalPairR _ = noLocalPairing
|
postFinishLocalPairR _ = noLocalPairing
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
getConfirmXMPPPairFriendR :: PairKey -> Handler RepHtml
|
getConfirmXMPPPairFriendR :: PairKey -> Handler Html
|
||||||
#ifdef WITH_XMPP
|
#ifdef WITH_XMPP
|
||||||
getConfirmXMPPPairFriendR pairkey@(PairKey _ t) = case parseJID t of
|
getConfirmXMPPPairFriendR pairkey@(PairKey _ t) = case parseJID t of
|
||||||
Nothing -> error "bad JID"
|
Nothing -> error "bad JID"
|
||||||
|
@ -170,7 +170,7 @@ getConfirmXMPPPairFriendR pairkey@(PairKey _ t) = case parseJID t of
|
||||||
getConfirmXMPPPairFriendR _ = noXMPPPairing
|
getConfirmXMPPPairFriendR _ = noXMPPPairing
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
getFinishXMPPPairFriendR :: PairKey -> Handler RepHtml
|
getFinishXMPPPairFriendR :: PairKey -> Handler Html
|
||||||
#ifdef WITH_XMPP
|
#ifdef WITH_XMPP
|
||||||
getFinishXMPPPairFriendR (PairKey theiruuid t) = case parseJID t of
|
getFinishXMPPPairFriendR (PairKey theiruuid t) = case parseJID t of
|
||||||
Nothing -> error "bad JID"
|
Nothing -> error "bad JID"
|
||||||
|
@ -188,13 +188,13 @@ getFinishXMPPPairFriendR _ = noXMPPPairing
|
||||||
{- Displays a page indicating pairing status and
|
{- Displays a page indicating pairing status and
|
||||||
- prompting to set up cloud repositories. -}
|
- prompting to set up cloud repositories. -}
|
||||||
#ifdef WITH_XMPP
|
#ifdef WITH_XMPP
|
||||||
xmppPairStatus :: Bool -> Maybe JID -> Handler RepHtml
|
xmppPairStatus :: Bool -> Maybe JID -> Handler Html
|
||||||
xmppPairStatus inprogress theirjid = pairPage $ do
|
xmppPairStatus inprogress theirjid = pairPage $ do
|
||||||
let friend = buddyName <$> theirjid
|
let friend = buddyName <$> theirjid
|
||||||
$(widgetFile "configurators/pairing/xmpp/end")
|
$(widgetFile "configurators/pairing/xmpp/end")
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
getRunningLocalPairR :: SecretReminder -> Handler RepHtml
|
getRunningLocalPairR :: SecretReminder -> Handler Html
|
||||||
#ifdef WITH_PAIRING
|
#ifdef WITH_PAIRING
|
||||||
getRunningLocalPairR s = pairPage $ do
|
getRunningLocalPairR s = pairPage $ do
|
||||||
let secret = fromSecretReminder s
|
let secret = fromSecretReminder s
|
||||||
|
@ -216,8 +216,8 @@ getRunningLocalPairR _ = noLocalPairing
|
||||||
-}
|
-}
|
||||||
startLocalPairing :: PairStage -> IO () -> (AlertButton -> Alert) -> Maybe UUID -> Text -> Secret -> Widget
|
startLocalPairing :: PairStage -> IO () -> (AlertButton -> Alert) -> Maybe UUID -> Text -> Secret -> Widget
|
||||||
startLocalPairing stage oncancel alert muuid displaysecret secret = do
|
startLocalPairing stage oncancel alert muuid displaysecret secret = do
|
||||||
urlrender <- lift getUrlRender
|
urlrender <- liftH getUrlRender
|
||||||
reldir <- fromJust . relDir <$> lift getYesod
|
reldir <- fromJust . relDir <$> liftH getYesod
|
||||||
|
|
||||||
sendrequests <- liftAssistant $ asIO2 $ mksendrequests urlrender
|
sendrequests <- liftAssistant $ asIO2 $ mksendrequests urlrender
|
||||||
{- Generating a ssh key pair can take a while, so do it in the
|
{- Generating a ssh key pair can take a while, so do it in the
|
||||||
|
@ -235,7 +235,7 @@ startLocalPairing stage oncancel alert muuid displaysecret secret = do
|
||||||
startSending pip stage $ sendrequests sender
|
startSending pip stage $ sendrequests sender
|
||||||
void $ liftIO $ forkIO thread
|
void $ liftIO $ forkIO thread
|
||||||
|
|
||||||
lift $ redirect $ RunningLocalPairR $ toSecretReminder displaysecret
|
liftH $ redirect $ RunningLocalPairR $ toSecretReminder displaysecret
|
||||||
where
|
where
|
||||||
{- Sends pairing messages until the thread is killed,
|
{- Sends pairing messages until the thread is killed,
|
||||||
- and shows an activity alert while doing it.
|
- and shows an activity alert while doing it.
|
||||||
|
@ -262,9 +262,9 @@ data InputSecret = InputSecret { secretText :: Maybe Text }
|
||||||
|
|
||||||
{- If a PairMsg is passed in, ensures that the user enters a secret
|
{- If a PairMsg is passed in, ensures that the user enters a secret
|
||||||
- that can validate it. -}
|
- that can validate it. -}
|
||||||
promptSecret :: Maybe PairMsg -> (Text -> Secret -> Widget) -> Handler RepHtml
|
promptSecret :: Maybe PairMsg -> (Text -> Secret -> Widget) -> Handler Html
|
||||||
promptSecret msg cont = pairPage $ do
|
promptSecret msg cont = pairPage $ do
|
||||||
((result, form), enctype) <- lift $
|
((result, form), enctype) <- liftH $
|
||||||
runFormPost $ renderBootstrap $
|
runFormPost $ renderBootstrap $
|
||||||
InputSecret <$> aopt textField "Secret phrase" Nothing
|
InputSecret <$> aopt textField "Secret phrase" Nothing
|
||||||
case result of
|
case result of
|
||||||
|
@ -319,9 +319,9 @@ sampleQuote = T.unwords
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
pairPage :: Widget -> Handler RepHtml
|
pairPage :: Widget -> Handler Html
|
||||||
pairPage = page "Pairing" (Just Configuration)
|
pairPage = page "Pairing" (Just Configuration)
|
||||||
|
|
||||||
noPairing :: Text -> Handler RepHtml
|
noPairing :: Text -> Handler Html
|
||||||
noPairing pairingtype = pairPage $
|
noPairing pairingtype = pairPage $
|
||||||
$(widgetFile "configurators/pairing/disabled")
|
$(widgetFile "configurators/pairing/disabled")
|
||||||
|
|
|
@ -18,9 +18,9 @@ import qualified Git
|
||||||
import Config
|
import Config
|
||||||
import Config.Files
|
import Config.Files
|
||||||
import Utility.DataUnits
|
import Utility.DataUnits
|
||||||
|
import Git.Config
|
||||||
|
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import System.Log.Logger
|
|
||||||
|
|
||||||
data PrefsForm = PrefsForm
|
data PrefsForm = PrefsForm
|
||||||
{ diskReserve :: Text
|
{ diskReserve :: Text
|
||||||
|
@ -29,7 +29,7 @@ data PrefsForm = PrefsForm
|
||||||
, debugEnabled :: Bool
|
, debugEnabled :: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
prefsAForm :: PrefsForm -> AForm WebApp WebApp PrefsForm
|
prefsAForm :: PrefsForm -> MkAForm PrefsForm
|
||||||
prefsAForm def = PrefsForm
|
prefsAForm def = PrefsForm
|
||||||
<$> areq (storageField `withNote` diskreservenote)
|
<$> areq (storageField `withNote` diskreservenote)
|
||||||
"Disk reserve" (Just $ diskReserve def)
|
"Disk reserve" (Just $ diskReserve def)
|
||||||
|
@ -68,7 +68,7 @@ getPrefs = PrefsForm
|
||||||
<$> (T.pack . roughSize storageUnits False . annexDiskReserve <$> Annex.getGitConfig)
|
<$> (T.pack . roughSize storageUnits False . annexDiskReserve <$> Annex.getGitConfig)
|
||||||
<*> (annexNumCopies <$> Annex.getGitConfig)
|
<*> (annexNumCopies <$> Annex.getGitConfig)
|
||||||
<*> inAutoStartFile
|
<*> inAutoStartFile
|
||||||
<*> ((==) <$> (pure $ Just DEBUG) <*> (liftIO $ getLevel <$> getRootLogger))
|
<*> (annexDebug <$> Annex.getGitConfig)
|
||||||
|
|
||||||
storePrefs :: PrefsForm -> Annex ()
|
storePrefs :: PrefsForm -> Annex ()
|
||||||
storePrefs p = do
|
storePrefs p = do
|
||||||
|
@ -79,18 +79,20 @@ storePrefs p = do
|
||||||
liftIO $ if autoStart p
|
liftIO $ if autoStart p
|
||||||
then addAutoStartFile here
|
then addAutoStartFile here
|
||||||
else removeAutoStartFile here
|
else removeAutoStartFile here
|
||||||
liftIO $ updateGlobalLogger rootLoggerName $ setLevel $
|
setConfig (annexConfig "debug") (boolConfig $ debugEnabled p)
|
||||||
if debugEnabled p then DEBUG else WARNING
|
liftIO $ if debugEnabled p
|
||||||
|
then enableDebugOutput
|
||||||
|
else disableDebugOutput
|
||||||
|
|
||||||
getPreferencesR :: Handler RepHtml
|
getPreferencesR :: Handler Html
|
||||||
getPreferencesR = postPreferencesR
|
getPreferencesR = postPreferencesR
|
||||||
postPreferencesR :: Handler RepHtml
|
postPreferencesR :: Handler Html
|
||||||
postPreferencesR = page "Preferences" (Just Configuration) $ do
|
postPreferencesR = page "Preferences" (Just Configuration) $ do
|
||||||
((result, form), enctype) <- lift $ do
|
((result, form), enctype) <- liftH $ do
|
||||||
current <- liftAnnex getPrefs
|
current <- liftAnnex getPrefs
|
||||||
runFormPost $ renderBootstrap $ prefsAForm current
|
runFormPost $ renderBootstrap $ prefsAForm current
|
||||||
case result of
|
case result of
|
||||||
FormSuccess new -> lift $ do
|
FormSuccess new -> liftH $ do
|
||||||
liftAnnex $ storePrefs new
|
liftAnnex $ storePrefs new
|
||||||
redirect ConfigurationR
|
redirect ConfigurationR
|
||||||
_ -> $(widgetFile "configurators/preferences")
|
_ -> $(widgetFile "configurators/preferences")
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
- Licensed under the GNU AGPL version 3 or higher.
|
- Licensed under the GNU AGPL version 3 or higher.
|
||||||
-}
|
-}
|
||||||
|
|
||||||
{-# LANGUAGE TypeFamilies, QuasiQuotes, MultiParamTypeClasses, TemplateHaskell, OverloadedStrings, RankNTypes #-}
|
{-# LANGUAGE QuasiQuotes, TemplateHaskell, OverloadedStrings #-}
|
||||||
{-# LANGUAGE CPP #-}
|
{-# LANGUAGE CPP #-}
|
||||||
|
|
||||||
module Assistant.WebApp.Configurators.Ssh where
|
module Assistant.WebApp.Configurators.Ssh where
|
||||||
|
@ -24,7 +24,7 @@ import qualified Data.Text as T
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
import Network.Socket
|
import Network.Socket
|
||||||
|
|
||||||
sshConfigurator :: Widget -> Handler RepHtml
|
sshConfigurator :: Widget -> Handler Html
|
||||||
sshConfigurator = page "Add a remote server" (Just Configuration)
|
sshConfigurator = page "Add a remote server" (Just Configuration)
|
||||||
|
|
||||||
data SshInput = SshInput
|
data SshInput = SshInput
|
||||||
|
@ -58,7 +58,11 @@ mkSshInput s = SshInput
|
||||||
, inputPort = sshPort s
|
, inputPort = sshPort s
|
||||||
}
|
}
|
||||||
|
|
||||||
sshInputAForm :: (Field WebApp WebApp Text) -> SshInput -> AForm WebApp WebApp SshInput
|
#if MIN_VERSION_yesod(1,2,0)
|
||||||
|
sshInputAForm :: Field Handler Text -> SshInput -> AForm Handler SshInput
|
||||||
|
#else
|
||||||
|
sshInputAForm :: Field WebApp WebApp Text -> SshInput -> AForm WebApp WebApp SshInput
|
||||||
|
#endif
|
||||||
sshInputAForm hostnamefield def = SshInput
|
sshInputAForm hostnamefield def = SshInput
|
||||||
<$> aopt check_hostname "Host name" (Just $ inputHostname def)
|
<$> aopt check_hostname "Host name" (Just $ inputHostname def)
|
||||||
<*> aopt check_username "User name" (Just $ inputUsername def)
|
<*> aopt check_username "User name" (Just $ inputUsername def)
|
||||||
|
@ -102,12 +106,12 @@ usable (UnusableServer _) = False
|
||||||
usable UsableRsyncServer = True
|
usable UsableRsyncServer = True
|
||||||
usable UsableSshInput = True
|
usable UsableSshInput = True
|
||||||
|
|
||||||
getAddSshR :: Handler RepHtml
|
getAddSshR :: Handler Html
|
||||||
getAddSshR = postAddSshR
|
getAddSshR = postAddSshR
|
||||||
postAddSshR :: Handler RepHtml
|
postAddSshR :: Handler Html
|
||||||
postAddSshR = sshConfigurator $ do
|
postAddSshR = sshConfigurator $ do
|
||||||
u <- liftIO $ T.pack <$> myUserName
|
u <- liftIO $ T.pack <$> myUserName
|
||||||
((result, form), enctype) <- lift $
|
((result, form), enctype) <- liftH $
|
||||||
runFormPost $ renderBootstrap $ sshInputAForm textField $
|
runFormPost $ renderBootstrap $ sshInputAForm textField $
|
||||||
SshInput Nothing (Just u) Nothing 22
|
SshInput Nothing (Just u) Nothing 22
|
||||||
case result of
|
case result of
|
||||||
|
@ -115,7 +119,7 @@ postAddSshR = sshConfigurator $ do
|
||||||
s <- liftIO $ testServer sshinput
|
s <- liftIO $ testServer sshinput
|
||||||
case s of
|
case s of
|
||||||
Left status -> showform form enctype status
|
Left status -> showform form enctype status
|
||||||
Right sshdata -> lift $ redirect $ ConfirmSshR sshdata
|
Right sshdata -> liftH $ redirect $ ConfirmSshR sshdata
|
||||||
_ -> showform form enctype UntestedServer
|
_ -> showform form enctype UntestedServer
|
||||||
where
|
where
|
||||||
showform form enctype status = $(widgetFile "configurators/ssh/add")
|
showform form enctype status = $(widgetFile "configurators/ssh/add")
|
||||||
|
@ -131,19 +135,19 @@ sshTestModal = $(widgetFile "configurators/ssh/testmodal")
|
||||||
- Note that there's no EnableSshR because ssh remotes are not special
|
- Note that there's no EnableSshR because ssh remotes are not special
|
||||||
- remotes, and so their configuration is not shared between repositories.
|
- remotes, and so their configuration is not shared between repositories.
|
||||||
-}
|
-}
|
||||||
getEnableRsyncR :: UUID -> Handler RepHtml
|
getEnableRsyncR :: UUID -> Handler Html
|
||||||
getEnableRsyncR = postEnableRsyncR
|
getEnableRsyncR = postEnableRsyncR
|
||||||
postEnableRsyncR :: UUID -> Handler RepHtml
|
postEnableRsyncR :: UUID -> Handler Html
|
||||||
postEnableRsyncR u = do
|
postEnableRsyncR u = do
|
||||||
m <- fromMaybe M.empty . M.lookup u <$> liftAnnex readRemoteLog
|
m <- fromMaybe M.empty . M.lookup u <$> liftAnnex readRemoteLog
|
||||||
case (parseSshRsyncUrl =<< M.lookup "rsyncurl" m, M.lookup "name" m) of
|
case (parseSshRsyncUrl =<< M.lookup "rsyncurl" m, M.lookup "name" m) of
|
||||||
(Just sshinput, Just reponame) -> sshConfigurator $ do
|
(Just sshinput, Just reponame) -> sshConfigurator $ do
|
||||||
((result, form), enctype) <- lift $
|
((result, form), enctype) <- liftH $
|
||||||
runFormPost $ renderBootstrap $ sshInputAForm textField sshinput
|
runFormPost $ renderBootstrap $ sshInputAForm textField sshinput
|
||||||
case result of
|
case result of
|
||||||
FormSuccess sshinput'
|
FormSuccess sshinput'
|
||||||
| isRsyncNet (inputHostname sshinput') ->
|
| isRsyncNet (inputHostname sshinput') ->
|
||||||
void $ lift $ makeRsyncNet sshinput' reponame (const noop)
|
void $ liftH $ makeRsyncNet sshinput' reponame (const noop)
|
||||||
| otherwise -> do
|
| otherwise -> do
|
||||||
s <- liftIO $ testServer sshinput'
|
s <- liftIO $ testServer sshinput'
|
||||||
case s of
|
case s of
|
||||||
|
@ -156,7 +160,7 @@ postEnableRsyncR u = do
|
||||||
showform form enctype status = do
|
showform form enctype status = do
|
||||||
description <- liftAnnex $ T.pack <$> prettyUUID u
|
description <- liftAnnex $ T.pack <$> prettyUUID u
|
||||||
$(widgetFile "configurators/ssh/enable")
|
$(widgetFile "configurators/ssh/enable")
|
||||||
enable sshdata = lift $ redirect $ ConfirmSshR $
|
enable sshdata = liftH $ redirect $ ConfirmSshR $
|
||||||
sshdata { rsyncOnly = True }
|
sshdata { rsyncOnly = True }
|
||||||
|
|
||||||
{- Converts a rsyncurl value to a SshInput. But only if it's a ssh rsync
|
{- Converts a rsyncurl value to a SshInput. But only if it's a ssh rsync
|
||||||
|
@ -249,18 +253,18 @@ testServer sshinput@(SshInput { inputHostname = Just hn }) = do
|
||||||
|
|
||||||
{- Runs a ssh command; if it fails shows the user the transcript,
|
{- Runs a ssh command; if it fails shows the user the transcript,
|
||||||
- and if it succeeds, runs an action. -}
|
- and if it succeeds, runs an action. -}
|
||||||
sshSetup :: [String] -> String -> Handler RepHtml -> Handler RepHtml
|
sshSetup :: [String] -> String -> Handler Html -> Handler Html
|
||||||
sshSetup opts input a = do
|
sshSetup opts input a = do
|
||||||
(transcript, ok) <- liftIO $ sshTranscript opts (Just input)
|
(transcript, ok) <- liftIO $ sshTranscript opts (Just input)
|
||||||
if ok
|
if ok
|
||||||
then a
|
then a
|
||||||
else showSshErr transcript
|
else showSshErr transcript
|
||||||
|
|
||||||
showSshErr :: String -> Handler RepHtml
|
showSshErr :: String -> Handler Html
|
||||||
showSshErr msg = sshConfigurator $
|
showSshErr msg = sshConfigurator $
|
||||||
$(widgetFile "configurators/ssh/error")
|
$(widgetFile "configurators/ssh/error")
|
||||||
|
|
||||||
getConfirmSshR :: SshData -> Handler RepHtml
|
getConfirmSshR :: SshData -> Handler Html
|
||||||
getConfirmSshR sshdata = sshConfigurator $
|
getConfirmSshR sshdata = sshConfigurator $
|
||||||
$(widgetFile "configurators/ssh/confirm")
|
$(widgetFile "configurators/ssh/confirm")
|
||||||
|
|
||||||
|
@ -269,29 +273,29 @@ getRetrySshR sshdata = do
|
||||||
s <- liftIO $ testServer $ mkSshInput sshdata
|
s <- liftIO $ testServer $ mkSshInput sshdata
|
||||||
redirect $ either (const $ ConfirmSshR sshdata) ConfirmSshR s
|
redirect $ either (const $ ConfirmSshR sshdata) ConfirmSshR s
|
||||||
|
|
||||||
getMakeSshGitR :: SshData -> Handler RepHtml
|
getMakeSshGitR :: SshData -> Handler Html
|
||||||
getMakeSshGitR = makeSsh False setupGroup
|
getMakeSshGitR = makeSsh False setupGroup
|
||||||
|
|
||||||
getMakeSshRsyncR :: SshData -> Handler RepHtml
|
getMakeSshRsyncR :: SshData -> Handler Html
|
||||||
getMakeSshRsyncR = makeSsh True setupGroup
|
getMakeSshRsyncR = makeSsh True setupGroup
|
||||||
|
|
||||||
makeSsh :: Bool -> (Remote -> Handler ()) -> SshData -> Handler RepHtml
|
makeSsh :: Bool -> (Remote -> Handler ()) -> SshData -> Handler Html
|
||||||
makeSsh rsync setup sshdata
|
makeSsh rsync setup sshdata
|
||||||
| needsPubKey sshdata = do
|
| needsPubKey sshdata = do
|
||||||
keypair <- liftIO genSshKeyPair
|
keypair <- liftIO genSshKeyPair
|
||||||
sshdata' <- liftIO $ setupSshKeyPair keypair sshdata
|
sshdata' <- liftIO $ setupSshKeyPair keypair sshdata
|
||||||
makeSsh' rsync setup sshdata' (Just keypair)
|
makeSsh' rsync setup sshdata sshdata' (Just keypair)
|
||||||
| sshPort sshdata /= 22 = do
|
| sshPort sshdata /= 22 = do
|
||||||
sshdata' <- liftIO $ setSshConfig sshdata []
|
sshdata' <- liftIO $ setSshConfig sshdata []
|
||||||
makeSsh' rsync setup sshdata' Nothing
|
makeSsh' rsync setup sshdata sshdata' Nothing
|
||||||
| otherwise = makeSsh' rsync setup sshdata Nothing
|
| otherwise = makeSsh' rsync setup sshdata sshdata Nothing
|
||||||
|
|
||||||
makeSsh' :: Bool -> (Remote -> Handler ()) -> SshData -> Maybe SshKeyPair -> Handler RepHtml
|
makeSsh' :: Bool -> (Remote -> Handler ()) -> SshData -> SshData -> Maybe SshKeyPair -> Handler Html
|
||||||
makeSsh' rsync setup sshdata keypair =
|
makeSsh' rsync setup origsshdata sshdata keypair = do
|
||||||
sshSetup [sshhost, remoteCommand] "" $
|
sshSetup ["-p", show (sshPort origsshdata), sshhost, remoteCommand] "" $
|
||||||
makeSshRepo rsync setup sshdata
|
makeSshRepo rsync setup sshdata
|
||||||
where
|
where
|
||||||
sshhost = genSshHost (sshHostName sshdata) (sshUserName sshdata)
|
sshhost = genSshHost (sshHostName origsshdata) (sshUserName origsshdata)
|
||||||
remotedir = T.unpack $ sshDirectory sshdata
|
remotedir = T.unpack $ sshDirectory sshdata
|
||||||
remoteCommand = shellWrap $ intercalate "&&" $ catMaybes
|
remoteCommand = shellWrap $ intercalate "&&" $ catMaybes
|
||||||
[ Just $ "mkdir -p " ++ shellEscape remotedir
|
[ Just $ "mkdir -p " ++ shellEscape remotedir
|
||||||
|
@ -299,19 +303,19 @@ makeSsh' rsync setup sshdata keypair =
|
||||||
, if rsync then Nothing else Just "if [ ! -d .git ]; then git init --bare --shared; fi"
|
, if rsync then Nothing else Just "if [ ! -d .git ]; then git init --bare --shared; fi"
|
||||||
, if rsync then Nothing else Just "git annex init"
|
, if rsync then Nothing else Just "git annex init"
|
||||||
, if needsPubKey sshdata
|
, if needsPubKey sshdata
|
||||||
then addAuthorizedKeysCommand (rsyncOnly sshdata) remotedir . sshPubKey <$> keypair
|
then addAuthorizedKeysCommand (rsync || rsyncOnly sshdata) remotedir . sshPubKey <$> keypair
|
||||||
else Nothing
|
else Nothing
|
||||||
]
|
]
|
||||||
|
|
||||||
makeSshRepo :: Bool -> (Remote -> Handler ()) -> SshData -> Handler RepHtml
|
makeSshRepo :: Bool -> (Remote -> Handler ()) -> SshData -> Handler Html
|
||||||
makeSshRepo forcersync setup sshdata = do
|
makeSshRepo forcersync setup sshdata = do
|
||||||
r <- liftAssistant $ makeSshRemote forcersync sshdata Nothing
|
r <- liftAssistant $ makeSshRemote forcersync sshdata Nothing
|
||||||
setup r
|
setup r
|
||||||
redirect $ EditNewCloudRepositoryR $ Remote.uuid r
|
redirect $ EditNewCloudRepositoryR $ Remote.uuid r
|
||||||
|
|
||||||
getAddRsyncNetR :: Handler RepHtml
|
getAddRsyncNetR :: Handler Html
|
||||||
getAddRsyncNetR = postAddRsyncNetR
|
getAddRsyncNetR = postAddRsyncNetR
|
||||||
postAddRsyncNetR :: Handler RepHtml
|
postAddRsyncNetR :: Handler Html
|
||||||
postAddRsyncNetR = do
|
postAddRsyncNetR = do
|
||||||
((result, form), enctype) <- runFormPost $
|
((result, form), enctype) <- runFormPost $
|
||||||
renderBootstrap $ sshInputAForm hostnamefield $
|
renderBootstrap $ sshInputAForm hostnamefield $
|
||||||
|
@ -339,7 +343,7 @@ postAddRsyncNetR = do
|
||||||
user name something like "7491"
|
user name something like "7491"
|
||||||
|]
|
|]
|
||||||
|
|
||||||
makeRsyncNet :: SshInput -> String -> (Remote -> Handler ()) -> Handler RepHtml
|
makeRsyncNet :: SshInput -> String -> (Remote -> Handler ()) -> Handler Html
|
||||||
makeRsyncNet sshinput reponame setup = do
|
makeRsyncNet sshinput reponame setup = do
|
||||||
knownhost <- liftIO $ maybe (return False) knownHost (inputHostname sshinput)
|
knownhost <- liftIO $ maybe (return False) knownHost (inputHostname sshinput)
|
||||||
keypair <- liftIO $ genSshKeyPair
|
keypair <- liftIO $ genSshKeyPair
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
- Licensed under the GNU AGPL version 3 or higher.
|
- Licensed under the GNU AGPL version 3 or higher.
|
||||||
-}
|
-}
|
||||||
|
|
||||||
{-# LANGUAGE CPP, TypeFamilies, QuasiQuotes, MultiParamTypeClasses, TemplateHaskell, OverloadedStrings, RankNTypes #-}
|
{-# LANGUAGE CPP, QuasiQuotes, TemplateHaskell, OverloadedStrings #-}
|
||||||
|
|
||||||
module Assistant.WebApp.Configurators.WebDAV where
|
module Assistant.WebApp.Configurators.WebDAV where
|
||||||
|
|
||||||
|
@ -26,10 +26,10 @@ import qualified Data.Map as M
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import Network.URI
|
import Network.URI
|
||||||
|
|
||||||
webDAVConfigurator :: Widget -> Handler RepHtml
|
webDAVConfigurator :: Widget -> Handler Html
|
||||||
webDAVConfigurator = page "Add a WebDAV repository" (Just Configuration)
|
webDAVConfigurator = page "Add a WebDAV repository" (Just Configuration)
|
||||||
|
|
||||||
boxConfigurator :: Widget -> Handler RepHtml
|
boxConfigurator :: Widget -> Handler Html
|
||||||
boxConfigurator = page "Add a Box.com repository" (Just Configuration)
|
boxConfigurator = page "Add a Box.com repository" (Just Configuration)
|
||||||
|
|
||||||
data WebDAVInput = WebDAVInput
|
data WebDAVInput = WebDAVInput
|
||||||
|
@ -43,7 +43,7 @@ data WebDAVInput = WebDAVInput
|
||||||
toCredPair :: WebDAVInput -> CredPair
|
toCredPair :: WebDAVInput -> CredPair
|
||||||
toCredPair input = (T.unpack $ user input, T.unpack $ password input)
|
toCredPair input = (T.unpack $ user input, T.unpack $ password input)
|
||||||
|
|
||||||
boxComAForm :: Maybe CredPair -> AForm WebApp WebApp WebDAVInput
|
boxComAForm :: Maybe CredPair -> MkAForm WebDAVInput
|
||||||
boxComAForm defcreds = WebDAVInput
|
boxComAForm defcreds = WebDAVInput
|
||||||
<$> areq textField "Username or Email" (T.pack . fst <$> defcreds)
|
<$> areq textField "Username or Email" (T.pack . fst <$> defcreds)
|
||||||
<*> areq passwordField "Box.com Password" (T.pack . snd <$> defcreds)
|
<*> areq passwordField "Box.com Password" (T.pack . snd <$> defcreds)
|
||||||
|
@ -51,7 +51,7 @@ boxComAForm defcreds = WebDAVInput
|
||||||
<*> areq textField "Directory" (Just "annex")
|
<*> areq textField "Directory" (Just "annex")
|
||||||
<*> enableEncryptionField
|
<*> enableEncryptionField
|
||||||
|
|
||||||
webDAVCredsAForm :: Maybe CredPair -> AForm WebApp WebApp WebDAVInput
|
webDAVCredsAForm :: Maybe CredPair -> MkAForm WebDAVInput
|
||||||
webDAVCredsAForm defcreds = WebDAVInput
|
webDAVCredsAForm defcreds = WebDAVInput
|
||||||
<$> areq textField "Username or Email" (T.pack . fst <$> defcreds)
|
<$> areq textField "Username or Email" (T.pack . fst <$> defcreds)
|
||||||
<*> areq passwordField "Password" (T.pack . snd <$> defcreds)
|
<*> areq passwordField "Password" (T.pack . snd <$> defcreds)
|
||||||
|
@ -59,17 +59,17 @@ webDAVCredsAForm defcreds = WebDAVInput
|
||||||
<*> pure T.empty
|
<*> pure T.empty
|
||||||
<*> pure NoEncryption -- not used!
|
<*> pure NoEncryption -- not used!
|
||||||
|
|
||||||
getAddBoxComR :: Handler RepHtml
|
getAddBoxComR :: Handler Html
|
||||||
getAddBoxComR = postAddBoxComR
|
getAddBoxComR = postAddBoxComR
|
||||||
postAddBoxComR :: Handler RepHtml
|
postAddBoxComR :: Handler Html
|
||||||
#ifdef WITH_WEBDAV
|
#ifdef WITH_WEBDAV
|
||||||
postAddBoxComR = boxConfigurator $ do
|
postAddBoxComR = boxConfigurator $ do
|
||||||
defcreds <- liftAnnex $ previouslyUsedWebDAVCreds "box.com"
|
defcreds <- liftAnnex $ previouslyUsedWebDAVCreds "box.com"
|
||||||
((result, form), enctype) <- lift $
|
((result, form), enctype) <- liftH $
|
||||||
runFormPost $ renderBootstrap $ boxComAForm defcreds
|
runFormPost $ renderBootstrap $ boxComAForm defcreds
|
||||||
case result of
|
case result of
|
||||||
FormSuccess input -> lift $
|
FormSuccess input -> liftH $
|
||||||
makeWebDavRemote "box.com" (toCredPair input) setgroup $ M.fromList
|
makeWebDavRemote initSpecialRemote "box.com" (toCredPair input) setgroup $ M.fromList
|
||||||
[ configureEncryption $ enableEncryption input
|
[ configureEncryption $ enableEncryption input
|
||||||
, ("embedcreds", if embedCreds input then "yes" else "no")
|
, ("embedcreds", if embedCreds input then "yes" else "no")
|
||||||
, ("type", "webdav")
|
, ("type", "webdav")
|
||||||
|
@ -87,9 +87,9 @@ postAddBoxComR = boxConfigurator $ do
|
||||||
postAddBoxComR = error "WebDAV not supported by this build"
|
postAddBoxComR = error "WebDAV not supported by this build"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
getEnableWebDAVR :: UUID -> Handler RepHtml
|
getEnableWebDAVR :: UUID -> Handler Html
|
||||||
getEnableWebDAVR = postEnableWebDAVR
|
getEnableWebDAVR = postEnableWebDAVR
|
||||||
postEnableWebDAVR :: UUID -> Handler RepHtml
|
postEnableWebDAVR :: UUID -> Handler Html
|
||||||
#ifdef WITH_WEBDAV
|
#ifdef WITH_WEBDAV
|
||||||
postEnableWebDAVR uuid = do
|
postEnableWebDAVR uuid = do
|
||||||
m <- liftAnnex readRemoteLog
|
m <- liftAnnex readRemoteLog
|
||||||
|
@ -99,8 +99,8 @@ postEnableWebDAVR uuid = do
|
||||||
mcreds <- liftAnnex $
|
mcreds <- liftAnnex $
|
||||||
getRemoteCredPairFor "webdav" c (WebDAV.davCreds uuid)
|
getRemoteCredPairFor "webdav" c (WebDAV.davCreds uuid)
|
||||||
case mcreds of
|
case mcreds of
|
||||||
Just creds -> webDAVConfigurator $ lift $
|
Just creds -> webDAVConfigurator $ liftH $
|
||||||
makeWebDavRemote name creds (const noop) M.empty
|
makeWebDavRemote enableSpecialRemote name creds (const noop) M.empty
|
||||||
Nothing
|
Nothing
|
||||||
| "box.com/" `isInfixOf` url ->
|
| "box.com/" `isInfixOf` url ->
|
||||||
boxConfigurator $ showform name url
|
boxConfigurator $ showform name url
|
||||||
|
@ -111,11 +111,11 @@ postEnableWebDAVR uuid = do
|
||||||
defcreds <- liftAnnex $
|
defcreds <- liftAnnex $
|
||||||
maybe (pure Nothing) previouslyUsedWebDAVCreds $
|
maybe (pure Nothing) previouslyUsedWebDAVCreds $
|
||||||
urlHost url
|
urlHost url
|
||||||
((result, form), enctype) <- lift $
|
((result, form), enctype) <- liftH $
|
||||||
runFormPost $ renderBootstrap $ webDAVCredsAForm defcreds
|
runFormPost $ renderBootstrap $ webDAVCredsAForm defcreds
|
||||||
case result of
|
case result of
|
||||||
FormSuccess input -> lift $
|
FormSuccess input -> liftH $
|
||||||
makeWebDavRemote name (toCredPair input) (const noop) M.empty
|
makeWebDavRemote enableSpecialRemote name (toCredPair input) (const noop) M.empty
|
||||||
_ -> do
|
_ -> do
|
||||||
description <- liftAnnex $
|
description <- liftAnnex $
|
||||||
T.pack <$> Remote.prettyUUID uuid
|
T.pack <$> Remote.prettyUUID uuid
|
||||||
|
@ -125,13 +125,10 @@ postEnableWebDAVR _ = error "WebDAV not supported by this build"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef WITH_WEBDAV
|
#ifdef WITH_WEBDAV
|
||||||
makeWebDavRemote :: String -> CredPair -> (Remote -> Handler ()) -> RemoteConfig -> Handler ()
|
makeWebDavRemote :: SpecialRemoteMaker -> String -> CredPair -> (Remote -> Handler ()) -> RemoteConfig -> Handler ()
|
||||||
makeWebDavRemote name creds setup config = do
|
makeWebDavRemote maker name creds setup config = do
|
||||||
remotename <- liftAnnex $ fromRepo $ uniqueRemoteName name 0
|
|
||||||
liftIO $ WebDAV.setCredsEnv creds
|
liftIO $ WebDAV.setCredsEnv creds
|
||||||
r <- liftAnnex $ addRemote $ do
|
r <- liftAnnex $ addRemote $ maker name WebDAV.remote config
|
||||||
makeSpecialRemote name WebDAV.remote config
|
|
||||||
return remotename
|
|
||||||
setup r
|
setup r
|
||||||
liftAssistant $ syncRemote r
|
liftAssistant $ syncRemote r
|
||||||
redirect $ EditNewCloudRepositoryR $ Remote.uuid r
|
redirect $ EditNewCloudRepositoryR $ Remote.uuid r
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
- Licensed under the GNU AGPL version 3 or higher.
|
- Licensed under the GNU AGPL version 3 or higher.
|
||||||
-}
|
-}
|
||||||
|
|
||||||
{-# LANGUAGE TypeFamilies, QuasiQuotes, MultiParamTypeClasses, TemplateHaskell, OverloadedStrings #-}
|
{-# LANGUAGE TypeFamilies, QuasiQuotes, TemplateHaskell, OverloadedStrings, FlexibleContexts #-}
|
||||||
{-# LANGUAGE CPP #-}
|
{-# LANGUAGE CPP #-}
|
||||||
|
|
||||||
module Assistant.WebApp.Configurators.XMPP where
|
module Assistant.WebApp.Configurators.XMPP where
|
||||||
|
@ -13,25 +13,23 @@ module Assistant.WebApp.Configurators.XMPP where
|
||||||
import Assistant.WebApp.Common
|
import Assistant.WebApp.Common
|
||||||
import Assistant.WebApp.Notifications
|
import Assistant.WebApp.Notifications
|
||||||
import Utility.NotificationBroadcaster
|
import Utility.NotificationBroadcaster
|
||||||
import qualified Remote
|
|
||||||
#ifdef WITH_XMPP
|
#ifdef WITH_XMPP
|
||||||
|
import qualified Remote
|
||||||
import Assistant.XMPP.Client
|
import Assistant.XMPP.Client
|
||||||
import Assistant.XMPP.Buddies
|
import Assistant.XMPP.Buddies
|
||||||
import Assistant.Types.Buddies
|
import Assistant.Types.Buddies
|
||||||
import Assistant.NetMessager
|
import Assistant.NetMessager
|
||||||
import Assistant.Alert
|
import Assistant.Alert
|
||||||
import Assistant.DaemonStatus
|
import Assistant.DaemonStatus
|
||||||
import Utility.SRV
|
|
||||||
import Assistant.WebApp.RepoList
|
import Assistant.WebApp.RepoList
|
||||||
import Assistant.WebApp.Configurators
|
import Assistant.WebApp.Configurators
|
||||||
import Assistant.XMPP
|
import Assistant.XMPP
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef WITH_XMPP
|
#ifdef WITH_XMPP
|
||||||
import Network
|
|
||||||
import Network.Protocol.XMPP
|
import Network.Protocol.XMPP
|
||||||
|
import Network
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import Control.Exception (SomeException)
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
{- Displays an alert suggesting to configure XMPP. -}
|
{- Displays an alert suggesting to configure XMPP. -}
|
||||||
|
@ -81,7 +79,7 @@ getBuddyName u = go =<< getclientjid
|
||||||
<$> getDaemonStatus
|
<$> getDaemonStatus
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
getNeedCloudRepoR :: UUID -> Handler RepHtml
|
getNeedCloudRepoR :: UUID -> Handler Html
|
||||||
#ifdef WITH_XMPP
|
#ifdef WITH_XMPP
|
||||||
getNeedCloudRepoR for = page "Cloud repository needed" (Just Configuration) $ do
|
getNeedCloudRepoR for = page "Cloud repository needed" (Just Configuration) $ do
|
||||||
buddyname <- liftAssistant $ getBuddyName for
|
buddyname <- liftAssistant $ getBuddyName for
|
||||||
|
@ -91,34 +89,34 @@ getNeedCloudRepoR _ = xmppPage $
|
||||||
$(widgetFile "configurators/xmpp/disabled")
|
$(widgetFile "configurators/xmpp/disabled")
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
getXMPPConfigR :: Handler RepHtml
|
getXMPPConfigR :: Handler Html
|
||||||
getXMPPConfigR = postXMPPConfigR
|
getXMPPConfigR = postXMPPConfigR
|
||||||
|
|
||||||
postXMPPConfigR :: Handler RepHtml
|
postXMPPConfigR :: Handler Html
|
||||||
postXMPPConfigR = xmppform DashboardR
|
postXMPPConfigR = xmppform DashboardR
|
||||||
|
|
||||||
getXMPPConfigForPairFriendR :: Handler RepHtml
|
getXMPPConfigForPairFriendR :: Handler Html
|
||||||
getXMPPConfigForPairFriendR = postXMPPConfigForPairFriendR
|
getXMPPConfigForPairFriendR = postXMPPConfigForPairFriendR
|
||||||
|
|
||||||
postXMPPConfigForPairFriendR :: Handler RepHtml
|
postXMPPConfigForPairFriendR :: Handler Html
|
||||||
postXMPPConfigForPairFriendR = xmppform StartXMPPPairFriendR
|
postXMPPConfigForPairFriendR = xmppform StartXMPPPairFriendR
|
||||||
|
|
||||||
getXMPPConfigForPairSelfR :: Handler RepHtml
|
getXMPPConfigForPairSelfR :: Handler Html
|
||||||
getXMPPConfigForPairSelfR = postXMPPConfigForPairSelfR
|
getXMPPConfigForPairSelfR = postXMPPConfigForPairSelfR
|
||||||
|
|
||||||
postXMPPConfigForPairSelfR :: Handler RepHtml
|
postXMPPConfigForPairSelfR :: Handler Html
|
||||||
postXMPPConfigForPairSelfR = xmppform StartXMPPPairSelfR
|
postXMPPConfigForPairSelfR = xmppform StartXMPPPairSelfR
|
||||||
|
|
||||||
xmppform :: Route WebApp -> Handler RepHtml
|
xmppform :: Route WebApp -> Handler Html
|
||||||
#ifdef WITH_XMPP
|
#ifdef WITH_XMPP
|
||||||
xmppform next = xmppPage $ do
|
xmppform next = xmppPage $ do
|
||||||
((result, form), enctype) <- lift $ do
|
((result, form), enctype) <- liftH $ do
|
||||||
oldcreds <- liftAnnex getXMPPCreds
|
oldcreds <- liftAnnex getXMPPCreds
|
||||||
runFormPost $ renderBootstrap $ xmppAForm $
|
runFormPost $ renderBootstrap $ xmppAForm $
|
||||||
creds2Form <$> oldcreds
|
creds2Form <$> oldcreds
|
||||||
let showform problem = $(widgetFile "configurators/xmpp")
|
let showform problem = $(widgetFile "configurators/xmpp")
|
||||||
case result of
|
case result of
|
||||||
FormSuccess f -> either (showform . Just . show) (lift . storecreds)
|
FormSuccess f -> either (showform . Just) (liftH . storecreds)
|
||||||
=<< liftIO (validateForm f)
|
=<< liftIO (validateForm f)
|
||||||
_ -> showform Nothing
|
_ -> showform Nothing
|
||||||
where
|
where
|
||||||
|
@ -135,12 +133,12 @@ xmppform _ = xmppPage $
|
||||||
-
|
-
|
||||||
- Returns a div, which will be inserted into the calling page.
|
- Returns a div, which will be inserted into the calling page.
|
||||||
-}
|
-}
|
||||||
getBuddyListR :: NotificationId -> Handler RepHtml
|
getBuddyListR :: NotificationId -> Handler Html
|
||||||
getBuddyListR nid = do
|
getBuddyListR nid = do
|
||||||
waitNotifier getBuddyListBroadcaster nid
|
waitNotifier getBuddyListBroadcaster nid
|
||||||
|
|
||||||
p <- widgetToPageContent buddyListDisplay
|
p <- widgetToPageContent buddyListDisplay
|
||||||
hamletToRepHtml $ [hamlet|^{pageBody p}|]
|
giveUrlRenderer $ [hamlet|^{pageBody p}|]
|
||||||
|
|
||||||
buddyListDisplay :: Widget
|
buddyListDisplay :: Widget
|
||||||
buddyListDisplay = do
|
buddyListDisplay = do
|
||||||
|
@ -173,44 +171,50 @@ data XMPPForm = XMPPForm
|
||||||
creds2Form :: XMPPCreds -> XMPPForm
|
creds2Form :: XMPPCreds -> XMPPForm
|
||||||
creds2Form c = XMPPForm (xmppJID c) (xmppPassword c)
|
creds2Form c = XMPPForm (xmppJID c) (xmppPassword c)
|
||||||
|
|
||||||
xmppAForm :: (Maybe XMPPForm) -> AForm WebApp WebApp XMPPForm
|
xmppAForm :: (Maybe XMPPForm) -> MkAForm XMPPForm
|
||||||
xmppAForm def = XMPPForm
|
xmppAForm def = XMPPForm
|
||||||
<$> areq jidField "Jabber address" (formJID <$> def)
|
<$> areq jidField "Jabber address" (formJID <$> def)
|
||||||
<*> areq passwordField "Password" Nothing
|
<*> areq passwordField "Password" Nothing
|
||||||
|
|
||||||
jidField :: Field WebApp WebApp Text
|
jidField :: MkField Text
|
||||||
jidField = checkBool (isJust . parseJID) bad textField
|
jidField = checkBool (isJust . parseJID) bad textField
|
||||||
where
|
where
|
||||||
bad :: Text
|
bad :: Text
|
||||||
bad = "This should look like an email address.."
|
bad = "This should look like an email address.."
|
||||||
|
|
||||||
validateForm :: XMPPForm -> IO (Either SomeException XMPPCreds)
|
validateForm :: XMPPForm -> IO (Either String XMPPCreds)
|
||||||
validateForm f = do
|
validateForm f = do
|
||||||
let jid = fromMaybe (error "bad JID") $ parseJID (formJID f)
|
let jid = fromMaybe (error "bad JID") $ parseJID (formJID f)
|
||||||
let domain = T.unpack $ strDomain $ jidDomain jid
|
|
||||||
hostports <- lookupSRV $ mkSRVTcp "xmpp-client" domain
|
|
||||||
let username = fromMaybe "" (strNode <$> jidNode jid)
|
let username = fromMaybe "" (strNode <$> jidNode jid)
|
||||||
case hostports of
|
testXMPP $ XMPPCreds
|
||||||
((h, PortNumber p):_) -> testXMPP $ XMPPCreds
|
{ xmppUsername = username
|
||||||
{ xmppUsername = username
|
, xmppPassword = formPassword f
|
||||||
, xmppPassword = formPassword f
|
, xmppHostname = T.unpack $ strDomain $ jidDomain jid
|
||||||
, xmppHostname = h
|
, xmppPort = 5222
|
||||||
|
, xmppJID = formJID f
|
||||||
|
}
|
||||||
|
|
||||||
|
testXMPP :: XMPPCreds -> IO (Either String XMPPCreds)
|
||||||
|
testXMPP creds = do
|
||||||
|
(good, bad) <- partition (either (const False) (const True) . snd)
|
||||||
|
<$> connectXMPP creds (const noop)
|
||||||
|
case good of
|
||||||
|
(((h, PortNumber p), _):_) -> return $ Right $ creds
|
||||||
|
{ xmppHostname = h
|
||||||
, xmppPort = fromIntegral p
|
, xmppPort = fromIntegral p
|
||||||
, xmppJID = formJID f
|
|
||||||
}
|
}
|
||||||
_ -> testXMPP $ XMPPCreds
|
(((h, _), _):_) -> return $ Right $ creds
|
||||||
{ xmppUsername = username
|
{ xmppHostname = h
|
||||||
, xmppPassword = formPassword f
|
|
||||||
, xmppHostname = T.unpack $ strDomain $ jidDomain jid
|
|
||||||
, xmppPort = 5222
|
|
||||||
, xmppJID = formJID f
|
|
||||||
}
|
}
|
||||||
|
_ -> return $ Left $ intercalate "; " $ map formatlog bad
|
||||||
|
where
|
||||||
|
formatlog ((h, p), Left e) = "host " ++ h ++ ":" ++ showport p ++ " failed: " ++ show e
|
||||||
|
formatlog _ = ""
|
||||||
|
|
||||||
testXMPP :: XMPPCreds -> IO (Either SomeException XMPPCreds)
|
showport (PortNumber n) = show n
|
||||||
testXMPP creds = either Left (const $ Right creds)
|
showport (Service s) = s
|
||||||
<$> connectXMPP creds (const noop)
|
showport (UnixSocket s) = s
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
xmppPage :: Widget -> Handler RepHtml
|
xmppPage :: Widget -> Handler Html
|
||||||
xmppPage = page "Jabber" (Just Configuration)
|
xmppPage = page "Jabber" (Just Configuration)
|
||||||
|
|
|
@ -20,11 +20,11 @@ import Control.Concurrent
|
||||||
import System.Posix (getProcessID, signalProcess, sigTERM)
|
import System.Posix (getProcessID, signalProcess, sigTERM)
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
|
|
||||||
getShutdownR :: Handler RepHtml
|
getShutdownR :: Handler Html
|
||||||
getShutdownR = page "Shutdown" Nothing $
|
getShutdownR = page "Shutdown" Nothing $
|
||||||
$(widgetFile "control/shutdown")
|
$(widgetFile "control/shutdown")
|
||||||
|
|
||||||
getShutdownConfirmedR :: Handler RepHtml
|
getShutdownConfirmedR :: Handler Html
|
||||||
getShutdownConfirmedR = do
|
getShutdownConfirmedR = do
|
||||||
{- Remove all alerts for currently running activities. -}
|
{- Remove all alerts for currently running activities. -}
|
||||||
liftAssistant $ do
|
liftAssistant $ do
|
||||||
|
@ -45,7 +45,7 @@ getShutdownConfirmedR = do
|
||||||
$(widgetFile "control/shutdownconfirmed")
|
$(widgetFile "control/shutdownconfirmed")
|
||||||
|
|
||||||
{- Quite a hack, and doesn't redirect the browser window. -}
|
{- Quite a hack, and doesn't redirect the browser window. -}
|
||||||
getRestartR :: Handler RepHtml
|
getRestartR :: Handler Html
|
||||||
getRestartR = page "Restarting" Nothing $ do
|
getRestartR = page "Restarting" Nothing $ do
|
||||||
void $ liftIO $ forkIO $ do
|
void $ liftIO $ forkIO $ do
|
||||||
threadDelay 2000000
|
threadDelay 2000000
|
||||||
|
@ -54,7 +54,7 @@ getRestartR = page "Restarting" Nothing $ do
|
||||||
error "restart failed"
|
error "restart failed"
|
||||||
$(widgetFile "control/restarting")
|
$(widgetFile "control/restarting")
|
||||||
where
|
where
|
||||||
restartcommand program = program ++ " assistant --stop; " ++
|
restartcommand program = program ++ " assistant --stop; exec " ++
|
||||||
program ++ " webapp"
|
program ++ " webapp"
|
||||||
|
|
||||||
getRestartThreadR :: ThreadName -> Handler ()
|
getRestartThreadR :: ThreadName -> Handler ()
|
||||||
|
@ -63,7 +63,7 @@ getRestartThreadR name = do
|
||||||
liftIO $ maybe noop snd $ M.lookup name m
|
liftIO $ maybe noop snd $ M.lookup name m
|
||||||
redirectBack
|
redirectBack
|
||||||
|
|
||||||
getLogR :: Handler RepHtml
|
getLogR :: Handler Html
|
||||||
getLogR = page "Logs" Nothing $ do
|
getLogR = page "Logs" Nothing $ do
|
||||||
logfile <- liftAnnex $ fromRepo gitAnnexLogFile
|
logfile <- liftAnnex $ fromRepo gitAnnexLogFile
|
||||||
logs <- liftIO $ listLogs logfile
|
logs <- liftIO $ listLogs logfile
|
||||||
|
|
|
@ -23,15 +23,15 @@ import Types.Key
|
||||||
import qualified Remote
|
import qualified Remote
|
||||||
import qualified Git
|
import qualified Git
|
||||||
|
|
||||||
import Text.Hamlet
|
import qualified Text.Hamlet as Hamlet
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
import Control.Concurrent
|
import Control.Concurrent
|
||||||
|
|
||||||
{- A display of currently running and queued transfers. -}
|
{- A display of currently running and queued transfers. -}
|
||||||
transfersDisplay :: Bool -> Widget
|
transfersDisplay :: Bool -> Widget
|
||||||
transfersDisplay warnNoScript = do
|
transfersDisplay warnNoScript = do
|
||||||
webapp <- lift getYesod
|
webapp <- liftH getYesod
|
||||||
current <- lift $ M.toList <$> getCurrentTransfers
|
current <- liftH $ M.toList <$> getCurrentTransfers
|
||||||
queued <- take 10 <$> liftAssistant getTransferQueue
|
queued <- take 10 <$> liftAssistant getTransferQueue
|
||||||
autoUpdate ident NotifierTransfersR (10 :: Int) (10 :: Int)
|
autoUpdate ident NotifierTransfersR (10 :: Int) (10 :: Int)
|
||||||
let transfers = simplifyTransfers $ current ++ queued
|
let transfers = simplifyTransfers $ current ++ queued
|
||||||
|
@ -62,12 +62,12 @@ simplifyTransfers (v@(t1, _):r@((t2, _):l))
|
||||||
- body is. To get the widget head content, the widget is also
|
- body is. To get the widget head content, the widget is also
|
||||||
- inserted onto the getDashboardR page.
|
- inserted onto the getDashboardR page.
|
||||||
-}
|
-}
|
||||||
getTransfersR :: NotificationId -> Handler RepHtml
|
getTransfersR :: NotificationId -> Handler Html
|
||||||
getTransfersR nid = do
|
getTransfersR nid = do
|
||||||
waitNotifier getTransferBroadcaster nid
|
waitNotifier getTransferBroadcaster nid
|
||||||
|
|
||||||
p <- widgetToPageContent $ transfersDisplay False
|
p <- widgetToPageContent $ transfersDisplay False
|
||||||
hamletToRepHtml $ [hamlet|^{pageBody p}|]
|
giveUrlRenderer $ [hamlet|^{pageBody p}|]
|
||||||
|
|
||||||
{- The main dashboard. -}
|
{- The main dashboard. -}
|
||||||
dashboard :: Bool -> Widget
|
dashboard :: Bool -> Widget
|
||||||
|
@ -77,7 +77,7 @@ dashboard warnNoScript = do
|
||||||
let transferlist = transfersDisplay warnNoScript
|
let transferlist = transfersDisplay warnNoScript
|
||||||
$(widgetFile "dashboard/main")
|
$(widgetFile "dashboard/main")
|
||||||
|
|
||||||
getDashboardR :: Handler RepHtml
|
getDashboardR :: Handler Html
|
||||||
getDashboardR = ifM (inFirstRun)
|
getDashboardR = ifM (inFirstRun)
|
||||||
( redirect ConfigurationR
|
( redirect ConfigurationR
|
||||||
, page "" (Just DashBoard) $ dashboard True
|
, page "" (Just DashBoard) $ dashboard True
|
||||||
|
@ -88,16 +88,16 @@ headDashboardR :: Handler ()
|
||||||
headDashboardR = noop
|
headDashboardR = noop
|
||||||
|
|
||||||
{- Same as DashboardR, except no autorefresh at all (and no noscript warning). -}
|
{- Same as DashboardR, except no autorefresh at all (and no noscript warning). -}
|
||||||
getNoScriptR :: Handler RepHtml
|
getNoScriptR :: Handler Html
|
||||||
getNoScriptR = page "" (Just DashBoard) $ dashboard False
|
getNoScriptR = page "" (Just DashBoard) $ dashboard False
|
||||||
|
|
||||||
{- Same as DashboardR, except with autorefreshing via meta refresh. -}
|
{- Same as DashboardR, except with autorefreshing via meta refresh. -}
|
||||||
getNoScriptAutoR :: Handler RepHtml
|
getNoScriptAutoR :: Handler Html
|
||||||
getNoScriptAutoR = page "" (Just DashBoard) $ do
|
getNoScriptAutoR = page "" (Just DashBoard) $ do
|
||||||
let ident = NoScriptR
|
let ident = NoScriptR
|
||||||
let delayseconds = 3 :: Int
|
let delayseconds = 3 :: Int
|
||||||
let this = NoScriptAutoR
|
let this = NoScriptAutoR
|
||||||
toWidgetHead $(hamletFile $ hamletTemplate "dashboard/metarefresh")
|
toWidgetHead $(Hamlet.hamletFile $ hamletTemplate "dashboard/metarefresh")
|
||||||
dashboard False
|
dashboard False
|
||||||
|
|
||||||
{- The javascript code does a post. -}
|
{- The javascript code does a post. -}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
- Licensed under the GNU AGPL version 3 or higher.
|
- Licensed under the GNU AGPL version 3 or higher.
|
||||||
-}
|
-}
|
||||||
|
|
||||||
{-# LANGUAGE TypeFamilies, QuasiQuotes, MultiParamTypeClasses, TemplateHaskell, OverloadedStrings, RankNTypes #-}
|
{-# LANGUAGE QuasiQuotes, TemplateHaskell, OverloadedStrings #-}
|
||||||
|
|
||||||
module Assistant.WebApp.Documentation where
|
module Assistant.WebApp.Documentation where
|
||||||
|
|
||||||
|
@ -21,12 +21,12 @@ licenseFile = do
|
||||||
base <- standaloneAppBase
|
base <- standaloneAppBase
|
||||||
return $ (</> "LICENSE") <$> base
|
return $ (</> "LICENSE") <$> base
|
||||||
|
|
||||||
getAboutR :: Handler RepHtml
|
getAboutR :: Handler Html
|
||||||
getAboutR = page "About git-annex" (Just About) $ do
|
getAboutR = page "About git-annex" (Just About) $ do
|
||||||
builtinlicense <- isJust <$> liftIO licenseFile
|
builtinlicense <- isJust <$> liftIO licenseFile
|
||||||
$(widgetFile "documentation/about")
|
$(widgetFile "documentation/about")
|
||||||
|
|
||||||
getLicenseR :: Handler RepHtml
|
getLicenseR :: Handler Html
|
||||||
getLicenseR = do
|
getLicenseR = do
|
||||||
v <- liftIO licenseFile
|
v <- liftIO licenseFile
|
||||||
case v of
|
case v of
|
||||||
|
@ -37,6 +37,6 @@ getLicenseR = do
|
||||||
license <- liftIO $ readFile f
|
license <- liftIO $ readFile f
|
||||||
$(widgetFile "documentation/license")
|
$(widgetFile "documentation/license")
|
||||||
|
|
||||||
getRepoGroupR :: Handler RepHtml
|
getRepoGroupR :: Handler Html
|
||||||
getRepoGroupR = page "About repository groups" (Just About) $ do
|
getRepoGroupR = page "About repository groups" (Just About) $ do
|
||||||
$(widgetFile "documentation/repogroup")
|
$(widgetFile "documentation/repogroup")
|
||||||
|
|
|
@ -8,10 +8,12 @@
|
||||||
{-# LANGUAGE FlexibleContexts, TypeFamilies, QuasiQuotes #-}
|
{-# LANGUAGE FlexibleContexts, TypeFamilies, QuasiQuotes #-}
|
||||||
{-# LANGUAGE MultiParamTypeClasses, TemplateHaskell #-}
|
{-# LANGUAGE MultiParamTypeClasses, TemplateHaskell #-}
|
||||||
{-# LANGUAGE OverloadedStrings, RankNTypes #-}
|
{-# LANGUAGE OverloadedStrings, RankNTypes #-}
|
||||||
|
{-# LANGUAGE CPP #-}
|
||||||
|
|
||||||
module Assistant.WebApp.Form where
|
module Assistant.WebApp.Form where
|
||||||
|
|
||||||
import Types.Remote (RemoteConfigKey)
|
import Types.Remote (RemoteConfigKey)
|
||||||
|
import Assistant.WebApp.Types
|
||||||
|
|
||||||
import Yesod hiding (textField, passwordField)
|
import Yesod hiding (textField, passwordField)
|
||||||
import Yesod.Form.Fields as F
|
import Yesod.Form.Fields as F
|
||||||
|
@ -24,15 +26,22 @@ import Data.Text (Text)
|
||||||
-
|
-
|
||||||
- Required fields are still checked by Yesod.
|
- Required fields are still checked by Yesod.
|
||||||
-}
|
-}
|
||||||
textField :: RenderMessage master FormMessage => Field sub master Text
|
textField :: MkField Text
|
||||||
textField = F.textField
|
textField = F.textField
|
||||||
{ fieldView = \theId name attrs val _isReq -> [whamlet|
|
{ fieldView = \theId name attrs val _isReq -> [whamlet|
|
||||||
<input id="#{theId}" name="#{name}" *{attrs} type="text" value="#{either id id val}">
|
<input id="#{theId}" name="#{name}" *{attrs} type="text" value="#{either id id val}">
|
||||||
|]
|
|]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readonlyTextField :: MkField Text
|
||||||
|
readonlyTextField = F.textField
|
||||||
|
{ fieldView = \theId name attrs val _isReq -> [whamlet|
|
||||||
|
<input id="#{theId}" name="#{name}" *{attrs} type="text" value="#{either id id val}" readonly="true">
|
||||||
|
|]
|
||||||
|
}
|
||||||
|
|
||||||
{- Also without required attribute. -}
|
{- Also without required attribute. -}
|
||||||
passwordField :: RenderMessage master FormMessage => Field sub master Text
|
passwordField :: MkField Text
|
||||||
passwordField = F.passwordField
|
passwordField = F.passwordField
|
||||||
{ fieldView = \theId name attrs val _isReq -> toWidget [hamlet|
|
{ fieldView = \theId name attrs val _isReq -> toWidget [hamlet|
|
||||||
<input id="#{theId}" name="#{name}" *{attrs} type="password" value="#{either id id val}">
|
<input id="#{theId}" name="#{name}" *{attrs} type="password" value="#{either id id val}">
|
||||||
|
@ -40,7 +49,11 @@ passwordField = F.passwordField
|
||||||
}
|
}
|
||||||
|
|
||||||
{- Makes a note widget be displayed after a field. -}
|
{- Makes a note widget be displayed after a field. -}
|
||||||
|
#if MIN_VERSION_yesod(1,2,0)
|
||||||
|
withNote :: (Monad m, ToWidget (HandlerSite m) a) => Field m v -> a -> Field m v
|
||||||
|
#else
|
||||||
withNote :: Field sub master v -> GWidget sub master () -> Field sub master v
|
withNote :: Field sub master v -> GWidget sub master () -> Field sub master v
|
||||||
|
#endif
|
||||||
withNote field note = field { fieldView = newview }
|
withNote field note = field { fieldView = newview }
|
||||||
where
|
where
|
||||||
newview theId name attrs val isReq =
|
newview theId name attrs val isReq =
|
||||||
|
@ -48,7 +61,11 @@ withNote field note = field { fieldView = newview }
|
||||||
in [whamlet|^{fieldwidget} <span>^{note}</span>|]
|
in [whamlet|^{fieldwidget} <span>^{note}</span>|]
|
||||||
|
|
||||||
{- Note that the toggle string must be unique on the form. -}
|
{- Note that the toggle string must be unique on the form. -}
|
||||||
|
#if MIN_VERSION_yesod(1,2,0)
|
||||||
|
withExpandableNote :: (Monad m, ToWidget (HandlerSite m) w) => Field m v -> (String, w) -> Field m v
|
||||||
|
#else
|
||||||
withExpandableNote :: Field sub master v -> (String, GWidget sub master ()) -> Field sub master v
|
withExpandableNote :: Field sub master v -> (String, GWidget sub master ()) -> Field sub master v
|
||||||
|
#endif
|
||||||
withExpandableNote field (toggle, note) = withNote field $ [whamlet|
|
withExpandableNote field (toggle, note) = withNote field $ [whamlet|
|
||||||
<a .btn data-toggle="collapse" data-target="##{ident}">
|
<a .btn data-toggle="collapse" data-target="##{ident}">
|
||||||
#{toggle}
|
#{toggle}
|
||||||
|
@ -62,7 +79,11 @@ data EnableEncryption = SharedEncryption | NoEncryption
|
||||||
deriving (Eq)
|
deriving (Eq)
|
||||||
|
|
||||||
{- Adds a check box to an AForm to control encryption. -}
|
{- Adds a check box to an AForm to control encryption. -}
|
||||||
|
#if MIN_VERSION_yesod(1,2,0)
|
||||||
|
enableEncryptionField :: (RenderMessage site FormMessage) => AForm (HandlerT site IO) EnableEncryption
|
||||||
|
#else
|
||||||
enableEncryptionField :: RenderMessage master FormMessage => AForm sub master EnableEncryption
|
enableEncryptionField :: RenderMessage master FormMessage => AForm sub master EnableEncryption
|
||||||
|
#endif
|
||||||
enableEncryptionField = areq (selectFieldList choices) "Encryption" (Just SharedEncryption)
|
enableEncryptionField = areq (selectFieldList choices) "Encryption" (Just SharedEncryption)
|
||||||
where
|
where
|
||||||
choices :: [(Text, EnableEncryption)]
|
choices :: [(Text, EnableEncryption)]
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
- Licensed under the GNU AGPL version 3 or higher.
|
- Licensed under the GNU AGPL version 3 or higher.
|
||||||
-}
|
-}
|
||||||
|
|
||||||
{-# LANGUAGE CPP, TypeFamilies, QuasiQuotes, MultiParamTypeClasses, TemplateHaskell, OverloadedStrings, RankNTypes #-}
|
{-# LANGUAGE CPP, QuasiQuotes, TemplateHaskell, OverloadedStrings #-}
|
||||||
|
|
||||||
#if defined VERSION_yesod_default
|
#if defined VERSION_yesod_default
|
||||||
#if ! MIN_VERSION_yesod_default(1,1,0)
|
#if ! MIN_VERSION_yesod_default(1,1,0)
|
||||||
|
@ -23,7 +23,6 @@ import Assistant.Types.Buddies
|
||||||
import Utility.NotificationBroadcaster
|
import Utility.NotificationBroadcaster
|
||||||
import Utility.Yesod
|
import Utility.Yesod
|
||||||
|
|
||||||
import Yesod
|
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
#ifndef WITH_OLD_YESOD
|
#ifndef WITH_OLD_YESOD
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
- Licensed under the GNU AGPL version 3 or higher.
|
- Licensed under the GNU AGPL version 3 or higher.
|
||||||
-}
|
-}
|
||||||
|
|
||||||
{-# LANGUAGE CPP, TypeFamilies, QuasiQuotes, MultiParamTypeClasses, TemplateHaskell, OverloadedStrings, RankNTypes #-}
|
{-# LANGUAGE QuasiQuotes, TemplateHaskell, OverloadedStrings #-}
|
||||||
|
|
||||||
module Assistant.WebApp.OtherRepos where
|
module Assistant.WebApp.OtherRepos where
|
||||||
|
|
||||||
|
@ -18,11 +18,10 @@ import Config.Files
|
||||||
import qualified Utility.Url as Url
|
import qualified Utility.Url as Url
|
||||||
import Utility.Yesod
|
import Utility.Yesod
|
||||||
|
|
||||||
import Yesod
|
|
||||||
import Control.Concurrent
|
import Control.Concurrent
|
||||||
import System.Process (cwd)
|
import System.Process (cwd)
|
||||||
|
|
||||||
getRepositorySwitcherR :: Handler RepHtml
|
getRepositorySwitcherR :: Handler Html
|
||||||
getRepositorySwitcherR = page "Switch repository" Nothing $ do
|
getRepositorySwitcherR = page "Switch repository" Nothing $ do
|
||||||
repolist <- liftIO listOtherRepos
|
repolist <- liftIO listOtherRepos
|
||||||
$(widgetFile "control/repositoryswitcher")
|
$(widgetFile "control/repositoryswitcher")
|
||||||
|
@ -40,9 +39,10 @@ listOtherRepos = do
|
||||||
- a gitAnnexUrlFile. Waits for the assistant to be up and listening for
|
- a gitAnnexUrlFile. Waits for the assistant to be up and listening for
|
||||||
- connections by testing the url. Once it's running, redirect to it.
|
- connections by testing the url. Once it's running, redirect to it.
|
||||||
-}
|
-}
|
||||||
getSwitchToRepositoryR :: FilePath -> Handler RepHtml
|
getSwitchToRepositoryR :: FilePath -> Handler Html
|
||||||
getSwitchToRepositoryR repo = do
|
getSwitchToRepositoryR repo = do
|
||||||
liftIO $ startAssistant repo
|
liftIO $ startAssistant repo
|
||||||
|
liftIO $ addAutoStartFile repo -- make this the new default repo
|
||||||
redirect =<< liftIO geturl
|
redirect =<< liftIO geturl
|
||||||
where
|
where
|
||||||
geturl = do
|
geturl = do
|
||||||
|
|
|
@ -15,8 +15,7 @@ import Assistant.WebApp.Types
|
||||||
import Assistant.WebApp.SideBar
|
import Assistant.WebApp.SideBar
|
||||||
import Utility.Yesod
|
import Utility.Yesod
|
||||||
|
|
||||||
import Yesod
|
import qualified Text.Hamlet as Hamlet
|
||||||
import Text.Hamlet
|
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
|
|
||||||
data NavBarItem = DashBoard | Configuration | About
|
data NavBarItem = DashBoard | Configuration | About
|
||||||
|
@ -43,14 +42,14 @@ selectNavBar = ifM (inFirstRun) (return firstRunNavBar, return defaultNavBar)
|
||||||
|
|
||||||
{- A standard page of the webapp, with a title, a sidebar, and that may
|
{- A standard page of the webapp, with a title, a sidebar, and that may
|
||||||
- be highlighted on the navbar. -}
|
- be highlighted on the navbar. -}
|
||||||
page :: Html -> Maybe NavBarItem -> Widget -> Handler RepHtml
|
page :: Hamlet.Html -> Maybe NavBarItem -> Widget -> Handler Html
|
||||||
page title navbaritem content = customPage navbaritem $ do
|
page title navbaritem content = customPage navbaritem $ do
|
||||||
setTitle title
|
setTitle title
|
||||||
sideBarDisplay
|
sideBarDisplay
|
||||||
content
|
content
|
||||||
|
|
||||||
{- A custom page, with no title or sidebar set. -}
|
{- A custom page, with no title or sidebar set. -}
|
||||||
customPage :: Maybe NavBarItem -> Widget -> Handler RepHtml
|
customPage :: Maybe NavBarItem -> Widget -> Handler Html
|
||||||
customPage navbaritem content = do
|
customPage navbaritem content = do
|
||||||
webapp <- getYesod
|
webapp <- getYesod
|
||||||
navbar <- map navdetails <$> selectNavBar
|
navbar <- map navdetails <$> selectNavBar
|
||||||
|
@ -62,7 +61,7 @@ customPage navbaritem content = do
|
||||||
addScript $ StaticR js_bootstrap_modal_js
|
addScript $ StaticR js_bootstrap_modal_js
|
||||||
addScript $ StaticR js_bootstrap_collapse_js
|
addScript $ StaticR js_bootstrap_collapse_js
|
||||||
$(widgetFile "page")
|
$(widgetFile "page")
|
||||||
hamletToRepHtml $(hamletFile $ hamletTemplate "bootstrap")
|
giveUrlRenderer $(Hamlet.hamletFile $ hamletTemplate "bootstrap")
|
||||||
where
|
where
|
||||||
navdetails i = (navBarName i, navBarRoute i, Just i == navbaritem)
|
navdetails i = (navBarName i, navBarRoute i, Just i == navbaritem)
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
- Licensed under the GNU AGPL version 3 or higher.
|
- Licensed under the GNU AGPL version 3 or higher.
|
||||||
-}
|
-}
|
||||||
|
|
||||||
{-# LANGUAGE TypeFamilies, QuasiQuotes, MultiParamTypeClasses, TemplateHaskell, OverloadedStrings, RankNTypes, CPP #-}
|
{-# LANGUAGE QuasiQuotes, TemplateHaskell, OverloadedStrings, CPP #-}
|
||||||
|
|
||||||
module Assistant.WebApp.RepoList where
|
module Assistant.WebApp.RepoList where
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ import Assistant.WebApp.Common
|
||||||
import Assistant.DaemonStatus
|
import Assistant.DaemonStatus
|
||||||
import Assistant.WebApp.Notifications
|
import Assistant.WebApp.Notifications
|
||||||
import Assistant.WebApp.Utility
|
import Assistant.WebApp.Utility
|
||||||
|
import Assistant.Ssh
|
||||||
import qualified Annex
|
import qualified Annex
|
||||||
import qualified Remote
|
import qualified Remote
|
||||||
import qualified Types.Remote as Remote
|
import qualified Types.Remote as Remote
|
||||||
|
@ -22,6 +23,8 @@ import Logs.Remote
|
||||||
import Logs.Trust
|
import Logs.Trust
|
||||||
import Logs.Group
|
import Logs.Group
|
||||||
import Config
|
import Config
|
||||||
|
import Git.Config
|
||||||
|
import Assistant.Sync
|
||||||
import Config.Cost
|
import Config.Cost
|
||||||
import qualified Git
|
import qualified Git
|
||||||
#ifdef WITH_XMPP
|
#ifdef WITH_XMPP
|
||||||
|
@ -79,11 +82,11 @@ notWanted _ = False
|
||||||
-
|
-
|
||||||
- Returns a div, which will be inserted into the calling page.
|
- Returns a div, which will be inserted into the calling page.
|
||||||
-}
|
-}
|
||||||
getRepoListR :: RepoListNotificationId -> Handler RepHtml
|
getRepoListR :: RepoListNotificationId -> Handler Html
|
||||||
getRepoListR (RepoListNotificationId nid reposelector) = do
|
getRepoListR (RepoListNotificationId nid reposelector) = do
|
||||||
waitNotifier getRepoListBroadcaster nid
|
waitNotifier getRepoListBroadcaster nid
|
||||||
p <- widgetToPageContent $ repoListDisplay reposelector
|
p <- widgetToPageContent $ repoListDisplay reposelector
|
||||||
hamletToRepHtml $ [hamlet|^{pageBody p}|]
|
giveUrlRenderer $ [hamlet|^{pageBody p}|]
|
||||||
|
|
||||||
mainRepoSelector :: RepoSelector
|
mainRepoSelector :: RepoSelector
|
||||||
mainRepoSelector = RepoSelector
|
mainRepoSelector = RepoSelector
|
||||||
|
@ -110,13 +113,14 @@ repoListDisplay reposelector = do
|
||||||
addScript $ StaticR jquery_ui_mouse_js
|
addScript $ StaticR jquery_ui_mouse_js
|
||||||
addScript $ StaticR jquery_ui_sortable_js
|
addScript $ StaticR jquery_ui_sortable_js
|
||||||
|
|
||||||
repolist <- lift $ repoList reposelector
|
repolist <- liftH $ repoList reposelector
|
||||||
let addmore = nudgeAddMore reposelector
|
let addmore = nudgeAddMore reposelector
|
||||||
let nootherrepos = length repolist < 2
|
let nootherrepos = length repolist < 2
|
||||||
|
|
||||||
$(widgetFile "repolist")
|
$(widgetFile "repolist")
|
||||||
where
|
where
|
||||||
ident = "repolist"
|
ident = "repolist"
|
||||||
|
unfinished uuid = uuid == NoUUID
|
||||||
|
|
||||||
type RepoList = [(String, UUID, Actions)]
|
type RepoList = [(String, UUID, Actions)]
|
||||||
|
|
||||||
|
@ -222,3 +226,30 @@ reorderCosts remote rs = zip rs'' (insertCostAfter costs i)
|
||||||
rs' = filter (\r -> Remote.uuid r /= Remote.uuid remote) rs
|
rs' = filter (\r -> Remote.uuid r /= Remote.uuid remote) rs
|
||||||
costs = map Remote.cost rs'
|
costs = map Remote.cost rs'
|
||||||
rs'' = (\(x, y) -> x ++ [remote] ++ y) $ splitAt (i + 1) rs'
|
rs'' = (\(x, y) -> x ++ [remote] ++ y) $ splitAt (i + 1) rs'
|
||||||
|
|
||||||
|
{- Checks to see if any repositories with NoUUID have annex-ignore set.
|
||||||
|
- That could happen if there's a problem contacting a ssh remote
|
||||||
|
- soon after it was added. -}
|
||||||
|
getCheckUnfinishedRepositoriesR :: Handler Html
|
||||||
|
getCheckUnfinishedRepositoriesR = page "Unfinished repositories" (Just Configuration) $ do
|
||||||
|
stalled <- liftAnnex findStalled
|
||||||
|
$(widgetFile "configurators/checkunfinished")
|
||||||
|
|
||||||
|
findStalled :: Annex [Remote]
|
||||||
|
findStalled = filter isstalled <$> remoteListRefresh
|
||||||
|
where
|
||||||
|
isstalled r = Remote.uuid r == NoUUID
|
||||||
|
&& remoteAnnexIgnore (Remote.gitconfig r)
|
||||||
|
|
||||||
|
getRetryUnfinishedRepositoriesR :: Handler ()
|
||||||
|
getRetryUnfinishedRepositoriesR = do
|
||||||
|
liftAssistant $ mapM_ unstall =<< liftAnnex findStalled
|
||||||
|
redirect DashboardR
|
||||||
|
where
|
||||||
|
unstall r = do
|
||||||
|
liftIO $ fixSshKeyPair
|
||||||
|
liftAnnex $ setConfig
|
||||||
|
(remoteConfig (Remote.repo r) "ignore")
|
||||||
|
(boolConfig False)
|
||||||
|
syncRemote r
|
||||||
|
liftAnnex $ void remoteListRefresh
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
- Licensed under the GNU AGPL version 3 or higher.
|
- Licensed under the GNU AGPL version 3 or higher.
|
||||||
-}
|
-}
|
||||||
|
|
||||||
{-# LANGUAGE TypeFamilies, QuasiQuotes, MultiParamTypeClasses, TemplateHaskell, OverloadedStrings, RankNTypes #-}
|
{-# LANGUAGE QuasiQuotes, TemplateHaskell, OverloadedStrings #-}
|
||||||
|
|
||||||
module Assistant.WebApp.SideBar where
|
module Assistant.WebApp.SideBar where
|
||||||
|
|
||||||
|
@ -18,7 +18,6 @@ import Assistant.DaemonStatus
|
||||||
import Utility.NotificationBroadcaster
|
import Utility.NotificationBroadcaster
|
||||||
import Utility.Yesod
|
import Utility.Yesod
|
||||||
|
|
||||||
import Yesod
|
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
|
@ -28,7 +27,7 @@ sideBarDisplay :: Widget
|
||||||
sideBarDisplay = do
|
sideBarDisplay = do
|
||||||
let content = do
|
let content = do
|
||||||
{- Add newest alerts to the sidebar. -}
|
{- Add newest alerts to the sidebar. -}
|
||||||
alertpairs <- lift $ M.toList . alertMap
|
alertpairs <- liftH $ M.toList . alertMap
|
||||||
<$> liftAssistant getDaemonStatus
|
<$> liftAssistant getDaemonStatus
|
||||||
mapM_ renderalert $
|
mapM_ renderalert $
|
||||||
take displayAlerts $ reverse $ sortAlertPairs alertpairs
|
take displayAlerts $ reverse $ sortAlertPairs alertpairs
|
||||||
|
@ -61,7 +60,7 @@ sideBarDisplay = do
|
||||||
- body is. To get the widget head content, the widget is also
|
- body is. To get the widget head content, the widget is also
|
||||||
- inserted onto all pages.
|
- inserted onto all pages.
|
||||||
-}
|
-}
|
||||||
getSideBarR :: NotificationId -> Handler RepHtml
|
getSideBarR :: NotificationId -> Handler Html
|
||||||
getSideBarR nid = do
|
getSideBarR nid = do
|
||||||
waitNotifier getAlertBroadcaster nid
|
waitNotifier getAlertBroadcaster nid
|
||||||
|
|
||||||
|
@ -73,7 +72,7 @@ getSideBarR nid = do
|
||||||
liftIO $ threadDelay 100000
|
liftIO $ threadDelay 100000
|
||||||
|
|
||||||
page <- widgetToPageContent sideBarDisplay
|
page <- widgetToPageContent sideBarDisplay
|
||||||
hamletToRepHtml $ [hamlet|^{pageBody page}|]
|
giveUrlRenderer $ [hamlet|^{pageBody page}|]
|
||||||
|
|
||||||
{- Called by the client to close an alert. -}
|
{- Called by the client to close an alert. -}
|
||||||
getCloseAlert :: AlertId -> Handler ()
|
getCloseAlert :: AlertId -> Handler ()
|
||||||
|
@ -92,7 +91,7 @@ getClickAlert i = do
|
||||||
redirect $ buttonUrl b
|
redirect $ buttonUrl b
|
||||||
_ -> redirectBack
|
_ -> redirectBack
|
||||||
|
|
||||||
htmlIcon :: AlertIcon -> GWidget WebApp WebApp ()
|
htmlIcon :: AlertIcon -> Widget
|
||||||
htmlIcon ActivityIcon = [whamlet|<img src="@{StaticR activityicon_gif}" alt="">|]
|
htmlIcon ActivityIcon = [whamlet|<img src="@{StaticR activityicon_gif}" alt="">|]
|
||||||
htmlIcon SyncIcon = [whamlet|<img src="@{StaticR syncicon_gif}" alt="">|]
|
htmlIcon SyncIcon = [whamlet|<img src="@{StaticR syncicon_gif}" alt="">|]
|
||||||
htmlIcon InfoIcon = bootstrapIcon "info-sign"
|
htmlIcon InfoIcon = bootstrapIcon "info-sign"
|
||||||
|
@ -101,5 +100,5 @@ htmlIcon ErrorIcon = bootstrapIcon "exclamation-sign"
|
||||||
-- utf-8 umbrella (utf-8 cloud looks too stormy)
|
-- utf-8 umbrella (utf-8 cloud looks too stormy)
|
||||||
htmlIcon TheCloud = [whamlet|☂|]
|
htmlIcon TheCloud = [whamlet|☂|]
|
||||||
|
|
||||||
bootstrapIcon :: Text -> GWidget sub master ()
|
bootstrapIcon :: Text -> Widget
|
||||||
bootstrapIcon name = [whamlet|<i .icon-#{name}></i>|]
|
bootstrapIcon name = [whamlet|<i .icon-#{name}></i>|]
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
|
|
||||||
{-# LANGUAGE TypeFamilies, QuasiQuotes, MultiParamTypeClasses #-}
|
{-# LANGUAGE TypeFamilies, QuasiQuotes, MultiParamTypeClasses #-}
|
||||||
{-# LANGUAGE TemplateHaskell, OverloadedStrings, RankNTypes #-}
|
{-# LANGUAGE TemplateHaskell, OverloadedStrings, RankNTypes #-}
|
||||||
{-# LANGUAGE FlexibleInstances #-}
|
{-# LANGUAGE FlexibleInstances, FlexibleContexts #-}
|
||||||
|
{-# LANGUAGE CPP #-}
|
||||||
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||||
|
|
||||||
module Assistant.WebApp.Types where
|
module Assistant.WebApp.Types where
|
||||||
|
@ -22,7 +23,6 @@ import Utility.Yesod
|
||||||
import Logs.Transfer
|
import Logs.Transfer
|
||||||
import Build.SysConfig (packageversion)
|
import Build.SysConfig (packageversion)
|
||||||
|
|
||||||
import Yesod
|
|
||||||
import Yesod.Static
|
import Yesod.Static
|
||||||
import Text.Hamlet
|
import Text.Hamlet
|
||||||
import Data.Text (Text, pack, unpack)
|
import Data.Text (Text, pack, unpack)
|
||||||
|
@ -71,7 +71,7 @@ instance Yesod WebApp where
|
||||||
addStylesheet $ StaticR css_bootstrap_css
|
addStylesheet $ StaticR css_bootstrap_css
|
||||||
addStylesheet $ StaticR css_bootstrap_responsive_css
|
addStylesheet $ StaticR css_bootstrap_responsive_css
|
||||||
$(widgetFile "error")
|
$(widgetFile "error")
|
||||||
hamletToRepHtml $(hamletFile $ hamletTemplate "bootstrap")
|
giveUrlRenderer $(hamletFile $ hamletTemplate "bootstrap")
|
||||||
|
|
||||||
instance RenderMessage WebApp FormMessage where
|
instance RenderMessage WebApp FormMessage where
|
||||||
renderMessage _ _ = defaultFormMessage
|
renderMessage _ _ = defaultFormMessage
|
||||||
|
@ -81,29 +81,65 @@ instance RenderMessage WebApp FormMessage where
|
||||||
- When the webapp is run outside a git-annex repository, the fallback
|
- When the webapp is run outside a git-annex repository, the fallback
|
||||||
- value is returned.
|
- value is returned.
|
||||||
-}
|
-}
|
||||||
|
#if MIN_VERSION_yesod(1,2,0)
|
||||||
|
liftAnnexOr :: forall a. a -> Annex a -> Handler a
|
||||||
|
#else
|
||||||
liftAnnexOr :: forall sub a. a -> Annex a -> GHandler sub WebApp a
|
liftAnnexOr :: forall sub a. a -> Annex a -> GHandler sub WebApp a
|
||||||
|
#endif
|
||||||
liftAnnexOr fallback a = ifM (noAnnex <$> getYesod)
|
liftAnnexOr fallback a = ifM (noAnnex <$> getYesod)
|
||||||
( return fallback
|
( return fallback
|
||||||
, liftAssistant $ liftAnnex a
|
, liftAssistant $ liftAnnex a
|
||||||
)
|
)
|
||||||
|
|
||||||
|
#if MIN_VERSION_yesod(1,2,0)
|
||||||
|
instance LiftAnnex Handler where
|
||||||
|
#else
|
||||||
instance LiftAnnex (GHandler sub WebApp) where
|
instance LiftAnnex (GHandler sub WebApp) where
|
||||||
liftAnnex = liftAnnexOr $ error "internal runAnnex"
|
#endif
|
||||||
|
liftAnnex = liftAnnexOr $ error "internal liftAnnex"
|
||||||
|
|
||||||
|
#if MIN_VERSION_yesod(1,2,0)
|
||||||
|
instance LiftAnnex (WidgetT WebApp IO) where
|
||||||
|
#else
|
||||||
instance LiftAnnex (GWidget WebApp WebApp) where
|
instance LiftAnnex (GWidget WebApp WebApp) where
|
||||||
liftAnnex = lift . liftAnnex
|
#endif
|
||||||
|
liftAnnex = liftH . liftAnnex
|
||||||
|
|
||||||
class LiftAssistant m where
|
class LiftAssistant m where
|
||||||
liftAssistant :: Assistant a -> m a
|
liftAssistant :: Assistant a -> m a
|
||||||
|
|
||||||
|
#if MIN_VERSION_yesod(1,2,0)
|
||||||
|
instance LiftAssistant Handler where
|
||||||
|
#else
|
||||||
instance LiftAssistant (GHandler sub WebApp) where
|
instance LiftAssistant (GHandler sub WebApp) where
|
||||||
|
#endif
|
||||||
liftAssistant a = liftIO . flip runAssistant a
|
liftAssistant a = liftIO . flip runAssistant a
|
||||||
=<< assistantData <$> getYesod
|
=<< assistantData <$> getYesod
|
||||||
|
|
||||||
|
#if MIN_VERSION_yesod(1,2,0)
|
||||||
|
instance LiftAssistant (WidgetT WebApp IO) where
|
||||||
|
#else
|
||||||
instance LiftAssistant (GWidget WebApp WebApp) where
|
instance LiftAssistant (GWidget WebApp WebApp) where
|
||||||
liftAssistant = lift . liftAssistant
|
#endif
|
||||||
|
liftAssistant = liftH . liftAssistant
|
||||||
|
|
||||||
type Form x = Html -> MForm WebApp WebApp (FormResult x, Widget)
|
#if MIN_VERSION_yesod(1,2,0)
|
||||||
|
type MkMForm x = MForm Handler (FormResult x, Widget)
|
||||||
|
#else
|
||||||
|
type MkMForm x = MForm WebApp WebApp (FormResult x, Widget)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if MIN_VERSION_yesod(1,2,0)
|
||||||
|
type MkAForm x = AForm Handler x
|
||||||
|
#else
|
||||||
|
type MkAForm x = AForm WebApp WebApp x
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if MIN_VERSION_yesod(1,2,0)
|
||||||
|
type MkField x = Monad m => RenderMessage (HandlerSite m) FormMessage => Field m x
|
||||||
|
#else
|
||||||
|
type MkField x = RenderMessage master FormMessage => Field sub master x
|
||||||
|
#endif
|
||||||
|
|
||||||
data RepoSelector = RepoSelector
|
data RepoSelector = RepoSelector
|
||||||
{ onlyCloud :: Bool
|
{ onlyCloud :: Bool
|
||||||
|
|
|
@ -32,6 +32,8 @@
|
||||||
/config/repository/edit/new/cloud/#UUID EditNewCloudRepositoryR GET POST
|
/config/repository/edit/new/cloud/#UUID EditNewCloudRepositoryR GET POST
|
||||||
/config/repository/sync/disable/#UUID DisableSyncR GET
|
/config/repository/sync/disable/#UUID DisableSyncR GET
|
||||||
/config/repository/sync/enable/#UUID EnableSyncR GET
|
/config/repository/sync/enable/#UUID EnableSyncR GET
|
||||||
|
/config/repository/unfinished/check CheckUnfinishedRepositoriesR GET
|
||||||
|
/config/repository/unfinished/retry RetryUnfinishedRepositoriesR GET
|
||||||
|
|
||||||
/config/repository/add/drive AddDriveR GET POST
|
/config/repository/add/drive AddDriveR GET POST
|
||||||
/config/repository/add/drive/confirm/#RemovableDrive ConfirmAddDriveR GET
|
/config/repository/add/drive/confirm/#RemovableDrive ConfirmAddDriveR GET
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{- core xmpp support
|
{- core xmpp support
|
||||||
-
|
-
|
||||||
- Copyright 2012 Joey Hess <joey@kitenet.net>
|
- Copyright 2012-2013 Joey Hess <joey@kitenet.net>
|
||||||
-
|
-
|
||||||
- Licensed under the GNU GPL version 3 or higher.
|
- Licensed under the GNU GPL version 3 or higher.
|
||||||
-}
|
-}
|
||||||
|
@ -12,6 +12,7 @@ module Assistant.XMPP where
|
||||||
import Assistant.Common
|
import Assistant.Common
|
||||||
import Assistant.Types.NetMessager
|
import Assistant.Types.NetMessager
|
||||||
import Assistant.Pairing
|
import Assistant.Pairing
|
||||||
|
import Git.Sha (extractSha)
|
||||||
|
|
||||||
import Network.Protocol.XMPP hiding (Node)
|
import Network.Protocol.XMPP hiding (Node)
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
|
@ -74,15 +75,33 @@ gitAnnexTagInfo v = case extractGitAnnexTag v of
|
||||||
<*> pure tag
|
<*> pure tag
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
|
|
||||||
{- A presence with a git-annex tag in it. -}
|
{- A presence with a git-annex tag in it.
|
||||||
|
- Also includes a status tag, which may be visible in XMPP clients. -}
|
||||||
gitAnnexPresence :: Element -> Presence
|
gitAnnexPresence :: Element -> Presence
|
||||||
gitAnnexPresence = insertGitAnnexTag $ emptyPresence PresenceAvailable
|
gitAnnexPresence = insertGitAnnexTag $ addStatusTag $ emptyPresence PresenceAvailable
|
||||||
|
where
|
||||||
|
addStatusTag p = p
|
||||||
|
{ presencePayloads = status : presencePayloads p }
|
||||||
|
status = Element "status" [] [statusMessage]
|
||||||
|
statusMessage = NodeContent $ ContentText $ T.pack "git-annex"
|
||||||
|
|
||||||
{- A presence with an empty git-annex tag in it, used for letting other
|
{- A presence with an empty git-annex tag in it, used for letting other
|
||||||
- clients know we're around and are a git-annex client. -}
|
- clients know we're around and are a git-annex client. -}
|
||||||
gitAnnexSignature :: Presence
|
gitAnnexSignature :: Presence
|
||||||
gitAnnexSignature = gitAnnexPresence $ Element gitAnnexTagName [] []
|
gitAnnexSignature = gitAnnexPresence $ Element gitAnnexTagName [] []
|
||||||
|
|
||||||
|
{- XMPP client to server ping -}
|
||||||
|
xmppPing :: JID -> IQ
|
||||||
|
xmppPing selfjid = (emptyIQ IQGet)
|
||||||
|
{ iqID = Just "c2s1"
|
||||||
|
, iqFrom = Just selfjid
|
||||||
|
, iqTo = Just $ JID Nothing (jidDomain selfjid) Nothing
|
||||||
|
, iqPayload = Just $ Element xmppPingTagName [] []
|
||||||
|
}
|
||||||
|
|
||||||
|
xmppPingTagName :: Name
|
||||||
|
xmppPingTagName = "{urn:xmpp}ping"
|
||||||
|
|
||||||
{- A message with a git-annex tag in it. -}
|
{- A message with a git-annex tag in it. -}
|
||||||
gitAnnexMessage :: Element -> JID -> JID -> Message
|
gitAnnexMessage :: Element -> JID -> JID -> Message
|
||||||
gitAnnexMessage elt tojid fromjid = (insertGitAnnexTag silentMessage elt)
|
gitAnnexMessage elt tojid fromjid = (insertGitAnnexTag silentMessage elt)
|
||||||
|
@ -131,8 +150,9 @@ decodePairingNotification m = parse . words . T.unpack . tagValue
|
||||||
pushMessage :: PushStage -> JID -> JID -> Message
|
pushMessage :: PushStage -> JID -> JID -> Message
|
||||||
pushMessage = gitAnnexMessage . encode
|
pushMessage = gitAnnexMessage . encode
|
||||||
where
|
where
|
||||||
encode (CanPush u) =
|
encode (CanPush u shas) =
|
||||||
gitAnnexTag canPushAttr $ T.pack $ fromUUID u
|
gitAnnexTag canPushAttr $ T.pack $ unwords $
|
||||||
|
fromUUID u : map show shas
|
||||||
encode (PushRequest u) =
|
encode (PushRequest u) =
|
||||||
gitAnnexTag pushRequestAttr $ T.pack $ fromUUID u
|
gitAnnexTag pushRequestAttr $ T.pack $ fromUUID u
|
||||||
encode (StartingPush u) =
|
encode (StartingPush u) =
|
||||||
|
@ -160,7 +180,7 @@ decodeMessage m = decode =<< gitAnnexTagInfo m
|
||||||
, receivePackDoneAttr
|
, receivePackDoneAttr
|
||||||
]
|
]
|
||||||
[ decodePairingNotification
|
[ decodePairingNotification
|
||||||
, pushdecoder $ gen CanPush
|
, pushdecoder $ shasgen CanPush
|
||||||
, pushdecoder $ gen PushRequest
|
, pushdecoder $ gen PushRequest
|
||||||
, pushdecoder $ gen StartingPush
|
, pushdecoder $ gen StartingPush
|
||||||
, pushdecoder $ seqgen ReceivePackOutput
|
, pushdecoder $ seqgen ReceivePackOutput
|
||||||
|
@ -172,11 +192,14 @@ decodeMessage m = decode =<< gitAnnexTagInfo m
|
||||||
pushdecoder a m' i = Pushing
|
pushdecoder a m' i = Pushing
|
||||||
<$> (formatJID <$> messageFrom m')
|
<$> (formatJID <$> messageFrom m')
|
||||||
<*> a i
|
<*> a i
|
||||||
gen c = Just . c . toUUID . T.unpack . tagValue
|
gen c i = c . toUUID <$> headMaybe (words (T.unpack (tagValue i)))
|
||||||
seqgen c i = do
|
seqgen c i = do
|
||||||
packet <- decodeTagContent $ tagElement i
|
packet <- decodeTagContent $ tagElement i
|
||||||
let seqnum = fromMaybe 0 $ readish $ T.unpack $ tagValue i
|
let seqnum = fromMaybe 0 $ readish $ T.unpack $ tagValue i
|
||||||
return $ c seqnum packet
|
return $ c seqnum packet
|
||||||
|
shasgen c i = do
|
||||||
|
let (u:shas) = words $ T.unpack $ tagValue i
|
||||||
|
return $ c (toUUID u) (mapMaybe extractSha shas)
|
||||||
|
|
||||||
decodeExitCode :: Int -> ExitCode
|
decodeExitCode :: Int -> ExitCode
|
||||||
decodeExitCode 0 = ExitSuccess
|
decodeExitCode 0 = ExitSuccess
|
||||||
|
@ -245,3 +268,6 @@ sendPackAttr = "sp"
|
||||||
|
|
||||||
receivePackDoneAttr :: Name
|
receivePackDoneAttr :: Name
|
||||||
receivePackDoneAttr = "rpdone"
|
receivePackDoneAttr = "rpdone"
|
||||||
|
|
||||||
|
shasAttr :: Name
|
||||||
|
shasAttr = "shas"
|
||||||
|
|
|
@ -27,36 +27,46 @@ data XMPPCreds = XMPPCreds
|
||||||
}
|
}
|
||||||
deriving (Read, Show)
|
deriving (Read, Show)
|
||||||
|
|
||||||
connectXMPP :: XMPPCreds -> (JID -> XMPP a) -> IO (Either SomeException ())
|
connectXMPP :: XMPPCreds -> (JID -> XMPP a) -> IO [(HostPort, Either SomeException ())]
|
||||||
connectXMPP c a = case parseJID (xmppJID c) of
|
connectXMPP c a = case parseJID (xmppJID c) of
|
||||||
Nothing -> error "bad JID"
|
Nothing -> error "bad JID"
|
||||||
Just jid -> connectXMPP' jid c a
|
Just jid -> connectXMPP' jid c a
|
||||||
|
|
||||||
{- Do a SRV lookup, but if it fails, fall back to the cached xmppHostname. -}
|
{- Do a SRV lookup, but if it fails, fall back to the cached xmppHostname. -}
|
||||||
connectXMPP' :: JID -> XMPPCreds -> (JID -> XMPP a) -> IO (Either SomeException ())
|
connectXMPP' :: JID -> XMPPCreds -> (JID -> XMPP a) -> IO [(HostPort, Either SomeException ())]
|
||||||
connectXMPP' jid c a = go =<< lookupSRV srvrecord
|
connectXMPP' jid c a = reverse <$> (handle =<< lookupSRV srvrecord)
|
||||||
where
|
where
|
||||||
srvrecord = mkSRVTcp "xmpp-client" $
|
srvrecord = mkSRVTcp "xmpp-client" $
|
||||||
T.unpack $ strDomain $ jidDomain jid
|
T.unpack $ strDomain $ jidDomain jid
|
||||||
serverjid = JID Nothing (jidDomain jid) Nothing
|
serverjid = JID Nothing (jidDomain jid) Nothing
|
||||||
|
|
||||||
go [] = run (xmppHostname c)
|
handle [] = do
|
||||||
(PortNumber $ fromIntegral $ xmppPort c)
|
let h = xmppHostname c
|
||||||
(a jid)
|
let p = PortNumber $ fromIntegral $ xmppPort c
|
||||||
go ((h,p):rest) = do
|
r <- run h p $ a jid
|
||||||
|
return [r]
|
||||||
|
handle srvs = go [] srvs
|
||||||
|
|
||||||
|
go l [] = return l
|
||||||
|
go l ((h,p):rest) = do
|
||||||
{- Try each SRV record in turn, until one connects,
|
{- Try each SRV record in turn, until one connects,
|
||||||
- at which point the MVar will be full. -}
|
- at which point the MVar will be full. -}
|
||||||
mv <- newEmptyMVar
|
mv <- newEmptyMVar
|
||||||
r <- run h p $ do
|
r <- run h p $ do
|
||||||
liftIO $ putMVar mv ()
|
liftIO $ putMVar mv ()
|
||||||
a jid
|
a jid
|
||||||
ifM (isEmptyMVar mv) (go rest, return r)
|
ifM (isEmptyMVar mv)
|
||||||
|
( go (r : l) rest
|
||||||
|
, return (r : l)
|
||||||
|
)
|
||||||
|
|
||||||
{- Async exceptions are let through so the XMPP thread can
|
{- Async exceptions are let through so the XMPP thread can
|
||||||
- be killed. -}
|
- be killed. -}
|
||||||
run h p a' = tryNonAsync $
|
run h p a' = do
|
||||||
runClientError (Server serverjid h p) jid
|
r <- tryNonAsync $
|
||||||
(xmppUsername c) (xmppPassword c) (void a')
|
runClientError (Server serverjid h p) jid
|
||||||
|
(xmppUsername c) (xmppPassword c) (void a')
|
||||||
|
return ((h, p), r)
|
||||||
|
|
||||||
{- XMPP runClient, that throws errors rather than returning an Either -}
|
{- XMPP runClient, that throws errors rather than returning an Either -}
|
||||||
runClientError :: Server -> JID -> T.Text -> T.Text -> XMPP a -> IO a
|
runClientError :: Server -> JID -> T.Text -> T.Text -> XMPP a -> IO a
|
||||||
|
|
|
@ -23,6 +23,7 @@ import qualified Annex.Branch
|
||||||
import Annex.UUID
|
import Annex.UUID
|
||||||
import Logs.UUID
|
import Logs.UUID
|
||||||
import Annex.TaggedPush
|
import Annex.TaggedPush
|
||||||
|
import Annex.CatFile
|
||||||
import Config
|
import Config
|
||||||
import Git
|
import Git
|
||||||
import qualified Git.Branch
|
import qualified Git.Branch
|
||||||
|
@ -43,6 +44,22 @@ import System.Timeout
|
||||||
import qualified Data.ByteString as B
|
import qualified Data.ByteString as B
|
||||||
import qualified Data.Map as M
|
import qualified Data.Map as M
|
||||||
|
|
||||||
|
{- Largest chunk of data to send in a single XMPP message. -}
|
||||||
|
chunkSize :: Int
|
||||||
|
chunkSize = 4096
|
||||||
|
|
||||||
|
{- How long to wait for an expected message before assuming the other side
|
||||||
|
- has gone away and canceling a push.
|
||||||
|
-
|
||||||
|
- This needs to be long enough to allow a message of up to 2+ times
|
||||||
|
- chunkSize to propigate up to a XMPP server, perhaps across to another
|
||||||
|
- server, and back down to us. On the other hand, other XMPP pushes can be
|
||||||
|
- delayed for running until the timeout is reached, so it should not be
|
||||||
|
- excessive.
|
||||||
|
-}
|
||||||
|
xmppTimeout :: Int
|
||||||
|
xmppTimeout = 120000000 -- 120 seconds
|
||||||
|
|
||||||
finishXMPPPairing :: JID -> UUID -> Assistant ()
|
finishXMPPPairing :: JID -> UUID -> Assistant ()
|
||||||
finishXMPPPairing jid u = void $ alertWhile alert $
|
finishXMPPPairing jid u = void $ alertWhile alert $
|
||||||
makeXMPPGitRemote buddy (baseJID jid) u
|
makeXMPPGitRemote buddy (baseJID jid) u
|
||||||
|
@ -83,8 +100,8 @@ makeXMPPGitRemote buddyname jid u = do
|
||||||
-
|
-
|
||||||
- We listen at the other end of the pipe and relay to and from XMPP.
|
- We listen at the other end of the pipe and relay to and from XMPP.
|
||||||
-}
|
-}
|
||||||
xmppPush :: ClientID -> (Git.Repo -> IO Bool) -> (NetMessage -> Assistant ()) -> Assistant Bool
|
xmppPush :: ClientID -> (Git.Repo -> IO Bool) -> Assistant Bool
|
||||||
xmppPush cid gitpush handledeferred = runPush SendPack cid handledeferred $ do
|
xmppPush cid gitpush = do
|
||||||
u <- liftAnnex getUUID
|
u <- liftAnnex getUUID
|
||||||
sendNetMessage $ Pushing cid (StartingPush u)
|
sendNetMessage $ Pushing cid (StartingPush u)
|
||||||
|
|
||||||
|
@ -120,7 +137,8 @@ xmppPush cid gitpush handledeferred = runPush SendPack cid handledeferred $ do
|
||||||
liftIO $ do
|
liftIO $ do
|
||||||
mapM_ killThread [t1, t2]
|
mapM_ killThread [t1, t2]
|
||||||
mapM_ hClose [inh, outh, controlh]
|
mapM_ hClose [inh, outh, controlh]
|
||||||
|
mapM_ closeFd [Fd inf, Fd outf, Fd controlf]
|
||||||
|
|
||||||
return r
|
return r
|
||||||
where
|
where
|
||||||
toxmpp seqnum inh = do
|
toxmpp seqnum inh = do
|
||||||
|
@ -132,24 +150,26 @@ xmppPush cid gitpush handledeferred = runPush SendPack cid handledeferred $ do
|
||||||
sendNetMessage $ Pushing cid $
|
sendNetMessage $ Pushing cid $
|
||||||
SendPackOutput seqnum' b
|
SendPackOutput seqnum' b
|
||||||
toxmpp seqnum' inh
|
toxmpp seqnum' inh
|
||||||
fromxmpp outh controlh = forever $ do
|
|
||||||
m <- timeout xmppTimeout <~> waitNetPushMessage SendPack
|
fromxmpp outh controlh = withPushMessagesInSequence cid SendPack handle
|
||||||
case m of
|
where
|
||||||
(Just (Pushing _ (ReceivePackOutput _ b))) ->
|
handle (Just (Pushing _ (ReceivePackOutput _ b))) =
|
||||||
liftIO $ writeChunk outh b
|
liftIO $ writeChunk outh b
|
||||||
(Just (Pushing _ (ReceivePackDone exitcode))) ->
|
handle (Just (Pushing _ (ReceivePackDone exitcode))) =
|
||||||
liftIO $ do
|
liftIO $ do
|
||||||
hPrint controlh exitcode
|
hPrint controlh exitcode
|
||||||
hFlush controlh
|
hFlush controlh
|
||||||
(Just _) -> noop
|
handle (Just _) = noop
|
||||||
Nothing -> do
|
handle Nothing = do
|
||||||
debug ["timeout waiting for git receive-pack output via XMPP"]
|
debug ["timeout waiting for git receive-pack output via XMPP"]
|
||||||
-- Send a synthetic exit code to git-annex
|
-- Send a synthetic exit code to git-annex
|
||||||
-- xmppgit, which will exit and cause git push
|
-- xmppgit, which will exit and cause git push
|
||||||
-- to die.
|
-- to die.
|
||||||
liftIO $ do
|
liftIO $ do
|
||||||
hPrint controlh (ExitFailure 1)
|
hPrint controlh (ExitFailure 1)
|
||||||
hFlush controlh
|
hFlush controlh
|
||||||
|
killThread =<< myThreadId
|
||||||
|
|
||||||
installwrapper tmpdir = liftIO $ do
|
installwrapper tmpdir = liftIO $ do
|
||||||
createDirectoryIfMissing True tmpdir
|
createDirectoryIfMissing True tmpdir
|
||||||
let wrapper = tmpdir </> "git-remote-xmpp"
|
let wrapper = tmpdir </> "git-remote-xmpp"
|
||||||
|
@ -159,6 +179,7 @@ xmppPush cid gitpush handledeferred = runPush SendPack cid handledeferred $ do
|
||||||
, "exec " ++ program ++ " xmppgit"
|
, "exec " ++ program ++ " xmppgit"
|
||||||
]
|
]
|
||||||
modifyFileMode wrapper $ addModes executeModes
|
modifyFileMode wrapper $ addModes executeModes
|
||||||
|
|
||||||
{- Use GIT_ANNEX_TMP_DIR if set, since that may be a better temp
|
{- Use GIT_ANNEX_TMP_DIR if set, since that may be a better temp
|
||||||
- dir (ie, not on a crippled filesystem where we can't make
|
- dir (ie, not on a crippled filesystem where we can't make
|
||||||
- the wrapper executable). -}
|
- the wrapper executable). -}
|
||||||
|
@ -169,7 +190,6 @@ xmppPush cid gitpush handledeferred = runPush SendPack cid handledeferred $ do
|
||||||
tmp <- liftAnnex $ fromRepo gitAnnexTmpDir
|
tmp <- liftAnnex $ fromRepo gitAnnexTmpDir
|
||||||
return $ tmp </> "xmppgit"
|
return $ tmp </> "xmppgit"
|
||||||
Just d -> return $ d </> "xmppgit"
|
Just d -> return $ d </> "xmppgit"
|
||||||
|
|
||||||
|
|
||||||
type EnvVar = String
|
type EnvVar = String
|
||||||
|
|
||||||
|
@ -219,8 +239,8 @@ xmppGitRelay = do
|
||||||
|
|
||||||
{- Relays git receive-pack stdin and stdout via XMPP, as well as propigating
|
{- Relays git receive-pack stdin and stdout via XMPP, as well as propigating
|
||||||
- its exit status to XMPP. -}
|
- its exit status to XMPP. -}
|
||||||
xmppReceivePack :: ClientID -> (NetMessage -> Assistant ()) -> Assistant Bool
|
xmppReceivePack :: ClientID -> Assistant Bool
|
||||||
xmppReceivePack cid handledeferred = runPush ReceivePack cid handledeferred $ do
|
xmppReceivePack cid = do
|
||||||
repodir <- liftAnnex $ fromRepo repoPath
|
repodir <- liftAnnex $ fromRepo repoPath
|
||||||
let p = (proc "git" ["receive-pack", repodir])
|
let p = (proc "git" ["receive-pack", repodir])
|
||||||
{ std_in = CreatePipe
|
{ std_in = CreatePipe
|
||||||
|
@ -245,19 +265,17 @@ xmppReceivePack cid handledeferred = runPush ReceivePack cid handledeferred $ do
|
||||||
let seqnum' = succ seqnum
|
let seqnum' = succ seqnum
|
||||||
sendNetMessage $ Pushing cid $ ReceivePackOutput seqnum' b
|
sendNetMessage $ Pushing cid $ ReceivePackOutput seqnum' b
|
||||||
relaytoxmpp seqnum' outh
|
relaytoxmpp seqnum' outh
|
||||||
relayfromxmpp inh = forever $ do
|
relayfromxmpp inh = withPushMessagesInSequence cid ReceivePack handle
|
||||||
m <- timeout xmppTimeout <~> waitNetPushMessage ReceivePack
|
where
|
||||||
case m of
|
handle (Just (Pushing _ (SendPackOutput _ b))) =
|
||||||
(Just (Pushing _ (SendPackOutput _ b))) ->
|
liftIO $ writeChunk inh b
|
||||||
liftIO $ writeChunk inh b
|
handle (Just _) = noop
|
||||||
(Just _) -> noop
|
handle Nothing = do
|
||||||
Nothing -> do
|
debug ["timeout waiting for git send-pack output via XMPP"]
|
||||||
debug ["timeout waiting for git send-pack output via XMPP"]
|
-- closing the handle will make git receive-pack exit
|
||||||
-- closing the handle will make
|
liftIO $ do
|
||||||
-- git receive-pack exit
|
hClose inh
|
||||||
liftIO $ do
|
killThread =<< myThreadId
|
||||||
hClose inh
|
|
||||||
killThread =<< myThreadId
|
|
||||||
|
|
||||||
xmppRemotes :: ClientID -> UUID -> Assistant [Remote]
|
xmppRemotes :: ClientID -> UUID -> Assistant [Remote]
|
||||||
xmppRemotes cid theiruuid = case baseJID <$> parseJID cid of
|
xmppRemotes cid theiruuid = case baseJID <$> parseJID cid of
|
||||||
|
@ -271,15 +289,12 @@ xmppRemotes cid theiruuid = case baseJID <$> parseJID cid of
|
||||||
matching loc r = repoIsUrl r && repoLocation r == loc
|
matching loc r = repoIsUrl r && repoLocation r == loc
|
||||||
knownuuid um r = Remote.uuid r == theiruuid || M.member theiruuid um
|
knownuuid um r = Remote.uuid r == theiruuid || M.member theiruuid um
|
||||||
|
|
||||||
handlePushInitiation :: (Remote -> Assistant ()) -> NetMessage -> Assistant ()
|
{- Returns the ClientID that it pushed to. -}
|
||||||
handlePushInitiation _ (Pushing cid (CanPush theiruuid)) =
|
runPush :: (Remote -> Assistant ()) -> NetMessage -> Assistant (Maybe ClientID)
|
||||||
unlessM (null <$> xmppRemotes cid theiruuid) $ do
|
runPush checkcloudrepos (Pushing cid (PushRequest theiruuid)) =
|
||||||
u <- liftAnnex getUUID
|
|
||||||
sendNetMessage $ Pushing cid (PushRequest u)
|
|
||||||
handlePushInitiation checkcloudrepos (Pushing cid (PushRequest theiruuid)) =
|
|
||||||
go =<< liftAnnex (inRepo Git.Branch.current)
|
go =<< liftAnnex (inRepo Git.Branch.current)
|
||||||
where
|
where
|
||||||
go Nothing = noop
|
go Nothing = return Nothing
|
||||||
go (Just branch) = do
|
go (Just branch) = do
|
||||||
rs <- xmppRemotes cid theiruuid
|
rs <- xmppRemotes cid theiruuid
|
||||||
liftAnnex $ Annex.Branch.commit "update"
|
liftAnnex $ Annex.Branch.commit "update"
|
||||||
|
@ -288,40 +303,80 @@ handlePushInitiation checkcloudrepos (Pushing cid (PushRequest theiruuid)) =
|
||||||
<*> getUUID
|
<*> getUUID
|
||||||
liftIO $ Command.Sync.updateBranch (Command.Sync.syncBranch branch) g
|
liftIO $ Command.Sync.updateBranch (Command.Sync.syncBranch branch) g
|
||||||
selfjid <- ((T.unpack <$>) . xmppClientID) <$> getDaemonStatus
|
selfjid <- ((T.unpack <$>) . xmppClientID) <$> getDaemonStatus
|
||||||
forM_ rs $ \r -> do
|
if null rs
|
||||||
void $ alertWhile (syncAlert [r]) $
|
then return Nothing
|
||||||
xmppPush cid
|
else do
|
||||||
(taggedPush u selfjid branch r)
|
forM_ rs $ \r -> do
|
||||||
(handleDeferred checkcloudrepos)
|
void $ alertWhile (syncAlert [r]) $
|
||||||
checkcloudrepos r
|
xmppPush cid (taggedPush u selfjid branch r)
|
||||||
handlePushInitiation checkcloudrepos (Pushing cid (StartingPush theiruuid)) = do
|
checkcloudrepos r
|
||||||
|
return $ Just cid
|
||||||
|
runPush checkcloudrepos (Pushing cid (StartingPush theiruuid)) = do
|
||||||
rs <- xmppRemotes cid theiruuid
|
rs <- xmppRemotes cid theiruuid
|
||||||
unless (null rs) $ do
|
if null rs
|
||||||
void $ alertWhile (syncAlert rs) $
|
then return Nothing
|
||||||
xmppReceivePack cid (handleDeferred checkcloudrepos)
|
else do
|
||||||
mapM_ checkcloudrepos rs
|
void $ alertWhile (syncAlert rs) $
|
||||||
handlePushInitiation _ _ = noop
|
xmppReceivePack cid
|
||||||
|
mapM_ checkcloudrepos rs
|
||||||
|
return $ Just cid
|
||||||
|
runPush _ _ = return Nothing
|
||||||
|
|
||||||
handleDeferred :: (Remote -> Assistant ()) -> NetMessage -> Assistant ()
|
{- Check if any of the shas that can be pushed are ones we do not
|
||||||
handleDeferred = handlePushInitiation
|
- have.
|
||||||
|
-
|
||||||
|
- (Older clients send no shas, so when there are none, always
|
||||||
|
- request a push.)
|
||||||
|
-}
|
||||||
|
handlePushNotice :: NetMessage -> Assistant ()
|
||||||
|
handlePushNotice (Pushing cid (CanPush theiruuid shas)) =
|
||||||
|
unlessM (null <$> xmppRemotes cid theiruuid) $
|
||||||
|
if null shas
|
||||||
|
then go
|
||||||
|
else ifM (haveall shas)
|
||||||
|
( debug ["ignoring CanPush with known shas"]
|
||||||
|
, go
|
||||||
|
)
|
||||||
|
where
|
||||||
|
go = do
|
||||||
|
u <- liftAnnex getUUID
|
||||||
|
sendNetMessage $ Pushing cid (PushRequest u)
|
||||||
|
haveall l = liftAnnex $ not <$> anyM donthave l
|
||||||
|
donthave sha = isNothing <$> catObjectDetails sha
|
||||||
|
handlePushNotice _ = noop
|
||||||
|
|
||||||
writeChunk :: Handle -> B.ByteString -> IO ()
|
writeChunk :: Handle -> B.ByteString -> IO ()
|
||||||
writeChunk h b = do
|
writeChunk h b = do
|
||||||
B.hPut h b
|
B.hPut h b
|
||||||
hFlush h
|
hFlush h
|
||||||
|
|
||||||
{- Largest chunk of data to send in a single XMPP message. -}
|
{- Gets NetMessages for a PushSide, ensures they are in order,
|
||||||
chunkSize :: Int
|
- and runs an action to handle each in turn. The action will be passed
|
||||||
chunkSize = 4096
|
- Nothing on timeout.
|
||||||
|
|
||||||
{- How long to wait for an expected message before assuming the other side
|
|
||||||
- has gone away and canceling a push.
|
|
||||||
-
|
-
|
||||||
- This needs to be long enough to allow a message of up to 2+ times
|
- Does not currently reorder messages, but does ensure that any
|
||||||
- chunkSize to propigate up to a XMPP server, perhaps across to another
|
- duplicate messages, or messages not in the sequence, are discarded.
|
||||||
- server, and back down to us. On the other hand, other XMPP pushes can be
|
|
||||||
- delayed for running until the timeout is reached, so it should not be
|
|
||||||
- excessive.
|
|
||||||
-}
|
-}
|
||||||
xmppTimeout :: Int
|
withPushMessagesInSequence :: ClientID -> PushSide -> (Maybe NetMessage -> Assistant ()) -> Assistant ()
|
||||||
xmppTimeout = 120000000 -- 120 seconds
|
withPushMessagesInSequence cid side a = loop 0
|
||||||
|
where
|
||||||
|
loop seqnum = do
|
||||||
|
m <- timeout xmppTimeout <~> waitInbox cid side
|
||||||
|
let go s = a m >> loop s
|
||||||
|
let next = seqnum + 1
|
||||||
|
case extractSequence =<< m of
|
||||||
|
Just seqnum'
|
||||||
|
| seqnum' == next -> go next
|
||||||
|
| seqnum' == 0 -> go seqnum
|
||||||
|
| seqnum' == seqnum -> do
|
||||||
|
debug ["ignoring duplicate sequence number", show seqnum]
|
||||||
|
loop seqnum
|
||||||
|
| otherwise -> do
|
||||||
|
debug ["ignoring out of order sequence number", show seqnum', "expected", show next]
|
||||||
|
loop seqnum
|
||||||
|
Nothing -> go seqnum
|
||||||
|
|
||||||
|
extractSequence :: NetMessage -> Maybe Int
|
||||||
|
extractSequence (Pushing _ (ReceivePackOutput seqnum _)) = Just seqnum
|
||||||
|
extractSequence (Pushing _ (SendPackOutput seqnum _)) = Just seqnum
|
||||||
|
extractSequence _ = Nothing
|
||||||
|
|
|
@ -27,16 +27,18 @@ backend = Backend
|
||||||
, canUpgradeKey = Nothing
|
, canUpgradeKey = Nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
fromUrl :: String -> Maybe Integer -> Key
|
{- When it's not too long, use the full url as the key name.
|
||||||
fromUrl url size = stubKey
|
- If the url is too long, it's truncated at half the filename length
|
||||||
{ keyName = key
|
- limit, and the md5 of the url is prepended to ensure a unique key. -}
|
||||||
, keyBackendName = "URL"
|
fromUrl :: String -> Maybe Integer -> Annex Key
|
||||||
, keySize = size
|
fromUrl url size = do
|
||||||
|
limit <- liftIO . fileNameLengthLimit =<< fromRepo gitAnnexDir
|
||||||
|
let truncurl = truncateFilePath (limit `div` 2) url
|
||||||
|
let key = if url == truncurl
|
||||||
|
then url
|
||||||
|
else truncurl ++ "-" ++ md5s (Str url)
|
||||||
|
return $ stubKey
|
||||||
|
{ keyName = key
|
||||||
|
, keyBackendName = "URL"
|
||||||
|
, keySize = size
|
||||||
}
|
}
|
||||||
where
|
|
||||||
{- when it's not too long, use the url as the key name
|
|
||||||
- 256 is the absolute filename max, but use a shorter
|
|
||||||
- length because this is not the entire key filename. -}
|
|
||||||
key
|
|
||||||
| length url < 128 = url
|
|
||||||
| otherwise = take 128 url ++ "-" ++ md5s (Str url)
|
|
||||||
|
|
|
@ -40,6 +40,8 @@ bundledPrograms = catMaybes
|
||||||
, SysConfig.sha512
|
, SysConfig.sha512
|
||||||
, SysConfig.sha224
|
, SysConfig.sha224
|
||||||
, SysConfig.sha384
|
, SysConfig.sha384
|
||||||
|
-- ionice is not included in the bundle; we rely on the system's
|
||||||
|
-- own version, which may better match its kernel
|
||||||
]
|
]
|
||||||
where
|
where
|
||||||
ifset True s = Just s
|
ifset True s = Just s
|
||||||
|
|
|
@ -31,6 +31,7 @@ tests =
|
||||||
, TestCase "curl" $ testCmd "curl" "curl --version >/dev/null"
|
, TestCase "curl" $ testCmd "curl" "curl --version >/dev/null"
|
||||||
, TestCase "wget" $ testCmd "wget" "wget --version >/dev/null"
|
, TestCase "wget" $ testCmd "wget" "wget --version >/dev/null"
|
||||||
, TestCase "bup" $ testCmd "bup" "bup --version >/dev/null"
|
, TestCase "bup" $ testCmd "bup" "bup --version >/dev/null"
|
||||||
|
, TestCase "ionice" $ testCmd "ionice" "ionice -c3 true >/dev/null"
|
||||||
, TestCase "gpg" $ maybeSelectCmd "gpg"
|
, TestCase "gpg" $ maybeSelectCmd "gpg"
|
||||||
[ ("gpg", "--version >/dev/null")
|
[ ("gpg", "--version >/dev/null")
|
||||||
, ("gpg2", "--version >/dev/null") ]
|
, ("gpg2", "--version >/dev/null") ]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{- Generating and installing a desktop menu entry file
|
{- Generating and installing a desktop menu entry file and icon,
|
||||||
- and a desktop autostart file. (And OSX equivilants.)
|
- and a desktop autostart file. (And OSX equivilants.)
|
||||||
-
|
-
|
||||||
- Copyright 2012 Joey Hess <joey@kitenet.net>
|
- Copyright 2012 Joey Hess <joey@kitenet.net>
|
||||||
|
@ -48,11 +48,14 @@ inDestDir f = do
|
||||||
|
|
||||||
writeFDODesktop :: FilePath -> IO ()
|
writeFDODesktop :: FilePath -> IO ()
|
||||||
writeFDODesktop command = do
|
writeFDODesktop command = do
|
||||||
datadir <- ifM systemwideInstall ( return systemDataDir, userDataDir )
|
systemwide <- systemwideInstall
|
||||||
installMenu command
|
|
||||||
=<< inDestDir (desktopMenuFilePath "git-annex" datadir)
|
|
||||||
|
|
||||||
configdir <- ifM systemwideInstall ( return systemConfigDir, userConfigDir )
|
datadir <- if systemwide then return systemDataDir else userDataDir
|
||||||
|
menufile <- inDestDir (desktopMenuFilePath "git-annex" datadir)
|
||||||
|
icondir <- inDestDir (iconDir datadir)
|
||||||
|
installMenu command menufile "doc" icondir
|
||||||
|
|
||||||
|
configdir <- if systemwide then return systemConfigDir else userConfigDir
|
||||||
installAutoStart command
|
installAutoStart command
|
||||||
=<< inDestDir (autoStartPath "git-annex" configdir)
|
=<< inDestDir (autoStartPath "git-annex" configdir)
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{- Generating and installing a desktop menu entry file
|
{- Generating and installing a desktop menu entry file and icon,
|
||||||
- and a desktop autostart file. (And OSX equivilants.)
|
- and a desktop autostart file. (And OSX equivilants.)
|
||||||
-
|
-
|
||||||
- Copyright 2012 Joey Hess <joey@kitenet.net>
|
- Copyright 2012 Joey Hess <joey@kitenet.net>
|
||||||
|
|
|
@ -119,6 +119,7 @@ cygwinDlls :: [FilePath]
|
||||||
cygwinDlls =
|
cygwinDlls =
|
||||||
[ "cygwin1.dll"
|
[ "cygwin1.dll"
|
||||||
, "cygasn1-8.dll"
|
, "cygasn1-8.dll"
|
||||||
|
, "cygattr-1.dll"
|
||||||
, "cygheimbase-1.dll"
|
, "cygheimbase-1.dll"
|
||||||
, "cygroken-18.dll"
|
, "cygroken-18.dll"
|
||||||
, "cygcom_err-2.dll"
|
, "cygcom_err-2.dll"
|
||||||
|
|
|
@ -30,6 +30,7 @@ import Annex.Content
|
||||||
import Annex.Ssh
|
import Annex.Ssh
|
||||||
import Annex.Environment
|
import Annex.Environment
|
||||||
import Command
|
import Command
|
||||||
|
import Types.Messages
|
||||||
|
|
||||||
type Params = [String]
|
type Params = [String]
|
||||||
type Flags = [Annex ()]
|
type Flags = [Annex ()]
|
||||||
|
@ -47,7 +48,11 @@ dispatch fuzzyok allargs allcmds commonoptions fields header getgitrepo = do
|
||||||
checkEnvironment
|
checkEnvironment
|
||||||
checkfuzzy
|
checkfuzzy
|
||||||
forM_ fields $ uncurry Annex.setField
|
forM_ fields $ uncurry Annex.setField
|
||||||
|
when (cmdnomessages cmd) $
|
||||||
|
Annex.setOutput QuietOutput
|
||||||
sequence_ flags
|
sequence_ flags
|
||||||
|
whenM (annexDebug <$> Annex.getGitConfig) $
|
||||||
|
liftIO enableDebugOutput
|
||||||
prepCommand cmd params
|
prepCommand cmd params
|
||||||
tryRun state' cmd $ [startup] ++ actions ++ [shutdown $ cmdnocommit cmd]
|
tryRun state' cmd $ [startup] ++ actions ++ [shutdown $ cmdnocommit cmd]
|
||||||
where
|
where
|
||||||
|
|
14
Command.hs
14
Command.hs
|
@ -9,6 +9,7 @@ module Command (
|
||||||
command,
|
command,
|
||||||
noRepo,
|
noRepo,
|
||||||
noCommit,
|
noCommit,
|
||||||
|
noMessages,
|
||||||
withOptions,
|
withOptions,
|
||||||
next,
|
next,
|
||||||
stop,
|
stop,
|
||||||
|
@ -40,13 +41,18 @@ import Annex.CheckAttr
|
||||||
|
|
||||||
{- Generates a normal command -}
|
{- Generates a normal command -}
|
||||||
command :: String -> String -> [CommandSeek] -> CommandSection -> String -> Command
|
command :: String -> String -> [CommandSeek] -> CommandSection -> String -> Command
|
||||||
command = Command [] Nothing commonChecks False
|
command = Command [] Nothing commonChecks False False
|
||||||
|
|
||||||
{- Indicates that a command doesn't need to commit any changes to
|
{- Indicates that a command doesn't need to commit any changes to
|
||||||
- the git-annex branch. -}
|
- the git-annex branch. -}
|
||||||
noCommit :: Command -> Command
|
noCommit :: Command -> Command
|
||||||
noCommit c = c { cmdnocommit = True }
|
noCommit c = c { cmdnocommit = True }
|
||||||
|
|
||||||
|
{- Indicates that a command should not output anything other than what
|
||||||
|
- it directly sends to stdout. (--json can override this). -}
|
||||||
|
noMessages :: Command -> Command
|
||||||
|
noMessages c = c { cmdnomessages = True }
|
||||||
|
|
||||||
{- Adds a fallback action to a command, that will be run if it's used
|
{- Adds a fallback action to a command, that will be run if it's used
|
||||||
- outside a git repository. -}
|
- outside a git repository. -}
|
||||||
noRepo :: IO () -> Command -> Command
|
noRepo :: IO () -> Command -> Command
|
||||||
|
@ -99,7 +105,11 @@ isBareRepo :: Annex Bool
|
||||||
isBareRepo = fromRepo Git.repoIsLocalBare
|
isBareRepo = fromRepo Git.repoIsLocalBare
|
||||||
|
|
||||||
numCopies :: FilePath -> Annex (Maybe Int)
|
numCopies :: FilePath -> Annex (Maybe Int)
|
||||||
numCopies file = readish <$> checkAttr "annex.numcopies" file
|
numCopies file = do
|
||||||
|
forced <- Annex.getState Annex.forcenumcopies
|
||||||
|
case forced of
|
||||||
|
Just n -> return $ Just n
|
||||||
|
Nothing -> readish <$> checkAttr "annex.numcopies" file
|
||||||
|
|
||||||
numCopiesCheck :: FilePath -> Key -> (Int -> Int -> Bool) -> Annex Bool
|
numCopiesCheck :: FilePath -> Key -> (Int -> Int -> Bool) -> Annex Bool
|
||||||
numCopiesCheck file key vs = do
|
numCopiesCheck file key vs = do
|
||||||
|
|
|
@ -31,6 +31,7 @@ import Config
|
||||||
import Utility.InodeCache
|
import Utility.InodeCache
|
||||||
import Annex.FileMatcher
|
import Annex.FileMatcher
|
||||||
import Annex.ReplaceFile
|
import Annex.ReplaceFile
|
||||||
|
import Utility.Tmp
|
||||||
|
|
||||||
def :: [Command]
|
def :: [Command]
|
||||||
def = [notBareRepo $ command "add" paramPaths seek SectionCommon
|
def = [notBareRepo $ command "add" paramPaths seek SectionCommon
|
||||||
|
@ -79,37 +80,54 @@ start file = ifAnnexed file addpresent add
|
||||||
next $ next $ cleanup file key =<< inAnnex key
|
next $ next $ cleanup file key =<< inAnnex key
|
||||||
|
|
||||||
{- The file that's being added is locked down before a key is generated,
|
{- The file that's being added is locked down before a key is generated,
|
||||||
- to prevent it from being modified in between. It's hard linked into a
|
- to prevent it from being modified in between. This lock down is not
|
||||||
- temporary location, and its writable bits are removed. It could still be
|
- perfect at best (and pretty weak at worst). For example, it does not
|
||||||
- written to by a process that already has it open for writing.
|
- guard against files that are already opened for write by another process.
|
||||||
|
- So a KeySource is returned. Its inodeCache can be used to detect any
|
||||||
|
- changes that might be made to the file after it was locked down.
|
||||||
|
-
|
||||||
|
- In indirect mode, the write bit is removed from the file as part of lock
|
||||||
|
- down to guard against further writes, and because objects in the annex
|
||||||
|
- have their write bit disabled anyway. This is not done in direct mode,
|
||||||
|
- because files there need to remain writable at all times.
|
||||||
|
-
|
||||||
|
- When possible, the file is hard linked to a temp directory. This guards
|
||||||
|
- against some changes, like deletion or overwrite of the file, and
|
||||||
|
- allows lsof checks to be done more efficiently when adding a lot of files.
|
||||||
-
|
-
|
||||||
- Lockdown can fail if a file gets deleted, and Nothing will be returned.
|
- Lockdown can fail if a file gets deleted, and Nothing will be returned.
|
||||||
-}
|
-}
|
||||||
lockDown :: FilePath -> Annex (Maybe KeySource)
|
lockDown :: FilePath -> Annex (Maybe KeySource)
|
||||||
lockDown file = ifM (crippledFileSystem)
|
lockDown file = ifM (crippledFileSystem)
|
||||||
( liftIO $ catchMaybeIO $ do
|
( liftIO $ catchMaybeIO nohardlink
|
||||||
|
, do
|
||||||
|
tmp <- fromRepo gitAnnexTmpDir
|
||||||
|
createAnnexDirectory tmp
|
||||||
|
unlessM (isDirect) $ liftIO $
|
||||||
|
void $ tryIO $ preventWrite file
|
||||||
|
liftIO $ catchMaybeIO $ do
|
||||||
|
(tmpfile, h) <- openTempFile tmp $
|
||||||
|
relatedTemplate $ takeFileName file
|
||||||
|
hClose h
|
||||||
|
nukeFile tmpfile
|
||||||
|
withhardlink tmpfile `catchIO` const nohardlink
|
||||||
|
)
|
||||||
|
where
|
||||||
|
nohardlink = do
|
||||||
cache <- genInodeCache file
|
cache <- genInodeCache file
|
||||||
return $ KeySource
|
return $ KeySource
|
||||||
{ keyFilename = file
|
{ keyFilename = file
|
||||||
, contentLocation = file
|
, contentLocation = file
|
||||||
, inodeCache = cache
|
, inodeCache = cache
|
||||||
}
|
}
|
||||||
, do
|
withhardlink tmpfile = do
|
||||||
tmp <- fromRepo gitAnnexTmpDir
|
createLink file tmpfile
|
||||||
createAnnexDirectory tmp
|
cache <- genInodeCache tmpfile
|
||||||
liftIO $ catchMaybeIO $ do
|
return $ KeySource
|
||||||
preventWrite file
|
{ keyFilename = file
|
||||||
(tmpfile, h) <- openTempFile tmp (takeFileName file)
|
, contentLocation = tmpfile
|
||||||
hClose h
|
, inodeCache = cache
|
||||||
nukeFile tmpfile
|
}
|
||||||
createLink file tmpfile
|
|
||||||
cache <- genInodeCache tmpfile
|
|
||||||
return $ KeySource
|
|
||||||
{ keyFilename = file
|
|
||||||
, contentLocation = tmpfile
|
|
||||||
, inodeCache = cache
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
{- Ingests a locked down file into the annex.
|
{- Ingests a locked down file into the annex.
|
||||||
-
|
-
|
||||||
|
@ -151,8 +169,6 @@ ingest (Just source) = do
|
||||||
finishIngestDirect :: Key -> KeySource -> Annex ()
|
finishIngestDirect :: Key -> KeySource -> Annex ()
|
||||||
finishIngestDirect key source = do
|
finishIngestDirect key source = do
|
||||||
void $ addAssociatedFile key $ keyFilename source
|
void $ addAssociatedFile key $ keyFilename source
|
||||||
unlessM crippledFileSystem $
|
|
||||||
liftIO $ allowWrite $ keyFilename source
|
|
||||||
when (contentLocation source /= keyFilename source) $
|
when (contentLocation source /= keyFilename source) $
|
||||||
liftIO $ nukeFile $ contentLocation source
|
liftIO $ nukeFile $ contentLocation source
|
||||||
|
|
||||||
|
@ -174,7 +190,7 @@ undo file key e = do
|
||||||
liftIO $ nukeFile file
|
liftIO $ nukeFile file
|
||||||
catchAnnex (fromAnnex key file) tryharder
|
catchAnnex (fromAnnex key file) tryharder
|
||||||
logStatus key InfoMissing
|
logStatus key InfoMissing
|
||||||
throw e
|
throwAnnex e
|
||||||
where
|
where
|
||||||
-- fromAnnex could fail if the file ownership is weird
|
-- fromAnnex could fail if the file ownership is weird
|
||||||
tryharder :: IOException -> Annex ()
|
tryharder :: IOException -> Annex ()
|
||||||
|
|
|
@ -8,10 +8,10 @@
|
||||||
module Command.AddUnused where
|
module Command.AddUnused where
|
||||||
|
|
||||||
import Common.Annex
|
import Common.Annex
|
||||||
import Logs.Unused
|
|
||||||
import Logs.Location
|
import Logs.Location
|
||||||
import Command
|
import Command
|
||||||
import qualified Command.Add
|
import qualified Command.Add
|
||||||
|
import Command.Unused (withUnusedMaps, UnusedMaps(..), startUnused)
|
||||||
import Types.Key
|
import Types.Key
|
||||||
|
|
||||||
def :: [Command]
|
def :: [Command]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{- git-annex command
|
{- git-annex command
|
||||||
-
|
-
|
||||||
- Copyright 2011 Joey Hess <joey@kitenet.net>
|
- Copyright 2011-2013 Joey Hess <joey@kitenet.net>
|
||||||
-
|
-
|
||||||
- Licensed under the GNU GPL version 3 or higher.
|
- Licensed under the GNU GPL version 3 or higher.
|
||||||
-}
|
-}
|
||||||
|
@ -54,17 +54,15 @@ start relaxed optfile pathdepth s = go $ fromMaybe bad $ parseURI s
|
||||||
bad = fromMaybe (error $ "bad url " ++ s) $
|
bad = fromMaybe (error $ "bad url " ++ s) $
|
||||||
parseURI $ escapeURIString isUnescapedInURI s
|
parseURI $ escapeURIString isUnescapedInURI s
|
||||||
go url = do
|
go url = do
|
||||||
let file = fromMaybe (url2file url pathdepth) optfile
|
pathmax <- liftIO $ fileNameLengthLimit "."
|
||||||
|
let file = fromMaybe (url2file url pathdepth pathmax) optfile
|
||||||
showStart "addurl" file
|
showStart "addurl" file
|
||||||
next $ perform relaxed s file
|
next $ perform relaxed s file
|
||||||
|
|
||||||
perform :: Bool -> String -> FilePath -> CommandPerform
|
perform :: Bool -> String -> FilePath -> CommandPerform
|
||||||
perform relaxed url file = ifAnnexed file addurl geturl
|
perform relaxed url file = ifAnnexed file addurl geturl
|
||||||
where
|
where
|
||||||
geturl = do
|
geturl = next $ addUrlFile relaxed url file
|
||||||
liftIO $ createDirectoryIfMissing True (parentDir file)
|
|
||||||
ifM (Annex.getState Annex.fast <||> pure relaxed)
|
|
||||||
( nodownload relaxed url file , download url file )
|
|
||||||
addurl (key, _backend)
|
addurl (key, _backend)
|
||||||
| relaxed = do
|
| relaxed = do
|
||||||
setUrlPresent key url
|
setUrlPresent key url
|
||||||
|
@ -76,26 +74,39 @@ perform relaxed url file = ifAnnexed file addurl geturl
|
||||||
setUrlPresent key url
|
setUrlPresent key url
|
||||||
next $ return True
|
next $ return True
|
||||||
, do
|
, do
|
||||||
warning $ "failed to verify url: " ++ url
|
warning $ "failed to verify url exists: " ++ url
|
||||||
stop
|
stop
|
||||||
)
|
)
|
||||||
|
|
||||||
download :: String -> FilePath -> CommandPerform
|
addUrlFile :: Bool -> String -> FilePath -> Annex Bool
|
||||||
|
addUrlFile relaxed url file = do
|
||||||
|
liftIO $ createDirectoryIfMissing True (parentDir file)
|
||||||
|
ifM (Annex.getState Annex.fast <||> pure relaxed)
|
||||||
|
( nodownload relaxed url file
|
||||||
|
, do
|
||||||
|
showAction $ "downloading " ++ url ++ " "
|
||||||
|
download url file
|
||||||
|
)
|
||||||
|
|
||||||
|
download :: String -> FilePath -> Annex Bool
|
||||||
download url file = do
|
download url file = do
|
||||||
showAction $ "downloading " ++ url ++ " "
|
|
||||||
dummykey <- genkey
|
dummykey <- genkey
|
||||||
tmp <- fromRepo $ gitAnnexTmpLocation dummykey
|
tmp <- fromRepo $ gitAnnexTmpLocation dummykey
|
||||||
stopUnless (runtransfer dummykey tmp) $ do
|
showOutput
|
||||||
backend <- chooseBackend file
|
ifM (runtransfer dummykey tmp)
|
||||||
let source = KeySource
|
( do
|
||||||
{ keyFilename = file
|
backend <- chooseBackend file
|
||||||
, contentLocation = tmp
|
let source = KeySource
|
||||||
, inodeCache = Nothing
|
{ keyFilename = file
|
||||||
}
|
, contentLocation = tmp
|
||||||
k <- genKey source backend
|
, inodeCache = Nothing
|
||||||
case k of
|
}
|
||||||
Nothing -> stop
|
k <- genKey source backend
|
||||||
Just (key, _) -> next $ cleanup url file key (Just tmp)
|
case k of
|
||||||
|
Nothing -> return False
|
||||||
|
Just (key, _) -> cleanup url file key (Just tmp)
|
||||||
|
, return False
|
||||||
|
)
|
||||||
where
|
where
|
||||||
{- Generate a dummy key to use for this download, before we can
|
{- Generate a dummy key to use for this download, before we can
|
||||||
- examine the file and find its real key. This allows resuming
|
- examine the file and find its real key. This allows resuming
|
||||||
|
@ -112,14 +123,14 @@ download url file = do
|
||||||
liftIO $ snd <$> Url.exists url headers
|
liftIO $ snd <$> Url.exists url headers
|
||||||
, return Nothing
|
, return Nothing
|
||||||
)
|
)
|
||||||
return $ Backend.URL.fromUrl url size
|
Backend.URL.fromUrl url size
|
||||||
runtransfer dummykey tmp =
|
runtransfer dummykey tmp =
|
||||||
Transfer.download webUUID dummykey (Just file) Transfer.forwardRetry $ const $ do
|
Transfer.download webUUID dummykey (Just file) Transfer.forwardRetry $ const $ do
|
||||||
liftIO $ createDirectoryIfMissing True (parentDir tmp)
|
liftIO $ createDirectoryIfMissing True (parentDir tmp)
|
||||||
downloadUrl [url] tmp
|
downloadUrl [url] tmp
|
||||||
|
|
||||||
|
|
||||||
cleanup :: String -> FilePath -> Key -> Maybe FilePath -> CommandCleanup
|
cleanup :: String -> FilePath -> Key -> Maybe FilePath -> Annex Bool
|
||||||
cleanup url file key mtmp = do
|
cleanup url file key mtmp = do
|
||||||
when (isJust mtmp) $
|
when (isJust mtmp) $
|
||||||
logStatus key InfoPresent
|
logStatus key InfoPresent
|
||||||
|
@ -133,7 +144,7 @@ cleanup url file key mtmp = do
|
||||||
maybe noop (moveAnnex key) mtmp
|
maybe noop (moveAnnex key) mtmp
|
||||||
return True
|
return True
|
||||||
|
|
||||||
nodownload :: Bool -> String -> FilePath -> CommandPerform
|
nodownload :: Bool -> String -> FilePath -> Annex Bool
|
||||||
nodownload relaxed url file = do
|
nodownload relaxed url file = do
|
||||||
headers <- getHttpHeaders
|
headers <- getHttpHeaders
|
||||||
(exists, size) <- if relaxed
|
(exists, size) <- if relaxed
|
||||||
|
@ -141,23 +152,23 @@ nodownload relaxed url file = do
|
||||||
else liftIO $ Url.exists url headers
|
else liftIO $ Url.exists url headers
|
||||||
if exists
|
if exists
|
||||||
then do
|
then do
|
||||||
let key = Backend.URL.fromUrl url size
|
key <- Backend.URL.fromUrl url size
|
||||||
next $ cleanup url file key Nothing
|
cleanup url file key Nothing
|
||||||
else do
|
else do
|
||||||
warning $ "unable to access url: " ++ url
|
warning $ "unable to access url: " ++ url
|
||||||
stop
|
return False
|
||||||
|
|
||||||
url2file :: URI -> Maybe Int -> FilePath
|
url2file :: URI -> Maybe Int -> Int -> FilePath
|
||||||
url2file url pathdepth = case pathdepth of
|
url2file url pathdepth pathmax = case pathdepth of
|
||||||
Nothing -> filesize $ escape fullurl
|
Nothing -> truncateFilePath pathmax $ escape fullurl
|
||||||
Just depth
|
Just depth
|
||||||
|
| depth >= length urlbits -> frombits id
|
||||||
| depth > 0 -> frombits $ drop depth
|
| depth > 0 -> frombits $ drop depth
|
||||||
| depth < 0 -> frombits $ reverse . take (negate depth) . reverse
|
| depth < 0 -> frombits $ reverse . take (negate depth) . reverse
|
||||||
| otherwise -> error "bad --pathdepth"
|
| otherwise -> error "bad --pathdepth"
|
||||||
where
|
where
|
||||||
fullurl = uriRegName auth ++ uriPath url ++ uriQuery url
|
fullurl = uriRegName auth ++ uriPath url ++ uriQuery url
|
||||||
frombits a = intercalate "/" $ a urlbits
|
frombits a = intercalate "/" $ a urlbits
|
||||||
urlbits = map (filesize . escape) $ filter (not . null) $ split "/" fullurl
|
urlbits = map (truncateFilePath pathmax . escape) $ filter (not . null) $ split "/" fullurl
|
||||||
auth = fromMaybe (error $ "bad url " ++ show url) $ uriAuthority url
|
auth = fromMaybe (error $ "bad url " ++ show url) $ uriAuthority url
|
||||||
filesize = take 255
|
|
||||||
escape = replace "/" "_" . replace "?" "_"
|
escape = replace "/" "_" . replace "?" "_"
|
||||||
|
|
|
@ -13,6 +13,7 @@ import qualified Option
|
||||||
import qualified Command.Watch
|
import qualified Command.Watch
|
||||||
import Init
|
import Init
|
||||||
import Config.Files
|
import Config.Files
|
||||||
|
import qualified Build.SysConfig
|
||||||
|
|
||||||
import System.Environment
|
import System.Environment
|
||||||
|
|
||||||
|
@ -55,13 +56,16 @@ autoStart = do
|
||||||
f <- autoStartFile
|
f <- autoStartFile
|
||||||
error $ "Nothing listed in " ++ f
|
error $ "Nothing listed in " ++ f
|
||||||
program <- readProgramFile
|
program <- readProgramFile
|
||||||
|
haveionice <- pure Build.SysConfig.ionice <&&> inPath "ionice"
|
||||||
forM_ dirs $ \d -> do
|
forM_ dirs $ \d -> do
|
||||||
putStrLn $ "git-annex autostart in " ++ d
|
putStrLn $ "git-annex autostart in " ++ d
|
||||||
ifM (catchBoolIO $ go program d)
|
ifM (catchBoolIO $ go haveionice program d)
|
||||||
( putStrLn "ok"
|
( putStrLn "ok"
|
||||||
, putStrLn "failed"
|
, putStrLn "failed"
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
go program dir = do
|
go haveionice program dir = do
|
||||||
setCurrentDirectory dir
|
setCurrentDirectory dir
|
||||||
boolSystem program [Param "assistant"]
|
if haveionice
|
||||||
|
then boolSystem "ionice" [Param "-c3", Param program, Param "assistant"]
|
||||||
|
else boolSystem program [Param "assistant"]
|
||||||
|
|
48
Command/Content.hs
Normal file
48
Command/Content.hs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
{- git-annex command
|
||||||
|
-
|
||||||
|
- Copyright 2013 Joey Hess <joey@kitenet.net>
|
||||||
|
-
|
||||||
|
- Licensed under the GNU GPL version 3 or higher.
|
||||||
|
-}
|
||||||
|
|
||||||
|
module Command.Content where
|
||||||
|
|
||||||
|
import Common.Annex
|
||||||
|
import Command
|
||||||
|
import qualified Remote
|
||||||
|
import Logs.PreferredContent
|
||||||
|
|
||||||
|
import qualified Data.Map as M
|
||||||
|
|
||||||
|
def :: [Command]
|
||||||
|
def = [command "content" (paramPair paramRemote (paramOptional paramExpression)) seek
|
||||||
|
SectionSetup "get or set preferred content expression"]
|
||||||
|
|
||||||
|
seek :: [CommandSeek]
|
||||||
|
seek = [withWords start]
|
||||||
|
|
||||||
|
start :: [String] -> CommandStart
|
||||||
|
start = parse
|
||||||
|
where
|
||||||
|
parse (name:[]) = go name performGet
|
||||||
|
parse (name:expr:[]) = go name $ \uuid -> do
|
||||||
|
showStart "content" name
|
||||||
|
performSet expr uuid
|
||||||
|
parse _ = error "Specify a repository."
|
||||||
|
|
||||||
|
go name a = do
|
||||||
|
u <- Remote.nameToUUID name
|
||||||
|
next $ a u
|
||||||
|
|
||||||
|
performGet :: UUID -> CommandPerform
|
||||||
|
performGet uuid = do
|
||||||
|
m <- preferredContentMapRaw
|
||||||
|
liftIO $ putStrLn $ fromMaybe "" $ M.lookup uuid m
|
||||||
|
next $ return True
|
||||||
|
|
||||||
|
performSet :: String -> UUID -> CommandPerform
|
||||||
|
performSet expr uuid = case checkPreferredContentExpression expr of
|
||||||
|
Just e -> error $ "Parse error: " ++ e
|
||||||
|
Nothing -> do
|
||||||
|
preferredContentSet uuid expr
|
||||||
|
next $ return True
|
|
@ -14,13 +14,16 @@ import qualified Remote
|
||||||
import Annex.Wanted
|
import Annex.Wanted
|
||||||
|
|
||||||
def :: [Command]
|
def :: [Command]
|
||||||
def = [withOptions Command.Move.options $ command "copy" paramPaths seek
|
def = [withOptions Command.Move.moveOptions $ command "copy" paramPaths seek
|
||||||
SectionCommon "copy content of files to/from another repository"]
|
SectionCommon "copy content of files to/from another repository"]
|
||||||
|
|
||||||
seek :: [CommandSeek]
|
seek :: [CommandSeek]
|
||||||
seek = [withField Command.Move.toOption Remote.byNameWithUUID $ \to ->
|
seek =
|
||||||
withField Command.Move.fromOption Remote.byNameWithUUID $ \from ->
|
[ withField Command.Move.toOption Remote.byNameWithUUID $ \to ->
|
||||||
withFilesInGit $ whenAnnexed $ start to from]
|
withField Command.Move.fromOption Remote.byNameWithUUID $ \from ->
|
||||||
|
withKeyOptions (Command.Move.startKey to from False) $
|
||||||
|
withFilesInGit $ whenAnnexed $ start to from
|
||||||
|
]
|
||||||
|
|
||||||
{- A copy is just a move that does not delete the source file.
|
{- A copy is just a move that does not delete the source file.
|
||||||
- However, --auto mode avoids unnecessary copies, and avoids getting or
|
- However, --auto mode avoids unnecessary copies, and avoids getting or
|
||||||
|
@ -33,4 +36,3 @@ start to from file (key, backend) = stopUnless shouldCopy $
|
||||||
check = case to of
|
check = case to of
|
||||||
Nothing -> wantGet False (Just file)
|
Nothing -> wantGet False (Just file)
|
||||||
Just r -> wantSend False (Just file) (Remote.uuid r)
|
Just r -> wantSend False (Just file) (Remote.uuid r)
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ seek = [withWords start]
|
||||||
start :: [String] -> CommandStart
|
start :: [String] -> CommandStart
|
||||||
start ws = do
|
start ws = do
|
||||||
let name = unwords ws
|
let name = unwords ws
|
||||||
showStart "dead " name
|
showStart "dead" name
|
||||||
u <- Remote.nameToUUID name
|
u <- Remote.nameToUUID name
|
||||||
next $ perform u
|
next $ perform u
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
|
|
||||||
module Command.DropUnused where
|
module Command.DropUnused where
|
||||||
|
|
||||||
import Logs.Unused
|
|
||||||
import Common.Annex
|
import Common.Annex
|
||||||
import Command
|
import Command
|
||||||
import qualified Annex
|
import qualified Annex
|
||||||
|
@ -15,6 +14,7 @@ import qualified Command.Drop
|
||||||
import qualified Remote
|
import qualified Remote
|
||||||
import qualified Git
|
import qualified Git
|
||||||
import qualified Option
|
import qualified Option
|
||||||
|
import Command.Unused (withUnusedMaps, UnusedMaps(..), startUnused)
|
||||||
|
|
||||||
def :: [Command]
|
def :: [Command]
|
||||||
def = [withOptions [Command.Drop.fromOption] $
|
def = [withOptions [Command.Drop.fromOption] $
|
||||||
|
@ -32,9 +32,8 @@ perform key = maybe droplocal dropremote =<< Remote.byNameWithUUID =<< from
|
||||||
where
|
where
|
||||||
dropremote r = do
|
dropremote r = do
|
||||||
showAction $ "from " ++ Remote.name r
|
showAction $ "from " ++ Remote.name r
|
||||||
ok <- Remote.removeKey r key
|
Command.Drop.performRemote key Nothing r
|
||||||
next $ Command.Drop.cleanupRemote key r ok
|
droplocal = Command.Drop.performLocal key Nothing Nothing
|
||||||
droplocal = Command.Drop.performLocal key (Just 0) Nothing -- force drop
|
|
||||||
from = Annex.getField $ Option.name Command.Drop.fromOption
|
from = Annex.getField $ Option.name Command.Drop.fromOption
|
||||||
|
|
||||||
performOther :: (Key -> Git.Repo -> FilePath) -> Key -> CommandPerform
|
performOther :: (Key -> Git.Repo -> FilePath) -> Key -> CommandPerform
|
||||||
|
|
|
@ -20,7 +20,7 @@ import Types.Key
|
||||||
import qualified Option
|
import qualified Option
|
||||||
|
|
||||||
def :: [Command]
|
def :: [Command]
|
||||||
def = [noCommit $ withOptions [formatOption, print0Option] $
|
def = [noCommit $ noMessages $ withOptions [formatOption, print0Option] $
|
||||||
command "find" paramPaths seek SectionQuery "lists available files"]
|
command "find" paramPaths seek SectionQuery "lists available files"]
|
||||||
|
|
||||||
formatOption :: Option
|
formatOption :: Option
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
- Licensed under the GNU GPL version 3 or higher.
|
- Licensed under the GNU GPL version 3 or higher.
|
||||||
-}
|
-}
|
||||||
|
|
||||||
|
{-# LANGUAGE CPP #-}
|
||||||
|
|
||||||
module Command.Fix where
|
module Command.Fix where
|
||||||
|
|
||||||
import System.PosixCompat.Files
|
import System.PosixCompat.Files
|
||||||
|
@ -12,6 +14,9 @@ import System.PosixCompat.Files
|
||||||
import Common.Annex
|
import Common.Annex
|
||||||
import Command
|
import Command
|
||||||
import qualified Annex.Queue
|
import qualified Annex.Queue
|
||||||
|
#ifndef __ANDROID__
|
||||||
|
import Utility.Touch
|
||||||
|
#endif
|
||||||
|
|
||||||
def :: [Command]
|
def :: [Command]
|
||||||
def = [notDirect $ noCommit $ command "fix" paramPaths seek
|
def = [notDirect $ noCommit $ command "fix" paramPaths seek
|
||||||
|
@ -30,9 +35,18 @@ start file (key, _) = do
|
||||||
|
|
||||||
perform :: FilePath -> FilePath -> CommandPerform
|
perform :: FilePath -> FilePath -> CommandPerform
|
||||||
perform file link = do
|
perform file link = do
|
||||||
liftIO $ createDirectoryIfMissing True (parentDir file)
|
liftIO $ do
|
||||||
liftIO $ removeFile file
|
#ifndef __ANDROID__
|
||||||
liftIO $ createSymbolicLink link file
|
-- preserve mtime of symlink
|
||||||
|
mtime <- catchMaybeIO $ TimeSpec . modificationTime
|
||||||
|
<$> getSymbolicLinkStatus file
|
||||||
|
#endif
|
||||||
|
createDirectoryIfMissing True (parentDir file)
|
||||||
|
removeFile file
|
||||||
|
createSymbolicLink link file
|
||||||
|
#ifndef __ANDROID__
|
||||||
|
maybe noop (\t -> touch file t False) mtime
|
||||||
|
#endif
|
||||||
next $ cleanup file
|
next $ cleanup file
|
||||||
|
|
||||||
cleanup :: FilePath -> CommandCleanup
|
cleanup :: FilePath -> CommandCleanup
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{- git-annex command
|
{- git-annex command
|
||||||
-
|
-
|
||||||
- Copyright 2010-2011 Joey Hess <joey@kitenet.net>
|
- Copyright 2010-2013 Joey Hess <joey@kitenet.net>
|
||||||
-
|
-
|
||||||
- Licensed under the GNU GPL version 3 or higher.
|
- Licensed under the GNU GPL version 3 or higher.
|
||||||
-}
|
-}
|
||||||
|
@ -20,6 +20,7 @@ import qualified Types.Key
|
||||||
import qualified Backend
|
import qualified Backend
|
||||||
import Annex.Content
|
import Annex.Content
|
||||||
import Annex.Content.Direct
|
import Annex.Content.Direct
|
||||||
|
import Annex.Direct
|
||||||
import Annex.Perms
|
import Annex.Perms
|
||||||
import Annex.Link
|
import Annex.Link
|
||||||
import Logs.Location
|
import Logs.Location
|
||||||
|
@ -31,8 +32,10 @@ import Config
|
||||||
import qualified Option
|
import qualified Option
|
||||||
import Types.Key
|
import Types.Key
|
||||||
import Utility.HumanTime
|
import Utility.HumanTime
|
||||||
|
import Git.FilePath
|
||||||
|
import GitAnnex.Options
|
||||||
|
|
||||||
#ifndef __WINDOWS__
|
#ifndef mingw32_HOST_OS
|
||||||
import System.Posix.Process (getProcessID)
|
import System.Posix.Process (getProcessID)
|
||||||
#else
|
#else
|
||||||
import System.Random (getStdRandom, random)
|
import System.Random (getStdRandom, random)
|
||||||
|
@ -43,7 +46,7 @@ import System.Posix.Types (EpochTime)
|
||||||
import System.Locale
|
import System.Locale
|
||||||
|
|
||||||
def :: [Command]
|
def :: [Command]
|
||||||
def = [withOptions options $ command "fsck" paramPaths seek
|
def = [withOptions fsckOptions $ command "fsck" paramPaths seek
|
||||||
SectionMaintenance "check for problems"]
|
SectionMaintenance "check for problems"]
|
||||||
|
|
||||||
fromOption :: Option
|
fromOption :: Option
|
||||||
|
@ -59,19 +62,20 @@ incrementalScheduleOption :: Option
|
||||||
incrementalScheduleOption = Option.field [] "incremental-schedule" paramTime
|
incrementalScheduleOption = Option.field [] "incremental-schedule" paramTime
|
||||||
"schedule incremental fscking"
|
"schedule incremental fscking"
|
||||||
|
|
||||||
options :: [Option]
|
fsckOptions :: [Option]
|
||||||
options =
|
fsckOptions =
|
||||||
[ fromOption
|
[ fromOption
|
||||||
, startIncrementalOption
|
, startIncrementalOption
|
||||||
, moreIncrementalOption
|
, moreIncrementalOption
|
||||||
, incrementalScheduleOption
|
, incrementalScheduleOption
|
||||||
]
|
] ++ keyOptions
|
||||||
|
|
||||||
seek :: [CommandSeek]
|
seek :: [CommandSeek]
|
||||||
seek =
|
seek =
|
||||||
[ withField fromOption Remote.byNameWithUUID $ \from ->
|
[ withField fromOption Remote.byNameWithUUID $ \from ->
|
||||||
withIncremental $ \i -> withFilesInGit $ whenAnnexed $ start from i
|
withIncremental $ \i ->
|
||||||
, withIncremental $ \i -> withBarePresentKeys $ startBare i
|
withKeyOptions (startKey i) $
|
||||||
|
withFilesInGit $ whenAnnexed $ start from i
|
||||||
]
|
]
|
||||||
|
|
||||||
withIncremental :: (Incremental -> CommandSeek) -> CommandSeek
|
withIncremental :: (Incremental -> CommandSeek) -> CommandSeek
|
||||||
|
@ -119,6 +123,7 @@ perform key file backend numcopies = check
|
||||||
[ fixLink key file
|
[ fixLink key file
|
||||||
, verifyLocationLog key file
|
, verifyLocationLog key file
|
||||||
, verifyDirectMapping key file
|
, verifyDirectMapping key file
|
||||||
|
, verifyDirectMode key file
|
||||||
, checkKeySize key
|
, checkKeySize key
|
||||||
, checkBackend backend key (Just file)
|
, checkBackend backend key (Just file)
|
||||||
, checkKeyNumCopies key file numcopies
|
, checkKeyNumCopies key file numcopies
|
||||||
|
@ -146,7 +151,7 @@ performRemote key file backend numcopies remote =
|
||||||
, checkKeyNumCopies key file numcopies
|
, checkKeyNumCopies key file numcopies
|
||||||
]
|
]
|
||||||
withtmp a = do
|
withtmp a = do
|
||||||
#ifndef __WINDOWS__
|
#ifndef mingw32_HOST_OS
|
||||||
v <- liftIO getProcessID
|
v <- liftIO getProcessID
|
||||||
#else
|
#else
|
||||||
v <- liftIO (getStdRandom random :: IO Int)
|
v <- liftIO (getStdRandom random :: IO Int)
|
||||||
|
@ -167,26 +172,15 @@ performRemote key file backend numcopies remote =
|
||||||
)
|
)
|
||||||
dummymeter _ = noop
|
dummymeter _ = noop
|
||||||
|
|
||||||
{- To fsck a bare repository, fsck each key in the location log. -}
|
startKey :: Incremental -> Key -> CommandStart
|
||||||
withBarePresentKeys :: (Key -> CommandStart) -> CommandSeek
|
startKey inc key = case Backend.maybeLookupBackendName (Types.Key.keyBackendName key) of
|
||||||
withBarePresentKeys a params = isBareRepo >>= go
|
|
||||||
where
|
|
||||||
go False = return []
|
|
||||||
go True = do
|
|
||||||
unless (null params) $
|
|
||||||
error "fsck should be run without parameters in a bare repository"
|
|
||||||
map a <$> loggedKeys
|
|
||||||
|
|
||||||
startBare :: Incremental -> Key -> CommandStart
|
|
||||||
startBare inc key = case Backend.maybeLookupBackendName (Types.Key.keyBackendName key) of
|
|
||||||
Nothing -> stop
|
Nothing -> stop
|
||||||
Just backend -> runFsck inc (key2file key) key $ performBare key backend
|
Just backend -> runFsck inc (key2file key) key $ performAll key backend
|
||||||
|
|
||||||
{- Note that numcopies cannot be checked in a bare repository, because
|
{- Note that numcopies cannot be checked in --all mode, since we do not
|
||||||
- getting the numcopies value requires a working copy with .gitattributes
|
- have associated filenames to look up in the .gitattributes file. -}
|
||||||
- files. -}
|
performAll :: Key -> Backend -> Annex Bool
|
||||||
performBare :: Key -> Backend -> Annex Bool
|
performAll key backend = check
|
||||||
performBare key backend = check
|
|
||||||
[ verifyLocationLog key (key2file key)
|
[ verifyLocationLog key (key2file key)
|
||||||
, checkKeySize key
|
, checkKeySize key
|
||||||
, checkBackend backend key Nothing
|
, checkBackend backend key Nothing
|
||||||
|
@ -206,24 +200,13 @@ fixLink key file = do
|
||||||
maybe noop (go want) have
|
maybe noop (go want) have
|
||||||
return True
|
return True
|
||||||
where
|
where
|
||||||
go want have = when (want /= have) $ do
|
go want have
|
||||||
{- Version 3.20120227 had a bug that could cause content
|
| want /= fromInternalGitPath have = do
|
||||||
- to be stored in the wrong hash directory. Clean up
|
showNote "fixing link"
|
||||||
- after the bug by moving the content.
|
liftIO $ createDirectoryIfMissing True (parentDir file)
|
||||||
-}
|
liftIO $ removeFile file
|
||||||
whenM (liftIO $ doesFileExist file) $
|
addAnnexLink want file
|
||||||
unlessM (inAnnex key) $ do
|
| otherwise = noop
|
||||||
showNote "fixing content location"
|
|
||||||
dir <- liftIO $ parentDir <$> absPath file
|
|
||||||
let content = absPathFrom dir have
|
|
||||||
unlessM crippledFileSystem $
|
|
||||||
liftIO $ allowWrite (parentDir content)
|
|
||||||
moveAnnex key content
|
|
||||||
|
|
||||||
showNote "fixing link"
|
|
||||||
liftIO $ createDirectoryIfMissing True (parentDir file)
|
|
||||||
liftIO $ removeFile file
|
|
||||||
addAnnexLink want file
|
|
||||||
|
|
||||||
{- Checks that the location log reflects the current status of the key,
|
{- Checks that the location log reflects the current status of the key,
|
||||||
- in this repository only. -}
|
- in this repository only. -}
|
||||||
|
@ -285,6 +268,20 @@ verifyDirectMapping key file = do
|
||||||
void $ removeAssociatedFile key f
|
void $ removeAssociatedFile key f
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
{- Ensures that files whose content is available are in direct mode. -}
|
||||||
|
verifyDirectMode :: Key -> FilePath -> Annex Bool
|
||||||
|
verifyDirectMode key file = do
|
||||||
|
whenM (isDirect <&&> islink) $ do
|
||||||
|
v <- toDirectGen key file
|
||||||
|
case v of
|
||||||
|
Nothing -> noop
|
||||||
|
Just a -> do
|
||||||
|
showNote "fixing direct mode"
|
||||||
|
a
|
||||||
|
return True
|
||||||
|
where
|
||||||
|
islink = liftIO $ isSymbolicLink <$> getSymbolicLinkStatus file
|
||||||
|
|
||||||
{- The size of the data for a key is checked against the size encoded in
|
{- The size of the data for a key is checked against the size encoded in
|
||||||
- the key's metadata, if available.
|
- the key's metadata, if available.
|
||||||
-
|
-
|
||||||
|
@ -461,7 +458,7 @@ recordFsckTime key = do
|
||||||
parent <- parentDir <$> calcRepo (gitAnnexLocation key)
|
parent <- parentDir <$> calcRepo (gitAnnexLocation key)
|
||||||
liftIO $ void $ tryIO $ do
|
liftIO $ void $ tryIO $ do
|
||||||
touchFile parent
|
touchFile parent
|
||||||
#ifndef __WINDOWS__
|
#ifndef mingw32_HOST_OS
|
||||||
setSticky parent
|
setSticky parent
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
288
Command/FuzzTest.hs
Normal file
288
Command/FuzzTest.hs
Normal file
|
@ -0,0 +1,288 @@
|
||||||
|
{- git-annex fuzz generator
|
||||||
|
-
|
||||||
|
- Copyright 2013 Joey Hess <joey@kitenet.net>
|
||||||
|
-
|
||||||
|
- Licensed under the GNU GPL version 3 or higher.
|
||||||
|
-}
|
||||||
|
|
||||||
|
module Command.FuzzTest where
|
||||||
|
|
||||||
|
import Common.Annex
|
||||||
|
import qualified Annex
|
||||||
|
import Command
|
||||||
|
import qualified Git.Config
|
||||||
|
import Config
|
||||||
|
import Utility.ThreadScheduler
|
||||||
|
import Annex.Exception
|
||||||
|
import Utility.DiskFree
|
||||||
|
|
||||||
|
import Data.Time.Clock
|
||||||
|
import System.Random (getStdRandom, random, randomR)
|
||||||
|
import Test.QuickCheck
|
||||||
|
import Control.Concurrent
|
||||||
|
|
||||||
|
def :: [Command]
|
||||||
|
def = [ notBareRepo $ command "fuzztest" paramNothing seek SectionPlumbing
|
||||||
|
"generates fuzz test files"]
|
||||||
|
|
||||||
|
seek :: [CommandSeek]
|
||||||
|
seek = [withNothing start]
|
||||||
|
|
||||||
|
start :: CommandStart
|
||||||
|
start = do
|
||||||
|
guardTest
|
||||||
|
logf <- fromRepo gitAnnexFuzzTestLogFile
|
||||||
|
showStart "fuzztest" logf
|
||||||
|
logh <-liftIO $ openFile logf WriteMode
|
||||||
|
void $ forever $ fuzz logh
|
||||||
|
stop
|
||||||
|
|
||||||
|
guardTest :: Annex ()
|
||||||
|
guardTest = unlessM (fromMaybe False . Git.Config.isTrue <$> getConfig key "") $
|
||||||
|
error $ unlines
|
||||||
|
[ "Running fuzz tests *writes* to and *deletes* files in"
|
||||||
|
, "this repository, and pushes those changes to other"
|
||||||
|
, "repositories! This is a developer tool, not something"
|
||||||
|
, "to play with."
|
||||||
|
, ""
|
||||||
|
, "Refusing to run fuzz tests, since " ++ keyname ++ " is not set!"
|
||||||
|
]
|
||||||
|
where
|
||||||
|
key = annexConfig "eat-my-repository"
|
||||||
|
(ConfigKey keyname) = key
|
||||||
|
|
||||||
|
|
||||||
|
fuzz :: Handle -> Annex ()
|
||||||
|
fuzz logh = do
|
||||||
|
action <- genFuzzAction
|
||||||
|
record logh $ flip Started action
|
||||||
|
result <- tryAnnex $ runFuzzAction action
|
||||||
|
record logh $ flip Finished $
|
||||||
|
either (const False) (const True) result
|
||||||
|
|
||||||
|
record :: Handle -> (UTCTime -> TimeStampedFuzzAction) -> Annex ()
|
||||||
|
record h tmpl = liftIO $ do
|
||||||
|
now <- getCurrentTime
|
||||||
|
let s = show $ tmpl now
|
||||||
|
print s
|
||||||
|
hPrint h s
|
||||||
|
hFlush h
|
||||||
|
|
||||||
|
{- Delay for either a fraction of a second, or a few seconds, or up
|
||||||
|
- to 1 minute.
|
||||||
|
-
|
||||||
|
- The MinutesDelay is used as an opportunity to do housekeeping tasks.
|
||||||
|
-}
|
||||||
|
randomDelay :: Delay -> Annex ()
|
||||||
|
randomDelay TinyDelay = liftIO $
|
||||||
|
threadDelay =<< getStdRandom (randomR (10000, 1000000))
|
||||||
|
randomDelay SecondsDelay = liftIO $
|
||||||
|
threadDelaySeconds =<< Seconds <$> getStdRandom (randomR (1, 10))
|
||||||
|
randomDelay MinutesDelay = do
|
||||||
|
liftIO $ threadDelaySeconds =<< Seconds <$> getStdRandom (randomR (1, 60))
|
||||||
|
reserve <- annexDiskReserve <$> Annex.getGitConfig
|
||||||
|
free <- liftIO $ getDiskFree "."
|
||||||
|
case free of
|
||||||
|
Just have | have < reserve -> do
|
||||||
|
warning "Low disk space; fuzz test paused."
|
||||||
|
liftIO $ threadDelaySeconds (Seconds 60)
|
||||||
|
randomDelay MinutesDelay
|
||||||
|
_ -> noop
|
||||||
|
|
||||||
|
data Delay
|
||||||
|
= TinyDelay
|
||||||
|
| SecondsDelay
|
||||||
|
| MinutesDelay
|
||||||
|
deriving (Read, Show, Eq)
|
||||||
|
|
||||||
|
instance Arbitrary Delay where
|
||||||
|
arbitrary = elements [TinyDelay, SecondsDelay, MinutesDelay]
|
||||||
|
|
||||||
|
data FuzzFile = FuzzFile FilePath
|
||||||
|
deriving (Read, Show, Eq)
|
||||||
|
|
||||||
|
data FuzzDir = FuzzDir FilePath
|
||||||
|
deriving (Read, Show, Eq)
|
||||||
|
|
||||||
|
instance Arbitrary FuzzFile where
|
||||||
|
arbitrary = FuzzFile <$> arbitrary
|
||||||
|
|
||||||
|
instance Arbitrary FuzzDir where
|
||||||
|
arbitrary = FuzzDir <$> arbitrary
|
||||||
|
|
||||||
|
class ToFilePath a where
|
||||||
|
toFilePath :: a -> FilePath
|
||||||
|
|
||||||
|
instance ToFilePath FuzzFile where
|
||||||
|
toFilePath (FuzzFile f) = f
|
||||||
|
|
||||||
|
instance ToFilePath FuzzDir where
|
||||||
|
toFilePath (FuzzDir d) = d
|
||||||
|
|
||||||
|
isFuzzFile :: FilePath -> Bool
|
||||||
|
isFuzzFile f = "fuzzfile_" `isPrefixOf` takeFileName f
|
||||||
|
|
||||||
|
isFuzzDir :: FilePath -> Bool
|
||||||
|
isFuzzDir d = "fuzzdir_" `isPrefixOf` d
|
||||||
|
|
||||||
|
mkFuzzFile :: FilePath -> [FuzzDir] -> FuzzFile
|
||||||
|
mkFuzzFile file dirs = FuzzFile $ joinPath (map toFilePath dirs) </> ("fuzzfile_" ++ file)
|
||||||
|
|
||||||
|
mkFuzzDir :: Int -> FuzzDir
|
||||||
|
mkFuzzDir n = FuzzDir $ "fuzzdir_" ++ show n
|
||||||
|
|
||||||
|
{- File is placed inside a directory hierarchy up to 4 subdirectories deep. -}
|
||||||
|
genFuzzFile :: IO FuzzFile
|
||||||
|
genFuzzFile = do
|
||||||
|
n <- getStdRandom $ randomR (0, 4)
|
||||||
|
dirs <- replicateM n genFuzzDir
|
||||||
|
file <- show <$> (getStdRandom random :: IO Int)
|
||||||
|
return $ mkFuzzFile file dirs
|
||||||
|
|
||||||
|
{- Only 16 distinct subdirectories are used. When nested 4 deep, this
|
||||||
|
- yields 69904 total directories max, which is below the default Linux
|
||||||
|
- inotify limit of 81920. The goal is not to run the assistant out of
|
||||||
|
- inotify descriptors. -}
|
||||||
|
genFuzzDir :: IO FuzzDir
|
||||||
|
genFuzzDir = mkFuzzDir <$> (getStdRandom (randomR (1,16)) :: IO Int)
|
||||||
|
|
||||||
|
localFile :: FilePath -> Bool
|
||||||
|
localFile f
|
||||||
|
| isAbsolute f = False
|
||||||
|
| ".." `isInfixOf` f = False
|
||||||
|
| ".git" `isPrefixOf` f = False
|
||||||
|
| otherwise = True
|
||||||
|
|
||||||
|
data TimeStampedFuzzAction
|
||||||
|
= Started UTCTime FuzzAction
|
||||||
|
| Finished UTCTime Bool
|
||||||
|
deriving (Read, Show)
|
||||||
|
|
||||||
|
data FuzzAction
|
||||||
|
= FuzzAdd FuzzFile
|
||||||
|
| FuzzDelete FuzzFile
|
||||||
|
| FuzzMove FuzzFile FuzzFile
|
||||||
|
| FuzzModify FuzzFile
|
||||||
|
| FuzzDeleteDir FuzzDir
|
||||||
|
| FuzzMoveDir FuzzDir FuzzDir
|
||||||
|
| FuzzPause Delay
|
||||||
|
deriving (Read, Show, Eq)
|
||||||
|
|
||||||
|
instance Arbitrary FuzzAction where
|
||||||
|
arbitrary = frequency
|
||||||
|
[ (50, FuzzAdd <$> arbitrary)
|
||||||
|
, (50, FuzzDelete <$> arbitrary)
|
||||||
|
, (10, FuzzMove <$> arbitrary <*> arbitrary)
|
||||||
|
, (10, FuzzModify <$> arbitrary)
|
||||||
|
, (10, FuzzDeleteDir <$> arbitrary)
|
||||||
|
, (10, FuzzMoveDir <$> arbitrary <*> arbitrary)
|
||||||
|
, (10, FuzzPause <$> arbitrary)
|
||||||
|
]
|
||||||
|
|
||||||
|
runFuzzAction :: FuzzAction -> Annex ()
|
||||||
|
runFuzzAction (FuzzAdd (FuzzFile f)) = liftIO $ do
|
||||||
|
createDirectoryIfMissing True $ parentDir f
|
||||||
|
n <- getStdRandom random :: IO Int
|
||||||
|
writeFile f $ show n ++ "\n"
|
||||||
|
runFuzzAction (FuzzDelete (FuzzFile f)) = liftIO $ nukeFile f
|
||||||
|
runFuzzAction (FuzzMove (FuzzFile src) (FuzzFile dest)) = liftIO $
|
||||||
|
rename src dest
|
||||||
|
runFuzzAction (FuzzModify (FuzzFile f)) = whenM isDirect $ liftIO $ do
|
||||||
|
n <- getStdRandom random :: IO Int
|
||||||
|
appendFile f $ show n ++ "\n"
|
||||||
|
runFuzzAction (FuzzDeleteDir (FuzzDir d)) = liftIO $
|
||||||
|
removeDirectoryRecursive d
|
||||||
|
runFuzzAction (FuzzMoveDir (FuzzDir src) (FuzzDir dest)) = liftIO $
|
||||||
|
rename src dest
|
||||||
|
runFuzzAction (FuzzPause d) = randomDelay d
|
||||||
|
|
||||||
|
genFuzzAction :: Annex FuzzAction
|
||||||
|
genFuzzAction = do
|
||||||
|
tmpl <- liftIO $ Prelude.head <$> sample' (arbitrary :: Gen FuzzAction)
|
||||||
|
-- Fix up template action to make sense in the current repo tree.
|
||||||
|
case tmpl of
|
||||||
|
FuzzAdd _ -> do
|
||||||
|
f <- liftIO newFile
|
||||||
|
maybe genFuzzAction (return . FuzzAdd) f
|
||||||
|
FuzzDelete _ -> do
|
||||||
|
f <- liftIO $ existingFile 0 ""
|
||||||
|
maybe genFuzzAction (return . FuzzDelete) f
|
||||||
|
FuzzMove _ _ -> do
|
||||||
|
src <- liftIO $ existingFile 0 ""
|
||||||
|
dest <- liftIO newFile
|
||||||
|
case (src, dest) of
|
||||||
|
(Just s, Just d) -> return $ FuzzMove s d
|
||||||
|
_ -> genFuzzAction
|
||||||
|
FuzzMoveDir _ _ -> do
|
||||||
|
md <- liftIO existingDir
|
||||||
|
case md of
|
||||||
|
Nothing -> genFuzzAction
|
||||||
|
Just d -> do
|
||||||
|
newd <- liftIO $ newDir (parentDir $ toFilePath d)
|
||||||
|
maybe genFuzzAction (return . FuzzMoveDir d) newd
|
||||||
|
FuzzDeleteDir _ -> do
|
||||||
|
d <- liftIO existingDir
|
||||||
|
maybe genFuzzAction (return . FuzzDeleteDir) d
|
||||||
|
FuzzModify _ -> do
|
||||||
|
f <- liftIO $ existingFile 0 ""
|
||||||
|
maybe genFuzzAction (return . FuzzModify) f
|
||||||
|
FuzzPause _ -> return tmpl
|
||||||
|
|
||||||
|
existingFile :: Int -> FilePath -> IO (Maybe FuzzFile)
|
||||||
|
existingFile 0 _ = return Nothing
|
||||||
|
existingFile n top = do
|
||||||
|
dir <- existingDirIncludingTop
|
||||||
|
contents <- catchDefaultIO [] (getDirectoryContents dir)
|
||||||
|
let files = filter isFuzzFile contents
|
||||||
|
if null files
|
||||||
|
then do
|
||||||
|
let dirs = filter isFuzzDir contents
|
||||||
|
if null dirs
|
||||||
|
then return Nothing
|
||||||
|
else do
|
||||||
|
i <- getStdRandom $ randomR (0, length dirs - 1)
|
||||||
|
existingFile (n - 1) (top </> dirs !! i)
|
||||||
|
else do
|
||||||
|
i <- getStdRandom $ randomR (0, length files - 1)
|
||||||
|
return $ Just $ FuzzFile $ top </> dir </> files !! i
|
||||||
|
|
||||||
|
existingDirIncludingTop :: IO FilePath
|
||||||
|
existingDirIncludingTop = do
|
||||||
|
dirs <- filter isFuzzDir <$> getDirectoryContents "."
|
||||||
|
if null dirs
|
||||||
|
then return "."
|
||||||
|
else do
|
||||||
|
n <- getStdRandom $ randomR (0, length dirs)
|
||||||
|
return $ ("." : dirs) !! n
|
||||||
|
|
||||||
|
existingDir :: IO (Maybe FuzzDir)
|
||||||
|
existingDir = do
|
||||||
|
d <- existingDirIncludingTop
|
||||||
|
return $ if isFuzzDir d
|
||||||
|
then Just $ FuzzDir d
|
||||||
|
else Nothing
|
||||||
|
|
||||||
|
newFile :: IO (Maybe FuzzFile)
|
||||||
|
newFile = go (100 :: Int)
|
||||||
|
where
|
||||||
|
go 0 = return Nothing
|
||||||
|
go n = do
|
||||||
|
f <- genFuzzFile
|
||||||
|
ifM (doesnotexist (toFilePath f))
|
||||||
|
( return $ Just f
|
||||||
|
, go (n - 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
newDir :: FilePath -> IO (Maybe FuzzDir)
|
||||||
|
newDir parent = go (100 :: Int)
|
||||||
|
where
|
||||||
|
go 0 = return Nothing
|
||||||
|
go n = do
|
||||||
|
(FuzzDir d) <- genFuzzDir
|
||||||
|
ifM (doesnotexist (parent </> d))
|
||||||
|
( return $ Just $ FuzzDir d
|
||||||
|
, go (n - 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
doesnotexist :: FilePath -> IO Bool
|
||||||
|
doesnotexist f = isNothing <$> catchMaybeIO (getSymbolicLinkStatus f)
|
|
@ -1,6 +1,6 @@
|
||||||
{- git-annex command
|
{- git-annex command
|
||||||
-
|
-
|
||||||
- Copyright 2010 Joey Hess <joey@kitenet.net>
|
- Copyright 2010, 2013 Joey Hess <joey@kitenet.net>
|
||||||
-
|
-
|
||||||
- Licensed under the GNU GPL version 3 or higher.
|
- Licensed under the GNU GPL version 3 or higher.
|
||||||
-}
|
-}
|
||||||
|
@ -14,37 +14,52 @@ import Annex.Content
|
||||||
import qualified Command.Move
|
import qualified Command.Move
|
||||||
import Logs.Transfer
|
import Logs.Transfer
|
||||||
import Annex.Wanted
|
import Annex.Wanted
|
||||||
|
import GitAnnex.Options
|
||||||
|
import Types.Key
|
||||||
|
|
||||||
def :: [Command]
|
def :: [Command]
|
||||||
def = [withOptions [Command.Move.fromOption] $ command "get" paramPaths seek
|
def = [withOptions getOptions $ command "get" paramPaths seek
|
||||||
SectionCommon "make content of annexed files available"]
|
SectionCommon "make content of annexed files available"]
|
||||||
|
|
||||||
|
getOptions :: [Option]
|
||||||
|
getOptions = [Command.Move.fromOption] ++ keyOptions
|
||||||
|
|
||||||
seek :: [CommandSeek]
|
seek :: [CommandSeek]
|
||||||
seek = [withField Command.Move.fromOption Remote.byNameWithUUID $ \from ->
|
seek =
|
||||||
withFilesInGit $ whenAnnexed $ start from]
|
[ withField Command.Move.fromOption Remote.byNameWithUUID $ \from ->
|
||||||
|
withKeyOptions (startKeys from) $
|
||||||
|
withFilesInGit $ whenAnnexed $ start from
|
||||||
|
]
|
||||||
|
|
||||||
start :: Maybe Remote -> FilePath -> (Key, Backend) -> CommandStart
|
start :: Maybe Remote -> FilePath -> (Key, Backend) -> CommandStart
|
||||||
start from file (key, _) = stopUnless (not <$> inAnnex key) $
|
start from file (key, _) = start' expensivecheck from key (Just file)
|
||||||
stopUnless (checkAuto (numCopiesCheck file key (<) <||> wantGet False (Just file))) $ do
|
|
||||||
case from of
|
|
||||||
Nothing -> go $ perform key file
|
|
||||||
Just src ->
|
|
||||||
-- get --from = copy --from
|
|
||||||
stopUnless (Command.Move.fromOk src key) $
|
|
||||||
go $ Command.Move.fromPerform src False key file
|
|
||||||
where
|
where
|
||||||
go a = do
|
expensivecheck = checkAuto (numCopiesCheck file key (<) <||> wantGet False (Just file))
|
||||||
showStart "get" file
|
|
||||||
|
startKeys :: Maybe Remote -> Key -> CommandStart
|
||||||
|
startKeys from key = start' (return True) from key Nothing
|
||||||
|
|
||||||
|
start' :: Annex Bool -> Maybe Remote -> Key -> AssociatedFile -> CommandStart
|
||||||
|
start' expensivecheck from key afile = stopUnless (not <$> inAnnex key) $
|
||||||
|
stopUnless expensivecheck $
|
||||||
|
case from of
|
||||||
|
Nothing -> go $ perform key afile
|
||||||
|
Just src ->
|
||||||
|
stopUnless (Command.Move.fromOk src key) $
|
||||||
|
go $ Command.Move.fromPerform src False key afile
|
||||||
|
where
|
||||||
|
go a = do
|
||||||
|
showStart "get" (fromMaybe (key2file key) afile)
|
||||||
next a
|
next a
|
||||||
|
|
||||||
perform :: Key -> FilePath -> CommandPerform
|
perform :: Key -> AssociatedFile -> CommandPerform
|
||||||
perform key file = stopUnless (getViaTmp key $ getKeyFile key file) $
|
perform key afile = stopUnless (getViaTmp key $ getKeyFile key afile) $
|
||||||
next $ return True -- no cleanup needed
|
next $ return True -- no cleanup needed
|
||||||
|
|
||||||
{- Try to find a copy of the file in one of the remotes,
|
{- Try to find a copy of the file in one of the remotes,
|
||||||
- and copy it to here. -}
|
- and copy it to here. -}
|
||||||
getKeyFile :: Key -> FilePath -> FilePath -> Annex Bool
|
getKeyFile :: Key -> AssociatedFile -> FilePath -> Annex Bool
|
||||||
getKeyFile key file dest = dispatch =<< Remote.keyPossibilities key
|
getKeyFile key afile dest = dispatch =<< Remote.keyPossibilities key
|
||||||
where
|
where
|
||||||
dispatch [] = do
|
dispatch [] = do
|
||||||
showNote "not available"
|
showNote "not available"
|
||||||
|
@ -69,7 +84,7 @@ getKeyFile key file dest = dispatch =<< Remote.keyPossibilities key
|
||||||
either (const False) id <$> Remote.hasKey r key
|
either (const False) id <$> Remote.hasKey r key
|
||||||
| otherwise = return True
|
| otherwise = return True
|
||||||
docopy r continue = do
|
docopy r continue = do
|
||||||
ok <- download (Remote.uuid r) key (Just file) noRetry $ \p -> do
|
ok <- download (Remote.uuid r) key afile noRetry $ \p -> do
|
||||||
showAction $ "from " ++ Remote.name r
|
showAction $ "from " ++ Remote.name r
|
||||||
Remote.retrieveKeyFile r key (Just file) dest p
|
Remote.retrieveKeyFile r key afile dest p
|
||||||
if ok then return ok else continue
|
if ok then return ok else continue
|
||||||
|
|
|
@ -17,7 +17,7 @@ import qualified Data.Set as S
|
||||||
|
|
||||||
def :: [Command]
|
def :: [Command]
|
||||||
def = [command "group" (paramPair paramRemote paramDesc) seek
|
def = [command "group" (paramPair paramRemote paramDesc) seek
|
||||||
SectionCommon "add a repository to a group"]
|
SectionSetup "add a repository to a group"]
|
||||||
|
|
||||||
seek :: [CommandSeek]
|
seek :: [CommandSeek]
|
||||||
seek = [withWords start]
|
seek = [withWords start]
|
||||||
|
|
|
@ -15,7 +15,7 @@ import qualified Annex
|
||||||
import qualified Command.Add
|
import qualified Command.Add
|
||||||
|
|
||||||
def :: [Command]
|
def :: [Command]
|
||||||
def = [notDirect $ notBareRepo $ command "import" paramPaths seek
|
def = [notBareRepo $ command "import" paramPaths seek
|
||||||
SectionCommon "move and add files from outside git working copy"]
|
SectionCommon "move and add files from outside git working copy"]
|
||||||
|
|
||||||
seek :: [CommandSeek]
|
seek :: [CommandSeek]
|
||||||
|
|
173
Command/ImportFeed.hs
Normal file
173
Command/ImportFeed.hs
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
{- git-annex command
|
||||||
|
-
|
||||||
|
- Copyright 2013 Joey Hess <joey@kitenet.net>
|
||||||
|
-
|
||||||
|
- Licensed under the GNU GPL version 3 or higher.
|
||||||
|
-}
|
||||||
|
|
||||||
|
module Command.ImportFeed where
|
||||||
|
|
||||||
|
import Text.Feed.Import
|
||||||
|
import Text.Feed.Query
|
||||||
|
import Text.Feed.Types
|
||||||
|
import qualified Data.Set as S
|
||||||
|
import qualified Data.Map as M
|
||||||
|
import Data.Char
|
||||||
|
|
||||||
|
import Common.Annex
|
||||||
|
import qualified Annex
|
||||||
|
import Command
|
||||||
|
import qualified Utility.Url as Url
|
||||||
|
import Logs.Web
|
||||||
|
import qualified Option
|
||||||
|
import qualified Utility.Format
|
||||||
|
import Utility.Tmp
|
||||||
|
import Command.AddUrl (addUrlFile, relaxedOption)
|
||||||
|
|
||||||
|
def :: [Command]
|
||||||
|
def = [notBareRepo $ withOptions [templateOption, relaxedOption] $
|
||||||
|
command "importfeed" (paramRepeating paramUrl) seek
|
||||||
|
SectionCommon "import files from podcast feeds"]
|
||||||
|
|
||||||
|
templateOption :: Option
|
||||||
|
templateOption = Option.field [] "template" paramFormat "template for filenames"
|
||||||
|
|
||||||
|
seek :: [CommandSeek]
|
||||||
|
seek = [withField templateOption return $ \tmpl ->
|
||||||
|
withFlag relaxedOption $ \relaxed ->
|
||||||
|
withValue (getCache tmpl) $ \cache ->
|
||||||
|
withStrings $ start relaxed cache]
|
||||||
|
|
||||||
|
start :: Bool -> Cache -> URLString -> CommandStart
|
||||||
|
start relaxed cache url = do
|
||||||
|
showStart "importfeed" url
|
||||||
|
next $ perform relaxed cache url
|
||||||
|
|
||||||
|
perform :: Bool -> Cache -> URLString -> CommandPerform
|
||||||
|
perform relaxed cache url = do
|
||||||
|
v <- findEnclosures url
|
||||||
|
case v of
|
||||||
|
Just l | not (null l) -> do
|
||||||
|
mapM_ (downloadEnclosure relaxed cache) l
|
||||||
|
next $ return True
|
||||||
|
_ -> stop
|
||||||
|
|
||||||
|
data ToDownload = ToDownload
|
||||||
|
{ feed :: Feed
|
||||||
|
, item :: Item
|
||||||
|
, location :: URLString
|
||||||
|
}
|
||||||
|
|
||||||
|
mkToDownload :: Feed -> Item -> Maybe ToDownload
|
||||||
|
mkToDownload f i = case getItemEnclosure i of
|
||||||
|
Nothing -> Nothing
|
||||||
|
Just (enclosureurl, _, _) -> Just $ ToDownload f i enclosureurl
|
||||||
|
|
||||||
|
data Cache = Cache
|
||||||
|
{ knownurls :: S.Set URLString
|
||||||
|
, template :: Utility.Format.Format
|
||||||
|
}
|
||||||
|
|
||||||
|
getCache :: Maybe String -> Annex Cache
|
||||||
|
getCache opttemplate = ifM (Annex.getState Annex.force)
|
||||||
|
( ret S.empty
|
||||||
|
, do
|
||||||
|
showSideAction "checking known urls"
|
||||||
|
ret =<< S.fromList <$> knownUrls
|
||||||
|
)
|
||||||
|
where
|
||||||
|
tmpl = Utility.Format.gen $ fromMaybe defaultTemplate opttemplate
|
||||||
|
ret s = return $ Cache s tmpl
|
||||||
|
|
||||||
|
findEnclosures :: URLString -> Annex (Maybe [ToDownload])
|
||||||
|
findEnclosures url = go =<< downloadFeed url
|
||||||
|
where
|
||||||
|
go Nothing = do
|
||||||
|
warning $ "failed to parse feed " ++ url
|
||||||
|
return Nothing
|
||||||
|
go (Just f) = return $ Just $
|
||||||
|
mapMaybe (mkToDownload f) (feedItems f)
|
||||||
|
|
||||||
|
{- Feeds change, so a feed download cannot be resumed. -}
|
||||||
|
downloadFeed :: URLString -> Annex (Maybe Feed)
|
||||||
|
downloadFeed url = do
|
||||||
|
showOutput
|
||||||
|
liftIO $ withTmpFile "feed" $ \f h -> do
|
||||||
|
fileEncoding h
|
||||||
|
ifM (Url.download url [] [] f)
|
||||||
|
( parseFeedString <$> hGetContentsStrict h
|
||||||
|
, return Nothing
|
||||||
|
)
|
||||||
|
|
||||||
|
{- Avoids downloading any urls that are already known to be associated
|
||||||
|
- with a file in the annex, unless forced. -}
|
||||||
|
downloadEnclosure :: Bool -> Cache -> ToDownload -> Annex ()
|
||||||
|
downloadEnclosure relaxed cache enclosure
|
||||||
|
| S.member url (knownurls cache) =
|
||||||
|
whenM forced go
|
||||||
|
| otherwise = go
|
||||||
|
where
|
||||||
|
forced = Annex.getState Annex.force
|
||||||
|
url = location enclosure
|
||||||
|
go = do
|
||||||
|
dest <- makeunique (1 :: Integer) $ feedFile (template cache) enclosure
|
||||||
|
case dest of
|
||||||
|
Nothing -> noop
|
||||||
|
Just f -> do
|
||||||
|
showStart "addurl" f
|
||||||
|
ifM (addUrlFile relaxed url f)
|
||||||
|
( showEndOk
|
||||||
|
, showEndFail
|
||||||
|
)
|
||||||
|
{- Find a unique filename to save the url to.
|
||||||
|
- If the file exists, prefixes it with a number.
|
||||||
|
- When forced, the file may already exist and have the same
|
||||||
|
- url, in which case Nothing is returned as it does not need
|
||||||
|
- to be re-downloaded. -}
|
||||||
|
makeunique n file = ifM alreadyexists
|
||||||
|
( ifM forced
|
||||||
|
( ifAnnexed f checksameurl tryanother
|
||||||
|
, tryanother
|
||||||
|
)
|
||||||
|
, return $ Just f
|
||||||
|
)
|
||||||
|
where
|
||||||
|
f = if n < 2
|
||||||
|
then file
|
||||||
|
else
|
||||||
|
let (d, base) = splitFileName file
|
||||||
|
in d </> show n ++ "_" ++ base
|
||||||
|
tryanother = makeunique (n + 1) file
|
||||||
|
alreadyexists = liftIO $ isJust <$> catchMaybeIO (getSymbolicLinkStatus f)
|
||||||
|
checksameurl (k, _) = ifM (elem url <$> getUrls k)
|
||||||
|
( return Nothing
|
||||||
|
, tryanother
|
||||||
|
)
|
||||||
|
|
||||||
|
defaultTemplate :: String
|
||||||
|
defaultTemplate = "${feedtitle}/${itemtitle}${extension}"
|
||||||
|
|
||||||
|
{- Generates a filename to use for a feed item by filling out the template.
|
||||||
|
- The filename may not be unique. -}
|
||||||
|
feedFile :: Utility.Format.Format -> ToDownload -> FilePath
|
||||||
|
feedFile tmpl i = Utility.Format.format tmpl $ M.fromList
|
||||||
|
[ field "feedtitle" $ getFeedTitle $ feed i
|
||||||
|
, fieldMaybe "itemtitle" $ getItemTitle $ item i
|
||||||
|
, fieldMaybe "feedauthor" $ getFeedAuthor $ feed i
|
||||||
|
, fieldMaybe "itemauthor" $ getItemAuthor $ item i
|
||||||
|
, fieldMaybe "itemsummary" $ getItemSummary $ item i
|
||||||
|
, fieldMaybe "itemdescription" $ getItemDescription $ item i
|
||||||
|
, fieldMaybe "itemrights" $ getItemRights $ item i
|
||||||
|
, fieldMaybe "itemid" $ snd <$> getItemId (item i)
|
||||||
|
, ("extension", map sanitize $ takeExtension $ location i)
|
||||||
|
]
|
||||||
|
where
|
||||||
|
field k v =
|
||||||
|
let s = map sanitize v in
|
||||||
|
if null s then (k, "none") else (k, s)
|
||||||
|
fieldMaybe k Nothing = (k, "none")
|
||||||
|
fieldMaybe k (Just v) = field k v
|
||||||
|
|
||||||
|
sanitize c
|
||||||
|
| isSpace c || isPunctuation c || c == '/' = '_'
|
||||||
|
| otherwise = c
|
|
@ -59,7 +59,7 @@ perform = do
|
||||||
setDirect False
|
setDirect False
|
||||||
|
|
||||||
top <- fromRepo Git.repoPath
|
top <- fromRepo Git.repoPath
|
||||||
(l, clean) <- inRepo $ Git.LsFiles.stagedDetails [top]
|
(l, clean) <- inRepo $ Git.LsFiles.stagedOthersDetails [top]
|
||||||
forM_ l go
|
forM_ l go
|
||||||
void $ liftIO clean
|
void $ liftIO clean
|
||||||
next cleanup
|
next cleanup
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{- git-annex command
|
{- git-annex command
|
||||||
-
|
-
|
||||||
- Copyright 2011 Joey Hess <joey@kitenet.net>
|
- Copyright 2011, 2013 Joey Hess <joey@kitenet.net>
|
||||||
-
|
-
|
||||||
- Licensed under the GNU GPL version 3 or higher.
|
- Licensed under the GNU GPL version 3 or higher.
|
||||||
-}
|
-}
|
||||||
|
@ -10,22 +10,29 @@ module Command.Merge where
|
||||||
import Common.Annex
|
import Common.Annex
|
||||||
import Command
|
import Command
|
||||||
import qualified Annex.Branch
|
import qualified Annex.Branch
|
||||||
|
import qualified Git.Branch
|
||||||
|
import Command.Sync (mergeLocal)
|
||||||
|
|
||||||
def :: [Command]
|
def :: [Command]
|
||||||
def = [command "merge" paramNothing seek SectionMaintenance
|
def = [command "merge" paramNothing seek SectionMaintenance
|
||||||
"auto-merge remote changes into git-annex branch"]
|
"automatically merge changes from remotes"]
|
||||||
|
|
||||||
seek :: [CommandSeek]
|
seek :: [CommandSeek]
|
||||||
seek = [withNothing start]
|
seek =
|
||||||
|
[ withNothing mergeBranch
|
||||||
|
, withNothing mergeSynced
|
||||||
|
]
|
||||||
|
|
||||||
start :: CommandStart
|
mergeBranch :: CommandStart
|
||||||
start = do
|
mergeBranch = do
|
||||||
showStart "merge" "."
|
showStart "merge" "git-annex"
|
||||||
next perform
|
next $ do
|
||||||
|
Annex.Branch.update
|
||||||
|
-- commit explicitly, in case no remote branches were merged
|
||||||
|
Annex.Branch.commit "update"
|
||||||
|
next $ return True
|
||||||
|
|
||||||
perform :: CommandPerform
|
mergeSynced :: CommandStart
|
||||||
perform = do
|
mergeSynced = do
|
||||||
Annex.Branch.update
|
branch <- inRepo Git.Branch.current
|
||||||
-- commit explicitly, in case no remote branches were merged
|
maybe stop mergeLocal branch
|
||||||
Annex.Branch.commit "update"
|
|
||||||
next $ return True
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{- git-annex command
|
{- git-annex command
|
||||||
-
|
-
|
||||||
- Copyright 2010-2011 Joey Hess <joey@kitenet.net>
|
- Copyright 2010-2013 Joey Hess <joey@kitenet.net>
|
||||||
-
|
-
|
||||||
- Licensed under the GNU GPL version 3 or higher.
|
- Licensed under the GNU GPL version 3 or higher.
|
||||||
-}
|
-}
|
||||||
|
@ -17,9 +17,11 @@ import Annex.UUID
|
||||||
import qualified Option
|
import qualified Option
|
||||||
import Logs.Presence
|
import Logs.Presence
|
||||||
import Logs.Transfer
|
import Logs.Transfer
|
||||||
|
import GitAnnex.Options
|
||||||
|
import Types.Key
|
||||||
|
|
||||||
def :: [Command]
|
def :: [Command]
|
||||||
def = [withOptions options $ command "move" paramPaths seek
|
def = [withOptions moveOptions $ command "move" paramPaths seek
|
||||||
SectionCommon "move content of files to/from another repository"]
|
SectionCommon "move content of files to/from another repository"]
|
||||||
|
|
||||||
fromOption :: Option
|
fromOption :: Option
|
||||||
|
@ -28,29 +30,40 @@ fromOption = Option.field ['f'] "from" paramRemote "source remote"
|
||||||
toOption :: Option
|
toOption :: Option
|
||||||
toOption = Option.field ['t'] "to" paramRemote "destination remote"
|
toOption = Option.field ['t'] "to" paramRemote "destination remote"
|
||||||
|
|
||||||
options :: [Option]
|
moveOptions :: [Option]
|
||||||
options = [fromOption, toOption]
|
moveOptions = [fromOption, toOption] ++ keyOptions
|
||||||
|
|
||||||
seek :: [CommandSeek]
|
seek :: [CommandSeek]
|
||||||
seek = [withField toOption Remote.byNameWithUUID $ \to ->
|
seek =
|
||||||
withField fromOption Remote.byNameWithUUID $ \from ->
|
[ withField toOption Remote.byNameWithUUID $ \to ->
|
||||||
withFilesInGit $ whenAnnexed $ start to from True]
|
withField fromOption Remote.byNameWithUUID $ \from ->
|
||||||
|
withKeyOptions (startKey to from True) $
|
||||||
|
withFilesInGit $ whenAnnexed $ start to from True
|
||||||
|
]
|
||||||
|
|
||||||
start :: Maybe Remote -> Maybe Remote -> Bool -> FilePath -> (Key, Backend) -> CommandStart
|
start :: Maybe Remote -> Maybe Remote -> Bool -> FilePath -> (Key, Backend) -> CommandStart
|
||||||
start to from move file (key, _) = do
|
start to from move file (key, _) = start' to from move (Just file) key
|
||||||
|
|
||||||
|
startKey :: Maybe Remote -> Maybe Remote -> Bool -> Key -> CommandStart
|
||||||
|
startKey to from move key = start' to from move Nothing key
|
||||||
|
|
||||||
|
start' :: Maybe Remote -> Maybe Remote -> Bool -> AssociatedFile -> Key -> CommandStart
|
||||||
|
start' to from move afile key = do
|
||||||
noAuto
|
noAuto
|
||||||
case (from, to) of
|
case (from, to) of
|
||||||
(Nothing, Nothing) -> error "specify either --from or --to"
|
(Nothing, Nothing) -> error "specify either --from or --to"
|
||||||
(Nothing, Just dest) -> toStart dest move file key
|
(Nothing, Just dest) -> toStart dest move afile key
|
||||||
(Just src, Nothing) -> fromStart src move file key
|
(Just src, Nothing) -> fromStart src move afile key
|
||||||
(_ , _) -> error "only one of --from or --to can be specified"
|
(_ , _) -> error "only one of --from or --to can be specified"
|
||||||
where
|
where
|
||||||
noAuto = when move $ whenM (Annex.getState Annex.auto) $ error
|
noAuto = when move $ whenM (Annex.getState Annex.auto) $ error
|
||||||
"--auto is not supported for move"
|
"--auto is not supported for move"
|
||||||
|
|
||||||
showMoveAction :: Bool -> FilePath -> Annex ()
|
showMoveAction :: Bool -> Key -> AssociatedFile -> Annex ()
|
||||||
showMoveAction True file = showStart "move" file
|
showMoveAction True _ (Just file) = showStart "move" file
|
||||||
showMoveAction False file = showStart "copy" file
|
showMoveAction False _ (Just file) = showStart "copy" file
|
||||||
|
showMoveAction True key Nothing = showStart "move" (key2file key)
|
||||||
|
showMoveAction False key Nothing = showStart "copy" (key2file key)
|
||||||
|
|
||||||
{- Moves (or copies) the content of an annexed file to a remote.
|
{- Moves (or copies) the content of an annexed file to a remote.
|
||||||
-
|
-
|
||||||
|
@ -61,17 +74,17 @@ showMoveAction False file = showStart "copy" file
|
||||||
- A file's content can be moved even if there are insufficient copies to
|
- A file's content can be moved even if there are insufficient copies to
|
||||||
- allow it to be dropped.
|
- allow it to be dropped.
|
||||||
-}
|
-}
|
||||||
toStart :: Remote -> Bool -> FilePath -> Key -> CommandStart
|
toStart :: Remote -> Bool -> AssociatedFile -> Key -> CommandStart
|
||||||
toStart dest move file key = do
|
toStart dest move afile key = do
|
||||||
u <- getUUID
|
u <- getUUID
|
||||||
ishere <- inAnnex key
|
ishere <- inAnnex key
|
||||||
if not ishere || u == Remote.uuid dest
|
if not ishere || u == Remote.uuid dest
|
||||||
then stop -- not here, so nothing to do
|
then stop -- not here, so nothing to do
|
||||||
else do
|
else do
|
||||||
showMoveAction move file
|
showMoveAction move key afile
|
||||||
next $ toPerform dest move key file
|
next $ toPerform dest move key afile
|
||||||
toPerform :: Remote -> Bool -> Key -> FilePath -> CommandPerform
|
toPerform :: Remote -> Bool -> Key -> AssociatedFile -> CommandPerform
|
||||||
toPerform dest move key file = moveLock move key $ do
|
toPerform dest move key afile = moveLock move key $ do
|
||||||
-- Checking the remote is expensive, so not done in the start step.
|
-- Checking the remote is expensive, so not done in the start step.
|
||||||
-- In fast mode, location tracking is assumed to be correct,
|
-- In fast mode, location tracking is assumed to be correct,
|
||||||
-- and an explicit check is not done, when copying. When moving,
|
-- and an explicit check is not done, when copying. When moving,
|
||||||
|
@ -87,8 +100,8 @@ toPerform dest move key file = moveLock move key $ do
|
||||||
stop
|
stop
|
||||||
Right False -> do
|
Right False -> do
|
||||||
showAction $ "to " ++ Remote.name dest
|
showAction $ "to " ++ Remote.name dest
|
||||||
ok <- upload (Remote.uuid dest) key (Just file) noRetry $
|
ok <- upload (Remote.uuid dest) key afile noRetry $
|
||||||
Remote.storeKey dest key (Just file)
|
Remote.storeKey dest key afile
|
||||||
if ok
|
if ok
|
||||||
then do
|
then do
|
||||||
Remote.logStatus dest key InfoPresent
|
Remote.logStatus dest key InfoPresent
|
||||||
|
@ -117,14 +130,14 @@ toPerform dest move key file = moveLock move key $ do
|
||||||
- If the current repository already has the content, it is still removed
|
- If the current repository already has the content, it is still removed
|
||||||
- from the remote.
|
- from the remote.
|
||||||
-}
|
-}
|
||||||
fromStart :: Remote -> Bool -> FilePath -> Key -> CommandStart
|
fromStart :: Remote -> Bool -> AssociatedFile -> Key -> CommandStart
|
||||||
fromStart src move file key
|
fromStart src move afile key
|
||||||
| move = go
|
| move = go
|
||||||
| otherwise = stopUnless (not <$> inAnnex key) go
|
| otherwise = stopUnless (not <$> inAnnex key) go
|
||||||
where
|
where
|
||||||
go = stopUnless (fromOk src key) $ do
|
go = stopUnless (fromOk src key) $ do
|
||||||
showMoveAction move file
|
showMoveAction move key afile
|
||||||
next $ fromPerform src move key file
|
next $ fromPerform src move key afile
|
||||||
|
|
||||||
fromOk :: Remote -> Key -> Annex Bool
|
fromOk :: Remote -> Key -> Annex Bool
|
||||||
fromOk src key
|
fromOk src key
|
||||||
|
@ -137,16 +150,16 @@ fromOk src key
|
||||||
remotes <- Remote.keyPossibilities key
|
remotes <- Remote.keyPossibilities key
|
||||||
return $ u /= Remote.uuid src && elem src remotes
|
return $ u /= Remote.uuid src && elem src remotes
|
||||||
|
|
||||||
fromPerform :: Remote -> Bool -> Key -> FilePath -> CommandPerform
|
fromPerform :: Remote -> Bool -> Key -> AssociatedFile -> CommandPerform
|
||||||
fromPerform src move key file = moveLock move key $
|
fromPerform src move key afile = moveLock move key $
|
||||||
ifM (inAnnex key)
|
ifM (inAnnex key)
|
||||||
( handle move True
|
( handle move True
|
||||||
, handle move =<< go
|
, handle move =<< go
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
go = download (Remote.uuid src) key (Just file) noRetry $ \p -> do
|
go = download (Remote.uuid src) key afile noRetry $ \p -> do
|
||||||
showAction $ "from " ++ Remote.name src
|
showAction $ "from " ++ Remote.name src
|
||||||
getViaTmp key $ \t -> Remote.retrieveKeyFile src key (Just file) t p
|
getViaTmp key $ \t -> Remote.retrieveKeyFile src key afile t p
|
||||||
handle _ False = stop -- failed
|
handle _ False = stop -- failed
|
||||||
handle False True = next $ return True -- copy complete
|
handle False True = next $ return True -- copy complete
|
||||||
handle True True = do -- finish moving
|
handle True True = do -- finish moving
|
||||||
|
|
|
@ -7,8 +7,6 @@
|
||||||
|
|
||||||
module Command.ReKey where
|
module Command.ReKey where
|
||||||
|
|
||||||
import System.PosixCompat.Files
|
|
||||||
|
|
||||||
import Common.Annex
|
import Common.Annex
|
||||||
import Command
|
import Command
|
||||||
import qualified Annex
|
import qualified Annex
|
||||||
|
@ -17,7 +15,6 @@ import Annex.Content
|
||||||
import qualified Command.Add
|
import qualified Command.Add
|
||||||
import Logs.Web
|
import Logs.Web
|
||||||
import Logs.Location
|
import Logs.Location
|
||||||
import Config
|
|
||||||
import Utility.CopyFile
|
import Utility.CopyFile
|
||||||
|
|
||||||
def :: [Command]
|
def :: [Command]
|
||||||
|
@ -49,18 +46,14 @@ perform file oldkey newkey = do
|
||||||
return True
|
return True
|
||||||
next $ cleanup file oldkey newkey
|
next $ cleanup file oldkey newkey
|
||||||
|
|
||||||
{- Make a hard link to the old key content, to avoid wasting disk space. -}
|
{- Make a hard link to the old key content (when supported),
|
||||||
|
- to avoid wasting disk space. -}
|
||||||
linkKey :: Key -> Key -> Annex Bool
|
linkKey :: Key -> Key -> Annex Bool
|
||||||
linkKey oldkey newkey = getViaTmpUnchecked newkey $ \tmp -> do
|
linkKey oldkey newkey = getViaTmpUnchecked newkey $ \tmp -> do
|
||||||
src <- calcRepo $ gitAnnexLocation oldkey
|
src <- calcRepo $ gitAnnexLocation oldkey
|
||||||
ifM (liftIO $ doesFileExist tmp)
|
liftIO $ ifM (doesFileExist tmp)
|
||||||
( return True
|
( return True
|
||||||
, ifM crippledFileSystem
|
, createLinkOrCopy src tmp
|
||||||
( liftIO $ copyFileExternal src tmp
|
|
||||||
, do
|
|
||||||
liftIO $ createLink src tmp
|
|
||||||
return True
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
cleanup :: FilePath -> Key -> Key -> CommandCleanup
|
cleanup :: FilePath -> Key -> Key -> CommandCleanup
|
||||||
|
|
|
@ -35,6 +35,7 @@ import Config
|
||||||
import Utility.Percentage
|
import Utility.Percentage
|
||||||
import Logs.Transfer
|
import Logs.Transfer
|
||||||
import Types.TrustLevel
|
import Types.TrustLevel
|
||||||
|
import Types.FileMatcher
|
||||||
import qualified Limit
|
import qualified Limit
|
||||||
|
|
||||||
-- a named computation that produces a statistic
|
-- a named computation that produces a statistic
|
||||||
|
@ -101,7 +102,6 @@ global_fast_stats =
|
||||||
, remote_list Trusted
|
, remote_list Trusted
|
||||||
, remote_list SemiTrusted
|
, remote_list SemiTrusted
|
||||||
, remote_list UnTrusted
|
, remote_list UnTrusted
|
||||||
, remote_list DeadTrusted
|
|
||||||
, transfer_list
|
, transfer_list
|
||||||
, disk_size
|
, disk_size
|
||||||
]
|
]
|
||||||
|
@ -286,7 +286,7 @@ getLocalStatInfo dir = do
|
||||||
where
|
where
|
||||||
initial = (emptyKeyData, emptyKeyData)
|
initial = (emptyKeyData, emptyKeyData)
|
||||||
update matcher key file vs@(presentdata, referenceddata) =
|
update matcher key file vs@(presentdata, referenceddata) =
|
||||||
ifM (matcher $ Annex.FileInfo file file)
|
ifM (matcher $ FileInfo file file)
|
||||||
( (,)
|
( (,)
|
||||||
<$> ifM (inAnnex key)
|
<$> ifM (inAnnex key)
|
||||||
( return $ addKey key presentdata
|
( return $ addKey key presentdata
|
||||||
|
|
|
@ -28,6 +28,7 @@ import qualified Types.Remote
|
||||||
import qualified Remote.Git
|
import qualified Remote.Git
|
||||||
import Types.Key
|
import Types.Key
|
||||||
import Config
|
import Config
|
||||||
|
import Annex.ReplaceFile
|
||||||
|
|
||||||
import Data.Hash.MD5
|
import Data.Hash.MD5
|
||||||
|
|
||||||
|
@ -137,7 +138,8 @@ pullRemote remote branch = do
|
||||||
|
|
||||||
{- The remote probably has both a master and a synced/master branch.
|
{- The remote probably has both a master and a synced/master branch.
|
||||||
- Which to merge from? Well, the master has whatever latest changes
|
- Which to merge from? Well, the master has whatever latest changes
|
||||||
- were committed, while the synced/master may have changes that some
|
- were committed (or pushed changes, if this is a bare remote),
|
||||||
|
- while the synced/master may have changes that some
|
||||||
- other remote synced to this remote. So, merge them both. -}
|
- other remote synced to this remote. So, merge them both. -}
|
||||||
mergeRemote :: Remote -> (Maybe Git.Ref) -> CommandCleanup
|
mergeRemote :: Remote -> (Maybe Git.Ref) -> CommandCleanup
|
||||||
mergeRemote remote b = case b of
|
mergeRemote remote b = case b of
|
||||||
|
@ -162,15 +164,29 @@ pushRemote remote branch = go =<< needpush
|
||||||
showOutput
|
showOutput
|
||||||
inRepo $ pushBranch remote branch
|
inRepo $ pushBranch remote branch
|
||||||
|
|
||||||
|
{- If the remote is a bare git repository, it's best to push the branch
|
||||||
|
- directly to it. On the other hand, if it's not bare, pushing to the
|
||||||
|
- checked out branch will fail, and this is why we use the syncBranch.
|
||||||
|
-
|
||||||
|
- Git offers no way to tell if a remote is bare or not, so both methods
|
||||||
|
- are tried.
|
||||||
|
-
|
||||||
|
- The direct push is likely to spew an ugly error message, so stderr is
|
||||||
|
- elided. Since progress is output to stderr too, the sync push is done
|
||||||
|
- first, and actually sends the data. Then the direct push is tried,
|
||||||
|
- with stderr discarded, to update the branch ref on the remote.
|
||||||
|
-}
|
||||||
pushBranch :: Remote -> Git.Ref -> Git.Repo -> IO Bool
|
pushBranch :: Remote -> Git.Ref -> Git.Repo -> IO Bool
|
||||||
pushBranch remote branch g =
|
pushBranch remote branch g = tryIO directpush `after` syncpush
|
||||||
Git.Command.runBool
|
where
|
||||||
|
syncpush = Git.Command.runBool (pushparams (refspec branch)) g
|
||||||
|
directpush = Git.Command.runQuiet (pushparams (show $ Git.Ref.base branch)) g
|
||||||
|
pushparams b =
|
||||||
[ Param "push"
|
[ Param "push"
|
||||||
, Param $ Remote.name remote
|
, Param $ Remote.name remote
|
||||||
, Param $ refspec Annex.Branch.name
|
, Param $ refspec Annex.Branch.name
|
||||||
, Param $ refspec branch
|
, Param b
|
||||||
] g
|
]
|
||||||
where
|
|
||||||
refspec b = concat
|
refspec b = concat
|
||||||
[ show $ Git.Ref.base b
|
[ show $ Git.Ref.base b
|
||||||
, ":"
|
, ":"
|
||||||
|
@ -247,8 +263,13 @@ resolveMerge' :: LsFiles.Unmerged -> Annex Bool
|
||||||
resolveMerge' u
|
resolveMerge' u
|
||||||
| issymlink LsFiles.valUs && issymlink LsFiles.valThem =
|
| issymlink LsFiles.valUs && issymlink LsFiles.valThem =
|
||||||
withKey LsFiles.valUs $ \keyUs ->
|
withKey LsFiles.valUs $ \keyUs ->
|
||||||
withKey LsFiles.valThem $ \keyThem -> do
|
withKey LsFiles.valThem $ \keyThem -> do
|
||||||
go keyUs keyThem
|
ifM isDirect
|
||||||
|
( maybe noop (\k -> removeDirect k file) keyUs
|
||||||
|
, liftIO $ nukeFile file
|
||||||
|
)
|
||||||
|
Annex.Queue.addCommand "rm" [Params "--quiet -f --"] [file]
|
||||||
|
go keyUs keyThem
|
||||||
| otherwise = return False
|
| otherwise = return False
|
||||||
where
|
where
|
||||||
go keyUs keyThem
|
go keyUs keyThem
|
||||||
|
@ -256,11 +277,6 @@ resolveMerge' u
|
||||||
makelink keyUs
|
makelink keyUs
|
||||||
return True
|
return True
|
||||||
| otherwise = do
|
| otherwise = do
|
||||||
ifM isDirect
|
|
||||||
( maybe noop (\k -> removeDirect k file) keyUs
|
|
||||||
, liftIO $ nukeFile file
|
|
||||||
)
|
|
||||||
Annex.Queue.addCommand "rm" [Params "--quiet -f --"] [file]
|
|
||||||
makelink keyUs
|
makelink keyUs
|
||||||
makelink keyThem
|
makelink keyThem
|
||||||
return True
|
return True
|
||||||
|
@ -270,8 +286,8 @@ resolveMerge' u
|
||||||
makelink (Just key) = do
|
makelink (Just key) = do
|
||||||
let dest = mergeFile file key
|
let dest = mergeFile file key
|
||||||
l <- inRepo $ gitAnnexLink dest key
|
l <- inRepo $ gitAnnexLink dest key
|
||||||
liftIO $ nukeFile dest
|
replaceFile dest $ makeAnnexLink l
|
||||||
addAnnexLink l dest
|
stageSymlink dest =<< hashSymlink l
|
||||||
whenM (isDirect) $
|
whenM (isDirect) $
|
||||||
toDirect key dest
|
toDirect key dest
|
||||||
makelink _ = noop
|
makelink _ = noop
|
||||||
|
@ -302,7 +318,7 @@ mergeFile file key
|
||||||
| otherwise = go $ shortHash $ key2file key
|
| otherwise = go $ shortHash $ key2file key
|
||||||
where
|
where
|
||||||
varmarker = ".variant-"
|
varmarker = ".variant-"
|
||||||
doubleconflict = varmarker `isSuffixOf` (dropExtension file)
|
doubleconflict = varmarker `isInfixOf` file
|
||||||
go v = takeDirectory file
|
go v = takeDirectory file
|
||||||
</> dropExtension (takeFileName file)
|
</> dropExtension (takeFileName file)
|
||||||
++ varmarker ++ v
|
++ varmarker ++ v
|
||||||
|
|
|
@ -24,7 +24,7 @@ def = [withOptions options $
|
||||||
"transfers a key from or to a remote"]
|
"transfers a key from or to a remote"]
|
||||||
|
|
||||||
options :: [Option]
|
options :: [Option]
|
||||||
options = fileOption : Command.Move.options
|
options = [fileOption, Command.Move.fromOption, Command.Move.toOption]
|
||||||
|
|
||||||
fileOption :: Option
|
fileOption :: Option
|
||||||
fileOption = Option.field [] "file" paramFile "the associated file"
|
fileOption = Option.field [] "file" paramFile "the associated file"
|
||||||
|
|
|
@ -15,7 +15,6 @@ import Annex.Content
|
||||||
import Logs.Location
|
import Logs.Location
|
||||||
import Logs.Transfer
|
import Logs.Transfer
|
||||||
import qualified Remote
|
import qualified Remote
|
||||||
import Types.Remote (AssociatedFile)
|
|
||||||
import Types.Key
|
import Types.Key
|
||||||
import qualified Option
|
import qualified Option
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue