Skip to content
Commits on Source (3)
...@@ -84,6 +84,8 @@ let toMingwPath (windowsPath: string) = ...@@ -84,6 +84,8 @@ let toMingwPath (windowsPath: string) =
result result
// Convert Mingw64 path to windows path. // Convert Mingw64 path to windows path.
// supported mingwPath format: /cygdrive/x/xxx
// return null if given path is not in this format.
let toWinPath (mingwPath: string) = let toWinPath (mingwPath: string) =
if mingwPath.StartsWith("/cygdrive/") then if mingwPath.StartsWith("/cygdrive/") then
let driveLetter = mingwPath.Substring("/cygdrive/".Length, 1).ToUpper() let driveLetter = mingwPath.Substring("/cygdrive/".Length, 1).ToUpper()
......
...@@ -38,6 +38,7 @@ open Argu ...@@ -38,6 +38,7 @@ open Argu
open Mbackup.Lib open Mbackup.Lib
open Mbackup.ConfigParser open Mbackup.ConfigParser
open Mbackup.TypedFilePath
let ExitSuccess = 0 let ExitSuccess = 0
let ExitBadParam = 1 let ExitBadParam = 1
...@@ -87,36 +88,25 @@ type MbackupRuntimeConfig = ...@@ -87,36 +88,25 @@ type MbackupRuntimeConfig =
Config: WellsConfig Config: WellsConfig
Options: ParseResults<CLIArguments> } Options: ParseResults<CLIArguments> }
let programFilesDirWin = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) |> ensureWinDir let mbackupDir = PortablePath("mbackup")
let programFilesDir = toMingwPath programFilesDirWin
let mbackupProgramDirWin = programFilesDirWin + "mbackup\\"
let mbackupProgramDir = toMingwPath mbackupProgramDirWin
let appDataRoamingDir = let programFilesDir = WinPath (Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles))
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) let mbackupProgramDir = joinPath programFilesDir mbackupDir
|> toMingwPath
|> ensureDir
let programDataDirWin = getEnv "PROGRAMDATA" |> ensureWinDir let appDataRoamingDir = WinPath (Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData))
let programDataDir = toMingwPath programDataDirWin
let appDataLocalDirWin = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) |> ensureWinDir
let appDataLocalDir = appDataLocalDirWin |> toMingwPath
let mbackupInstallDirWin = let programDataDir = WinPath (getEnv "PROGRAMDATA")
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) let appDataLocalDir = WinPath (Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData))
|> ensureDir
|> fun s -> s + "mbackup"
let mbackupInstallDir = mbackupInstallDirWin |> toMingwPath let mbackupInstallDir =
joinPath (WinPath(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles))) mbackupDir
let userHomeWin = let userHome =
getEnvDefault "HOME" (Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)) |> ensureWinDir WinPath(getEnvDefault "HOME" (Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)))
let userHome = userHomeWin |> toMingwPath let userHomeMingw = toMingw userHome
let userConfigDirWin = programDataDirWin + "mbackup\\" let userConfigDir = joinPath programDataDir mbackupDir
let userConfigDir = programDataDir + "mbackup/" let runtimeDir = joinPath appDataLocalDir mbackupDir
let runtimeDirWin = appDataLocalDirWin + "mbackup\\"
let runtimeDir = appDataLocalDir + "mbackup/"
// return true if target is a local dir. local dir can be unix style or windows style. // return true if target is a local dir. local dir can be unix style or windows style.
let isLocalTarget (target: string) = target.StartsWith "/" || Regex.IsMatch(target, "^[a-z]:", RegexOptions.IgnoreCase) let isLocalTarget (target: string) = target.StartsWith "/" || Regex.IsMatch(target, "^[a-z]:", RegexOptions.IgnoreCase)
...@@ -129,17 +119,11 @@ let isLocalTarget (target: string) = target.StartsWith "/" || Regex.IsMatch(targ ...@@ -129,17 +119,11 @@ let isLocalTarget (target: string) = target.StartsWith "/" || Regex.IsMatch(targ
let expandUserFile (fn: string) = let expandUserFile (fn: string) =
let fn = let fn =
let documentsDir = let documentsDir =
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) toMingwPath(WinPath(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)))
|> toMingwPath
|> ensureDir
let picturesDir = let picturesDir =
Environment.GetFolderPath(Environment.SpecialFolder.MyPictures) toMingwPath(WinPath(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures)))
|> toMingwPath
|> ensureDir
let desktopDir = let desktopDir =
Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) toMingwPath(WinPath(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory)))
|> toMingwPath
|> ensureDir
let fn = Regex.Replace(fn, "^My Documents/", documentsDir, RegexOptions.IgnoreCase) let fn = Regex.Replace(fn, "^My Documents/", documentsDir, RegexOptions.IgnoreCase)
let fn = Regex.Replace(fn, "^Documents/", documentsDir, RegexOptions.IgnoreCase) let fn = Regex.Replace(fn, "^Documents/", documentsDir, RegexOptions.IgnoreCase)
...@@ -152,34 +136,35 @@ let expandUserFile (fn: string) = ...@@ -152,34 +136,35 @@ let expandUserFile (fn: string) =
let fn = Regex.Replace(fn, "^桌面/", desktopDir) let fn = Regex.Replace(fn, "^桌面/", desktopDir)
fn fn
if fn.StartsWith("/") then fn if fn.StartsWith("/") then fn
else userHome + fn else
toMingwPath(joinPath userHomeMingw (MingwPath(fn)))
// read mbackup list file // read mbackup list file
let readMbackupListFile fn = let readMbackupListFile (fn: TypedFilePath) =
let dropEmptyLinesAndComments lines = let dropEmptyLinesAndComments lines =
Seq.filter (fun (line: string) -> not (line.TrimStart().StartsWith("#") || line.TrimEnd().Equals(""))) lines Seq.filter (fun (line: string) -> not (line.TrimStart().StartsWith("#") || line.TrimEnd().Equals(""))) lines
File.ReadAllLines(fn) |> dropEmptyLinesAndComments File.ReadAllLines(toWinPath fn) |> dropEmptyLinesAndComments
// generate MbackupFileName.GeneratedList file // generate MbackupFileName.GeneratedList file
let generateMbackupList (logger: Logger) = let generateMbackupList (logger: Logger) =
// TODO how to only regenerate if source file have changed? should I bundle GNU make with mbackup? // TODO how to only regenerate if source file have changed? should I bundle GNU make with mbackup?
// just compare MbackupFileName.GeneratedList mtime with its source files? // just compare MbackupFileName.GeneratedList mtime with its source files?
let mbackupDefaultList = userConfigDirWin + MbackupFileName.DefaultList let mbackupDefaultList = joinPortablePath userConfigDir MbackupFileName.DefaultList
let mbackupLocalList = userConfigDirWin + MbackupFileName.LocalList let mbackupLocalList = joinPortablePath userConfigDir MbackupFileName.LocalList
let mbackupUserDefaultList = userConfigDirWin + MbackupFileName.UserDefaultList let mbackupUserDefaultList = joinPortablePath userConfigDir MbackupFileName.UserDefaultList
let mbackupList = runtimeDirWin + MbackupFileName.GeneratedList let mbackupList = joinPortablePath runtimeDir MbackupFileName.GeneratedList
try try
let defaultListLines = readMbackupListFile mbackupDefaultList |> Seq.map toMingwPath let defaultListLines = readMbackupListFile mbackupDefaultList |> Seq.map Lib.toMingwPath
let localListLinesMaybe = let localListLinesMaybe =
try try
let lines = readMbackupListFile mbackupLocalList |> Seq.map toMingwPath let lines = readMbackupListFile mbackupLocalList |> Seq.map Lib.toMingwPath
(true, lines) (true, lines)
with with
| :? FileNotFoundException -> (true, Seq.empty) | :? FileNotFoundException -> (true, Seq.empty)
| ex -> | ex ->
logger.Error "Read mbackupLocalList %s failed: %s" mbackupLocalList ex.Message logger.Error "Read mbackupLocalList %s failed: %s" (toWinPath mbackupLocalList) ex.Message
(false, Seq.empty) (false, Seq.empty)
match localListLinesMaybe with match localListLinesMaybe with
| (false, _) -> failwith "Read mbackupLocalList failed" | (false, _) -> failwith "Read mbackupLocalList failed"
...@@ -189,9 +174,9 @@ let generateMbackupList (logger: Logger) = ...@@ -189,9 +174,9 @@ let generateMbackupList (logger: Logger) =
// For DefaultList and LocalList, exclude empty lines and comment lines. // For DefaultList and LocalList, exclude empty lines and comment lines.
// TODO skip and give a warning on non-absolute path. // TODO skip and give a warning on non-absolute path.
// For UserDefaultList, auto prefix user's home dir, auto expand Documents, Downloads etc special folder. // For UserDefaultList, auto prefix user's home dir, auto expand Documents, Downloads etc special folder.
Directory.CreateDirectory(runtimeDirWin) |> ignore Directory.CreateDirectory(toWinPath runtimeDir) |> ignore
File.WriteAllLines(mbackupList, allLines) File.WriteAllLines(toWinPath mbackupList, allLines)
logger.Info "GeneratedList written: %s" mbackupList logger.Info "GeneratedList written: %s" (toWinPath mbackupList)
true true
with with
| :? IOException as ex -> | :? IOException as ex ->
...@@ -205,22 +190,22 @@ exception PrivateKeyNotFoundException of string ...@@ -205,22 +190,22 @@ exception PrivateKeyNotFoundException of string
let addOptionsForRemoteBackup (rc: MbackupRuntimeConfig) (rsyncCmd: string list) = let addOptionsForRemoteBackup (rc: MbackupRuntimeConfig) (rsyncCmd: string list) =
let options = rc.Options let options = rc.Options
let sshExeFile = mbackupProgramDir + "rsync-w64/usr/bin/ssh.exe" let sshExeFile = joinPath (toMingw mbackupProgramDir) (MingwPath "rsync-w64/usr/bin/ssh.exe")
let sshConfigFile = userConfigDir + ".ssh/config" let sshConfigFile = joinPath (toMingw userConfigDir) (MingwPath ".ssh/config")
let sshKnownHostsFile = userConfigDir + ".ssh/known_hosts" let sshKnownHostsFile = joinPath (toMingw userConfigDir) (MingwPath ".ssh/known_hosts")
let sshPrivateKeyFile = options.GetResult(Ssh_Key, rc.Config.GetStrDefault "ssh-key" (userConfigDir + ".ssh/id_rsa")) |> toMingwPath let sshPrivateKeyFileDefault = joinPath (toMingw userConfigDir) (MingwPath ".ssh/id_rsa")
let sshPrivateKeyFileWin = toWinPath sshPrivateKeyFile let sshPrivateKeyFile = MingwPath (options.GetResult(Ssh_Key, rc.Config.GetStrDefault "ssh-key" (toString sshPrivateKeyFileDefault)) |> Lib.toMingwPath)
if not (File.Exists(sshPrivateKeyFileWin)) then if not (File.Exists(toWinPath sshPrivateKeyFile)) then
raise (PrivateKeyNotFoundException("ssh private key doesn't exist: " + sshPrivateKeyFileWin)) raise (PrivateKeyNotFoundException("ssh private key doesn't exist: " + toWinPath sshPrivateKeyFile))
else else
let sshConfigFileOption = let sshConfigFileOption =
if File.Exists(toWinPath sshConfigFile) then " -F " + sshConfigFile if File.Exists(toWinPath sshConfigFile) then " -F " + toMingwPath sshConfigFile
else "" else ""
let rsyncCmd = let rsyncCmd =
List.append rsyncCmd List.append rsyncCmd
[ sprintf "-e \"'%s'%s -i %s -o StrictHostKeyChecking=ask -o UserKnownHostsFile=%s\"" [ sprintf "-e \"'%s'%s -i %s -o StrictHostKeyChecking=ask -o UserKnownHostsFile=%s\""
sshExeFile sshConfigFileOption sshPrivateKeyFile sshKnownHostsFile] (toMingwPath sshExeFile) sshConfigFileOption (toMingwPath sshPrivateKeyFile) (toMingwPath sshKnownHostsFile)]
let nodeName = options.GetResult(Node_Name, (rc.Config.GetStrDefault "node-name" (Net.Dns.GetHostName()))) let nodeName = options.GetResult(Node_Name, (rc.Config.GetStrDefault "node-name" (Net.Dns.GetHostName())))
let remoteLogFile = sprintf "/var/log/mbackup/%s.log" nodeName let remoteLogFile = sprintf "/var/log/mbackup/%s.log" nodeName
...@@ -238,8 +223,8 @@ let main argv = ...@@ -238,8 +223,8 @@ let main argv =
parser.Parse argv parser.Parse argv
let rc = { let rc = {
MbackupRuntimeConfig.Config = MbackupRuntimeConfig.Config =
let mbackupConfigFile = userConfigDirWin + MbackupFileName.Config let mbackupConfigFile = joinPortablePath userConfigDir MbackupFileName.Config
WellsConfig(mbackupConfigFile) WellsConfig(toWinPath mbackupConfigFile)
Logger = logger Logger = logger
Options = options Options = options
} }
...@@ -248,9 +233,9 @@ let main argv = ...@@ -248,9 +233,9 @@ let main argv =
printfn "mbackup %s" versionStr printfn "mbackup %s" versionStr
Environment.Exit(ExitSuccess) Environment.Exit(ExitSuccess)
logger.Info "user config dir: %s" userConfigDirWin logger.Info "user config dir: %s" (toWinPath userConfigDir)
logger.Info "runtime dir: %s" runtimeDirWin logger.Info "runtime dir: %s" (toWinPath runtimeDir)
logger.Debug "program dir: %s" mbackupProgramDirWin logger.Debug "program dir: %s" (toWinPath mbackupProgramDir)
let rsyncCmd: string list = [] let rsyncCmd: string list = []
let rsyncCmd = appendWhen (options.Contains Dry_Run) rsyncCmd "--dry-run" let rsyncCmd = appendWhen (options.Contains Dry_Run) rsyncCmd "--dry-run"
...@@ -261,31 +246,31 @@ let main argv = ...@@ -261,31 +246,31 @@ let main argv =
if not (generateMbackupList logger) then if not (generateMbackupList logger) then
failwith (sprintf "Generate %s failed" MbackupFileName.GeneratedList) failwith (sprintf "Generate %s failed" MbackupFileName.GeneratedList)
let generatedFileList = runtimeDir + MbackupFileName.GeneratedList let generatedFileList = joinPortablePath runtimeDir MbackupFileName.GeneratedList
let rsyncCmd = List.append rsyncCmd [ sprintf "--files-from=%s" generatedFileList ] let rsyncCmd = List.append rsyncCmd [ sprintf "--files-from=%s" (toMingwPath generatedFileList) ]
let rsyncCmd = List.append rsyncCmd [ sprintf "--exclude-from=%s" (userConfigDir + MbackupFileName.DefaultExclude) ] let rsyncCmd = List.append rsyncCmd [ sprintf "--exclude-from=%s" (toMingwPath (joinPortablePath userConfigDir MbackupFileName.DefaultExclude)) ]
let runtimeLocalExcludeFile = runtimeDir + MbackupFileName.LocalExclude
let rsyncCmd = let rsyncCmd =
let localExcludeFile = userConfigDir + MbackupFileName.LocalExclude let runtimeLocalExcludeFile = joinPortablePath runtimeDir MbackupFileName.LocalExclude
if File.Exists localExcludeFile then let localExcludeFile = joinPortablePath userConfigDir MbackupFileName.LocalExclude
if File.Exists (toWinPath localExcludeFile) then
let convertAbsPathToMingwStyle (line: string) = let convertAbsPathToMingwStyle (line: string) =
if Regex.IsMatch(line, "[a-z]:", RegexOptions.IgnoreCase) then if Regex.IsMatch(line, "[a-z]:", RegexOptions.IgnoreCase) then
toMingwPath line Lib.toMingwPath line
else else
line line
let lines = let lines =
readMbackupListFile localExcludeFile readMbackupListFile localExcludeFile
|> Seq.map convertAbsPathToMingwStyle |> Seq.map convertAbsPathToMingwStyle
File.WriteAllLines(runtimeLocalExcludeFile, lines) File.WriteAllLines(toWinPath runtimeLocalExcludeFile, lines)
appendWhen (File.Exists localExcludeFile) rsyncCmd (sprintf "--exclude-from=%s" runtimeLocalExcludeFile) appendWhen (File.Exists (toWinPath localExcludeFile)) rsyncCmd (sprintf "--exclude-from=%s" (toMingwPath runtimeLocalExcludeFile))
let rsyncCmd = List.append rsyncCmd [ sprintf "--log-file=%s" (runtimeDir + MbackupFileName.Log) ] let rsyncCmd = List.append rsyncCmd [ sprintf "--log-file=%s" (toMingwPath (joinPortablePath runtimeDir MbackupFileName.Log)) ]
// precedence: command line argument > environment variable > config file // precedence: command line argument > environment variable > config file
let normalizeTarget target = let normalizeTarget target =
if isLocalTarget target then toMingwPath target if isLocalTarget target then Lib.toMingwPath target
else target else target
let backupTargetMaybe = let backupTargetMaybe =
...@@ -309,20 +294,20 @@ let main argv = ...@@ -309,20 +294,20 @@ let main argv =
let rsyncCmd = List.append rsyncCmd [ "/" ] let rsyncCmd = List.append rsyncCmd [ "/" ]
let rsyncCmd = List.append rsyncCmd [ backupTarget ] let rsyncCmd = List.append rsyncCmd [ backupTarget ]
let rsyncArgs = rsyncCmd |> String.concat " " let rsyncArgs = rsyncCmd |> String.concat " "
let rsyncExe = mbackupProgramDirWin + "rsync-w64\\usr\\bin\\rsync.exe" let rsyncExe = joinPath mbackupProgramDir (WinPath "rsync-w64\\usr\\bin\\rsync.exe")
Directory.CreateDirectory(runtimeDirWin) |> ignore Directory.CreateDirectory(toWinPath runtimeDir) |> ignore
Directory.CreateDirectory(userConfigDirWin) |> ignore Directory.CreateDirectory(toWinPath userConfigDir) |> ignore
logger.Info logger.Info
"Note: if you run the following rsync command yourself, make sure the generated file list (%s) is up-to-date.\n%s" "Note: if you run the following rsync command yourself, make sure the generated file list (%s) is up-to-date.\n%s"
generatedFileList (rsyncExe + " " + rsyncArgs) (toWinPath generatedFileList) (toWinPath rsyncExe + " " + rsyncArgs)
let processStartInfo = let processStartInfo =
ProcessStartInfo( ProcessStartInfo(
FileName = rsyncExe, FileName = toWinPath rsyncExe,
Arguments = rsyncArgs) Arguments = rsyncArgs)
//set HOME dir to prevent ssh.exe can't access /home/<user>/.ssh error. //set HOME dir to prevent ssh.exe can't access /home/<user>/.ssh error.
try try
processStartInfo.EnvironmentVariables.Add("HOME", userHomeWin) processStartInfo.EnvironmentVariables.Add("HOME", toWinPath userHome)
setEnv "HOME" userHomeWin setEnv "HOME" (toWinPath userHome)
with with
| :? ArgumentException -> () // variable already exists | :? ArgumentException -> () // variable already exists
| ex -> logger.Warning "set HOME environment variable failed: %A" ex | ex -> logger.Warning "set HOME environment variable failed: %A" ex
......
...@@ -61,7 +61,8 @@ Here are the backup list files and exclude pattern files: ...@@ -61,7 +61,8 @@ Here are the backup list files and exclude pattern files:
local-list.txt and local-exclude.txt are managed by user. mbackup will not local-list.txt and local-exclude.txt are managed by user. mbackup will not
modify those files when it is uninstalled or upgraded. The other lists are modify those files when it is uninstalled or upgraded. The other lists are
shipped with mbackup and will be removed/overwritten when shipped with mbackup and will be removed/overwritten when
uninstalling/upgrading mbackup. uninstalling/upgrading mbackup. All text files should be in utf-8 encoding,
especially if they include unicode file path.
To learn more about file list and exclude patterns, read rsync man page To learn more about file list and exclude patterns, read rsync man page
https://download.samba.org/pub/rsync/rsync.html --files-from option, https://download.samba.org/pub/rsync/rsync.html --files-from option,
......
module Mbackup.TypedFilePath
open System
open System.IO
// use Discriminated Unions to represent windows path and mingw style linux path.
type TypedFilePath =
| WinPath of path: string
| MingwPath of path: string
| PortablePath of path: string
let joinPath (part1: TypedFilePath) (part2: TypedFilePath) =
match part1 with
| WinPath(p1) ->
match part2 with
| WinPath(p2) | PortablePath(p2) ->
WinPath(Lib.ensureWinDir(p1) + p2)
| _ ->
failwith "Coding error. trying to join different Path type"
| MingwPath(p1) ->
match part2 with
| MingwPath(p2) | PortablePath(p2) ->
MingwPath(Lib.ensureDir(p1) + p2)
| _ ->
failwith "Coding error. trying to join different Path type"
| _ ->
failwith "Coding error. joinPath first path should not be PortablePath"
let joinPortablePath (part1: TypedFilePath) (part2: string) =
joinPath part1 (PortablePath part2)
let toString (tpath: TypedFilePath) =
match tpath with
| WinPath(path) | MingwPath(path) | PortablePath(path) -> path
let toWinPath (tpath: TypedFilePath): string =
match tpath with
| WinPath(path) | PortablePath(path) -> path
| MingwPath(path) -> Lib.toWinPath(path)
let toWin (tpath: TypedFilePath): TypedFilePath =
match tpath with
| MingwPath(path) -> WinPath(Lib.toWinPath(path))
| x -> x
let toMingwPath (tpath: TypedFilePath): string =
match tpath with
| WinPath(path) | PortablePath(path) -> Lib.toMingwPath(path)
| MingwPath(path) -> path
let toMingw (tpath: TypedFilePath): TypedFilePath =
match tpath with
| WinPath(path) -> MingwPath(Lib.toMingwPath(path))
| x -> x
let ensureDir (tpath: TypedFilePath) =
match tpath with
| WinPath(path) -> WinPath(if path.EndsWith "\\" then path else path + "\\")
| MingwPath(path) -> MingwPath(if path.EndsWith "/" then path else path + "/")
| PortablePath(path) -> failwith "Coding error. ensureDir should not be called on PortablePath"
# files/dirs to exclude, for syntax, check rsync patterns # files/dirs to exclude, for syntax, check rsync patterns
# This file is reserved when uninstall/upgrade mbackup. # This file is reserved when uninstall/upgrade mbackup.
# lines started with # are comments. # lines started with # are comments.
# unicode characters in path is supported such as D:\Movies\疯狂的赛车\***
# example: # example:
# *.o # *.o
# C:\foo\bar.iso # C:\foo\bar.iso
# local dirs to backup. # local dirs to backup.
# This file is reserved when uninstall/upgrade mbackup. # This file is reserved when uninstall/upgrade mbackup.
# lines started with # are comments. # lines started with # are comments.
# unicode characters in path is supported such as D:\Movies\疯狂的赛车
# example: # example:
# C:\mydir # C:\mydir
# D:\some dir\some file.doc # D:\some dir\some file.doc
...@@ -23,12 +23,13 @@ ...@@ -23,12 +23,13 @@
<RuntimeIdentifiers>win10-x64</RuntimeIdentifiers> <RuntimeIdentifiers>win10-x64</RuntimeIdentifiers>
<RootNamespace>Mbackup</RootNamespace> <RootNamespace>Mbackup</RootNamespace>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>0.6.1.0</Version> <Version>0.6.3.0</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Lib.fs" /> <Compile Include="Lib.fs" />
<Compile Include="ConfigParser.fs" /> <Compile Include="ConfigParser.fs" />
<Compile Include="TypedFilePath.fs" />
<Compile Include="Program.fs" /> <Compile Include="Program.fs" />
</ItemGroup> </ItemGroup>
......
* COMMENT -*- mode: org -*- * COMMENT -*- mode: org -*-
#+Date: 2019-11-12 #+Date: 2019-11-12
Time-stamp: <2019-11-18> Time-stamp: <2019-11-30>
#+STARTUP: content #+STARTUP: content
* notes :entry: * notes :entry:
** 2019-11-12 mbackup for windows :featurereq: ** 2019-11-12 mbackup for windows :featurereq:
...@@ -245,6 +245,18 @@ vscode should at least always indent using space for F#. ...@@ -245,6 +245,18 @@ vscode should at least always indent using space for F#.
* current :entry: * current :entry:
** **
** 2019-11-30 support itemize-changes option in config file.
itemize-changes=yes
itemize-changes=no
-i --itemize-changes
** 2019-11-30 support default-docs=no option.
both in command line arguments and in config file.
--default-docs
--no-default-docs
** 2019-11-24 Make sure Chinese file name works in local.list file. ** 2019-11-24 Make sure Chinese file name works in local.list file.
** 2019-11-23 when default .ssh dir is in %programdata%\mbackup\.ssh, ** 2019-11-23 when default .ssh dir is in %programdata%\mbackup\.ssh,
there is a security concern. there is a security concern.
...@@ -284,6 +296,25 @@ Are there any code change required? ...@@ -284,6 +296,25 @@ Are there any code change required?
** 2019-11-18 add log rotate for %localappdata%\mbackup\mbackup.log ** 2019-11-18 add log rotate for %localappdata%\mbackup\mbackup.log
* done :entry: * done :entry:
** 2019-11-30 bug: mbackup local exclude config is not used in rsync command.
L271 should use win path when check whether file exists.
- can I use typing to specify when a Linux path is required and when a windows
path is required?
Introduction to functional data types in F#
https://markvincze.github.io/fsharp-datatypes-intro/#/13
Discriminated Unions - F# | Microsoft Docs
https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/discriminated-unions
- fixed in v0.6.3.0
** 2019-11-30 shipped local list, local exclude file should be in utf-8 encoding.
when user open the file using notepad, it should be in utf-8 encoding.
just add some unicode character in shipped local list file and save in utf-8 encoding
in windows.
** 2019-11-19 allow local Users group to have full access to local list and local exclude file. ** 2019-11-19 allow local Users group to have full access to local list and local exclude file.
by default, only admin can modify them, Users can only read/execute them. by default, only admin can modify them, Users can only read/execute them.
......