sync-mht (empty) → 0.2.0.0
raw patch · 4 files changed
+255/−0 lines, 4 filesdep +arraydep +basedep +base16-bytestringsetup-changed
Dependencies added: array, base, base16-bytestring, byteable, bytestring, cereal, containers, cryptohash, directory, filepath, io-streams, mtl, process, text, transformers, unix
Files
- LICENSE +22/−0
- Setup.hs +2/−0
- src/main/hs/Main.hs +164/−0
- sync-mht.cabal +67/−0
+ LICENSE view
@@ -0,0 +1,22 @@+The MIT License (MIT)++Copyright (c) 2015 Emin Karayel++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.+
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ src/main/hs/Main.hs view
@@ -0,0 +1,164 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-}+import System.Environment+import System.Process+import System.IO+import System.IO.Error+import System.Console.GetOpt+import Paths_sync_mht (version)+import Data.List+import Data.Version (showVersion)++import Sync.MerkleTree.CommTypes+import Sync.MerkleTree.Sync++data SyncOptions+ = SyncOptions+ { so_source :: Maybe FilePath+ , so_destination :: Maybe FilePath+ , so_remote :: Maybe String+ , so_ignore :: [FilePath]+ , so_add :: Bool+ , so_update :: Bool+ , so_delete :: Bool+ , so_help :: Bool+ , so_nonOptions :: [String]+ }++defaultSyncOptions :: SyncOptions+defaultSyncOptions =+ SyncOptions+ { so_source = Nothing+ , so_destination = Nothing+ , so_remote = Nothing+ , so_ignore = []+ , so_add = False+ , so_update = False+ , so_delete = False+ , so_help = False+ , so_nonOptions = []+ }+++toClientServerOptions :: SyncOptions -> ClientServerOptions+toClientServerOptions so =+ ClientServerOptions+ { cs_add = so_add so+ , cs_update = so_update so+ , cs_delete = so_delete so+ , cs_ignore = so_ignore so+ }++optDescriptions :: [OptDescr (SyncOptions -> SyncOptions)]+optDescriptions =+ [ Option ['s'] ["source"] (ReqArg (\fp so -> so { so_source = Just fp }) "DIR")+ "directory to copy files from (source) (this option is required)"+ , Option ['d'] ["destination"] (ReqArg (\fp so -> so { so_destination = Just fp }) "DIR")+ "directory to copy files to (destination) (this option is required)"+ , Option ['r'] ["remote-shell"] (ReqArg (\s so -> so { so_remote = Just s }) "CMD")+ "synchroize with a remote-site using a remote command execution tool (like ssh or docker)"+ , Option ['i'] ["ignore"] (ReqArg (\fp so -> so { so_ignore = fp:(so_ignore so) }) "PATH") ( concat+ [ "files or directories (relative to the source and destination directories) that are to "+ , "be ignored during synchroization (this option can be given multiple times)"+ ])+ , Option ['a'] ["add"] (NoArg (\so -> so { so_add = True })) ( concat+ [ "copy files from the source directory if there is corresponding file inside "+ , "the destination directory"+ ])+ , Option ['u'] ["update"] (NoArg (\so -> so { so_update = True })) ( concat+ [ "overwrite existing files inside the destination directory if their content does not "+ , "match the respective files inside the source directory"+ ])+ , Option [] ["delete"] (NoArg (\so -> so { so_delete = True })) ( concat+ [ "delete files inside the destination directory if there is no corresponding file inside "+ , "the source directory"+ ])+ , Option ['h'] ["help"] (NoArg (\so -> so { so_help = True })) "shows usage information"+ ]++parseNonOption :: String -> (SyncOptions -> SyncOptions)+parseNonOption s so = so { so_nonOptions = s:(so_nonOptions so) }++toSyncOptions :: [(SyncOptions -> SyncOptions)] -> SyncOptions+toSyncOptions = foldl (flip id) defaultSyncOptions++putError :: String -> IO ()+putError = hPutStrLn stderr++printUsageInfo :: [String] -> IO ()+printUsageInfo prefix = mapM_ putError (prefix ++ [usageInfo header optDescriptions] ++ [details])+ where+ header = unlines+ [ "sync-mht version " ++ showVersion version+ , ""+ , "Usage: sync-mht [OPTIONS..]"+ ]+ details = concat+ [ "Note: If the --remote-shell option has been provided, exactly one of the directories "+ , "must be prepended with 'remote:' - indicating a folder on the site, accessible with "+ , "the provided remote shell command."+ ]++_HIDDENT_CLIENT_MODE_OPTION_ :: String+_HIDDENT_CLIENT_MODE_OPTION_ = "--hidden-client-mode-option"++main :: IO ()+main = flip catchIOError (putError . show) $+ do args <- getArgs+ let parsedOpts = getOpt (ReturnInOrder parseNonOption) optDescriptions args+ case () of+ () | [_HIDDENT_CLIENT_MODE_OPTION_] == args -> child+ | (options,[],[]) <- parsedOpts -> run $ toSyncOptions options+ | (_,_,errs) <- parsedOpts -> printUsageInfo errs++data Side+ = Remote FilePath+ | Local FilePath++parseFilePath :: FilePath -> Side+parseFilePath fp+ | Just rest <- stripPrefix "remote:" fp = Remote rest+ | otherwise = Local fp++run :: SyncOptions -> IO ()+run so+ | so_help so =+ printUsageInfo []+ | not (null (so_nonOptions so)) =+ printUsageInfo ["Could not understand the following options: " ++ show (so_nonOptions so)]+ | Just source <- so_source so, Just destination <- so_destination so =+ case (parseFilePath source, parseFilePath destination) of+ (Remote _, Remote _) -> printUsageInfo [doubleRemote]+ (Local source', Local destination')+ | Just _ <- so_remote so -> printUsageInfo [missingRemote]+ | otherwise -> local cs source' destination'+ (Remote source', Local destination')+ | Just remoteCmd <- so_remote so -> runParent cs remoteCmd source' destination' FromRemote+ | otherwise -> printUsageInfo [missingRemoteCmd]+ (Local source', Remote destination')+ | Just remoteCmd <- so_remote so -> runParent cs remoteCmd source' destination' ToRemote+ | otherwise -> printUsageInfo [missingRemoteCmd]+ | otherwise =+ do let missingOpts =+ intercalate ", " $ map snd $ filter ((== Nothing) . ($ so) . fst)+ [(so_source, "--source"),(so_destination, "--destination")]+ printUsageInfo ["The follwing required options: " ++ missingOpts ++ " were missing."]+ where+ cs = toClientServerOptions so+ doubleRemote = "Either the directory given in --source or --destination must be local."+ missingRemote = concat+ [ "The --remote-shell options requires that either the directory given at "+ , "--source or --destination is at remote site. (Indicated by the prefix: 'remote:')"+ ]+ missingRemoteCmd = "The --remote-shell is required when the prefix 'remote:' is used."++runParent :: ClientServerOptions -> String -> FilePath -> FilePath -> Direction -> IO ()+runParent clientServerOpts remoteCmd source destination dir =+ do let remoteCmd' = remoteCmd ++ " " ++ _HIDDENT_CLIENT_MODE_OPTION_+ handles <- createProcess ((shell remoteCmd') { std_in = CreatePipe, std_out = CreatePipe })+ case handles of+ (Just hIn, Just hOut, Nothing, _ph) ->+ do streams <- openStreams hOut hIn+ parent streams source destination dir clientServerOpts+ _ -> fail "createProcess did return the correct set of handles."+
+ sync-mht.cabal view
@@ -0,0 +1,67 @@+-- This .cabal file was generated by src/build/hs/CabalTemplate.hs+name: sync-mht+version: 0.2.0.0+synopsis: + Fast incremental file transfer using Merke-Hash-Trees+ +description: + A command line tool that can be used to incrementally synchronize a directory hierarchy with a+ second one. It is using a Merkle-Hash-Tree to compare the folders, such that the synchronization+ time and communication (round) complexity grows only logarithmically with the actual size of the+ directories (assuming the actual difference of the directories is small).+ + The communication happens through standard streams between parent and child processes, which can+ easily be routed through remote command execution tools, e.g.+ + sync-mht -s foo/ -d bar+ + will synchronize the local folder bar/ with the local folder foo/, but+ + sync-mht -s foo/ -d remote:/bar -r "ssh fred@example.org sync-mht"+ + will synchronize the folder bar/ in the home directory of the user fred on the host machine+ example.org with the local folder foo/.+ + It is also possible to use it with docker, e.g.+ + sync-mht -s foo/ -d remote:/bar -r "docker run -i --volumes-from bar ekarayel/sync-mht sync-mht"+ + to synchronize the folder /bar (of the container named bar) with the local folder foo/.+ +license: MIT+license-file: LICENSE+author: Emin Karayel <me@eminkarayel.de>+maintainer: Emin Karayel <me@eminkarayel.de>+package-url: https://github.com/ekarayel/sync-mht+category: Utility+build-type: Simple+cabal-version: >=1.10+source-repository head+ type: git+ location: https://github.com/ekarayel/sync-mht+source-repository this+ type: git+ tag: 0.2.0.0+ location: https://github.com/ekarayel/sync-mht+executable sync-mht+ main-is: Main.hs+ ghc-options: -Wall+ build-depends: + base >=4.7 && <4.8+ , unix >=2.7 && <2.8+ , directory >=1.2 && <1.3+ , filepath >=1.3 && <1.4+ , process >=1.2 && <1.3+ , cryptohash >=0.11 && <0.12+ , byteable >=0.1 && <0.2+ , array >=0.5 && <0.6+ , containers >=0.5 && <0.6+ , text >=1.2 && <1.3+ , bytestring >=0.10 && <0.11+ , base16-bytestring >=0.1 && <0.2+ , cereal >= 0.4 && < 0.5+ , io-streams >= 1.2 && <1.3+ , transformers >= 0.4 && < 0.5+ , mtl >= 2.2 && < 2.3+ hs-source-dirs: src/main/hs+ default-language: Haskell2010