Commit d8cf9beb authored by Yuanle Song's avatar Yuanle Song
Browse files

WIP ported mbackup python code to F# code

still missing many pieces and missing windows specific handling.
parent d179f801
Loading
Loading
Loading
Loading

.gitignore

0 → 100644
+3 −0
Original line number Diff line number Diff line
obj/
bin/
.ionide/

Program.fs

0 → 100644
+143 −0
Original line number Diff line number Diff line
// Learn more about F# at http://fsharp.org
//
//   - backup file list
//     /%appdata%/mbackup/mbackup-default.list
//     /%appdata%/mbackup/user-default.list
//     /%appdata%/mbackup/local.list (optional)
//   - exclude pattern
//     /%appdata%/mbackup/mbackup-default.exclude
//     /%appdata%/mbackup/local.exclude (optional)

open System
open System.Diagnostics

open Argu

let ExitBadParam = 1
let ExitTimeout = 2

type CLIArguments =
    | DryRun
    | Cron
    | Target of backupTarget: string
with
    interface IArgParserTemplate with
        member s.Usage =
            match s with
            | DryRun _ -> "only show what will be done, do not transfer any file"
            | Cron _ -> "run in cron mode, do not ask user any questions"
            | Target _ -> "rsync target, could be local dir or remote ssh dir"

type Logger() =
  let mutable level = Logger.DEBUG

  static member DEBUG = 10
  static member INFO = 20
  static member WARNING = 30
  static member ERROR = 40
  static member LevelToString level =
    match level with
      | 10 -> "DEBUG"
      | 20 -> "INFO"
      | 30 -> "WARNING"
      | 40 -> "ERROR"
      | _ -> failwith (sprintf "Unknown log level: %d" level)
  
  member this.Level = level

  member this.LogMaybe level fmt = 
    if this.Level <= level then
      Printf.ksprintf (
        fun s ->
            let time = DateTime.Now
            printfn "%02d:%02d:%02d %s %s" time.Hour time.Minute time.Second (Logger.LevelToString level) s)
        fmt
    else
      Printf.ksprintf (fun _ -> printfn "") fmt

  member this.SetLevel level =
    this.Level = level
  member this.Debug = this.LogMaybe Logger.DEBUG
  member this.Info = this.LogMaybe Logger.INFO
  member this.Warning = this.LogMaybe Logger.WARNING
  member this.Error = this.LogMaybe Logger.ERROR

let GetEnv (varName: string) = Environment.GetEnvironmentVariable varName

let GetEnvDefault (varName: string) (defaultValue: string) =
  let value = Environment.GetEnvironmentVariable varName
  match value with
    | null -> defaultValue
    | "" -> defaultValue
    | _ -> value

// Convert windows path to Mingw64 path
let ToMingwPath windowsPath =
  // TODO implement me
  windowsPath

let appDataRoamingDir = ToMingwPath (Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData))
let appDataLocalDir = ToMingwPath (Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData))

// TODO make sure dir ends with /
let userConfigDir = appDataRoamingDir + "/mbackup/"
let runtimeDir = appDataLocalDir

let isLocalTarget (target: string) = target.StartsWith "/"

[<EntryPoint>]
let main argv =
    let errorHandler = ProcessExiter(colorizer = function ErrorCode.HelpText -> None | _ -> Some ConsoleColor.Red)
    let parser = ArgumentParser.Create<CLIArguments>(programName = "mbackup.exe", errorHandler = errorHandler)
    let results = parser.Parse argv
    let cron = results.Contains Cron
    let dryRun = results.Contains DryRun

    let logger = Logger()

    logger.Info "userConfigDir=%s" userConfigDir
    logger.Info "runtimeDir=%s" runtimeDir

    let rsyncCmd: string list = []
    let rsyncCmd = if dryRun then List.append rsyncCmd ["--dry-run"] else rsyncCmd
    let rsyncCmd = List.append rsyncCmd ("-h --stats -air --delete --delete-excluded --ignore-missing-args".Split [|' '|] |> Array.toList)
    let mbackupFile = runtimeDir + "mbackup.list"
    let rsyncCmd = List.append rsyncCmd [sprintf "--files-from=%s" mbackupFile]
    let excludeFile = userConfigDir + "mbackup-default.exclude"
    let rsyncCmd = List.append rsyncCmd [sprintf "--exclude-from=%s" excludeFile]
    let localExcludeFile = userConfigDir + "local.list"
    let rsyncCmd = if IO.File.Exists localExcludeFile then List.append rsyncCmd [sprintf "--exclude-from=%s" localExcludeFile] else rsyncCmd
    let localLogFile = runtimeDir + "mbackup.log"
    let rsyncCmd = List.append rsyncCmd [sprintf "--log-file=%s" localLogFile]

    let backupTarget = results.GetResult (Target, defaultValue = Environment.GetEnvironmentVariable "TARGET")
    match backupTarget with
      | null ->
          logger.Error "TARGET is not defined"
          ExitBadParam
      | _ ->
          let backupTarget = ToMingwPath backupTarget
          let rsyncCmd =
            if not (isLocalTarget backupTarget)
              then
                let hostname: string = Net.Dns.GetHostName()
                let nodeName: string = GetEnvDefault "NODE_NAME" hostname
                let remoteLogFile = sprintf "/var/log/mbackup/%s.log" nodeName
                List.append rsyncCmd [sprintf "--remote-option=--log-file=%s" remoteLogFile]
              else
                rsyncCmd

          let rsyncCmd = List.append rsyncCmd ["/"]
          let rsyncCmd = List.append rsyncCmd [backupTarget]
          let rsyncArgs = rsyncCmd |> String.concat " "
          // TODO print rsyncArgs in the same Info call when Info supports multiple params.
          logger.Info "Note: if you run the following rsync command yourself, make sure the generated file list (%s) is up-to-date." mbackupFile
          let rsyncExe = "D:\\downloads\\apps\\rsync-w64-3.1.3-2-standalone\\usr\\bin\\rsync.exe"
          let echoExe = "C:\\Program Files\\Git\\usr\\bin\\echo.exe"
          let proc = Process.Start(echoExe, rsyncArgs)
          logger.Info "%s" (rsyncExe + rsyncArgs)
          if proc.WaitForExit Int32.MaxValue then
            proc.ExitCode
          else
            logger.Error "mbackup timed out while waiting for rsync to complete"
            ExitTimeout
+78 −0
Original line number Diff line number Diff line
*.exe
*.msi
*.zip
*.rar
*.7z
*.tar.gz
*.iso
*.img
*.vmdk
*.vdi
*.vhd
*.sav

.cabal-sandbox/
.stack-work/

bower_components/
node_modules/

*.py[co]
__pycache__
.tox/
.venv/
.venv-pypy/
.pytest_cache/
lib/python2.*/
lib/python3.*/
bin/python*
utils/virtualenv-*/
django/
local/
doc/_build/

*.class
build/
dist/
target/
#target/uberjar/
#target/classes/
.gradle/
_build/
_rel/
deps/
bin/
obj/

#testing-only/

*.elc
*.egg-info
*.[oa]
*.hi
TAGS
a.out
*.fasl
*~
*#
*.swp
tmp
temp
tmp.*
temp.*
t[0-9]
t[0-9].*
.coverage
.sconsign.dblite
CMakeFiles/
CTestTestfile.cmake
CMakeCache.txt
cmake_install.cmake
.java-project.el
__history/
nohup.out
octave-core
core
vgcore.*
.sass-cache/
image-dired/
+3 −0
Original line number Diff line number Diff line
# dirs to backup
/cygdrive/c/path/to/dir
/cygdrive/d/path/to/dir
+97 −0
Original line number Diff line number Diff line
# user dir prefix will be /cygdrive/%userprofile%/, usually /cygdrive/c/Users/<user>/

#############################################
# windows specific files
#############################################
Pictures/Saved Pictures/

#############################################
# below are file list from linux environment
#############################################

# shell
.profile
.bashrc
.bash.d/
.inputrc
.screenrc
.zshrc

# passwords, keys
.netrc
.ssh/
.gnupg/
.aws/
.azure/
.pgpass
.pypirc
.acme.sh/
.docker/config.json
.kube/config
.erlang.cookie
.lein/credentials.clj.gpg

# editor
.vimrc
.vim/
.emacs
.emacs.el
.emacs.d/
.gnus.el
.aspell.en.prepl
.aspell.en.pws

# mail
.muttrc
.forward
.fetchmailrc
.mailcap
.mime.types

# developer
.gitconfig
.gitignore
.hgrc
.pip/pip.conf
.pylintrc
.npmrc
.gemrc
.m2/settings.xml
.gradle/gradle.properties
.stack/stack.yaml
.stack/config.yaml
.cabal/config
.sbclrc
.roswell/init.lisp
.lein/profiles.clj

# Xorg and WM
.xinitrc
.Xresources
.xsessionrc
.xmodmaprc
.fonts.conf
.fonts
.i3/
.config/i3status/
.config/openbox/
.config/fbpanel/
.gtk-bookmarks
.gtkrc-2.0
.xbindkeysrc
.xbindkeysrc.scm
.xmobarrc
.xmonad/xmonad.hs
.cache/ibus/pinyin/user-1.0.db

# backup dirs
bak/
texts/
StudioProjects/

# misc, other
.config/dconf/user
.aptitude/config
.lftp/bookmarks
.wgetrc
.curlrc
Loading