Skip to content
Commits on Source (3)
module Mbackup.ConfigParser
open System
open System.IO
open System.Text.RegularExpressions
open Mbackup.Lib
type WellsConfig(fn: string) =
let mutable keyValuePairs: Map<string, string> = Map.empty
do
let dropEmptyLinesAndComments lines = Seq.filter (fun (line: string) -> not (line.TrimStart().StartsWith("#") || line.TrimEnd().Equals(""))) lines
let dropQuotesMaybe (value: string) = if value.StartsWith("\"") || value.StartsWith("'") then value.Substring(1, value.Length - 2) else value
let toKeyValue = Seq.map (fun (line: string) ->
let result: string[] = line.Split('=', 2)
(result.[0].Trim(), dropQuotesMaybe (result.[1].Trim())))
let result = File.ReadAllLines(fn) // file IO can throw Exception
|> dropEmptyLinesAndComments
|> toKeyValue
|> Seq.fold (fun (m: Map<string, string>) (k, v) -> m.Add(k, v)) keyValuePairs
keyValuePairs <- result
member this.ConfigFile = fn
member this.GetStr key =
let getEnv (varName: string) = Environment.GetEnvironmentVariable varName
let configKeyToEnvVar (key: string) =
key.ToUpper().Replace(".", "_").Replace("-", "_")
match getEnv (configKeyToEnvVar key) with
| null | "" -> keyValuePairs.TryFind key
| envValue -> Some envValue
member this.GetBool key =
Option.map (fun value -> Regex.IsMatch(value, "^(yes|true|enable|1)$", RegexOptions.IgnoreCase)) (this.GetStr key)
member this.GetFloat key =
let value = keyValuePairs.TryGetValue key
let parseFloat s = try Some (float s) with | _ -> None
Option.map parseFloat (this.GetStr key)
member this.GetInt key =
let value = keyValuePairs.TryGetValue key
let parseInt s = try Some (int s) with | _ -> None
Option.map parseInt (this.GetStr key)
module Mbackup.Lib
open System
open System.Text.RegularExpressions
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 =
let doAfter s =
if this.Level <= level then
let ts = DateTime.Now.ToString("s")
printf "%s %s %s\n" ts (Logger.LevelToString level) s
else
printf ""
Printf.ksprintf doAfter fmt
member this.SetLevel level =
this.Level = level
member this.Debug fmt = this.LogMaybe Logger.DEBUG fmt
member this.Info fmt = this.LogMaybe Logger.INFO fmt
member this.Warning fmt = this.LogMaybe Logger.WARNING fmt
member this.Error fmt = this.LogMaybe Logger.ERROR fmt
// append string s to list if pred is true
let appendWhen (pred: bool) (lst: string list) (s: string) = if pred then List.append lst [s] else lst
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.
// Supported windows path: C:\foo, C:/foo, /c/foo
// MingwPath format: /cygdrive/c/foo
let toMingwPath (windowsPath: string) =
let pattern = Regex("^/([c-zC-Z])/", RegexOptions.None)
let result =
if pattern.IsMatch(windowsPath) then
"/cygdrive" + windowsPath
else
let pattern = Regex("^([c-zC-Z]):", RegexOptions.None)
if pattern.IsMatch(windowsPath) then
let result = windowsPath.Replace('\\', '/')
"/cygdrive/" + result.Substring(0, 1).ToLower() + result.Substring(2)
else
windowsPath
result
let ensureDir (path: string) = if path.EndsWith "/" then path else path + "/"
let ensureWinDir (path: string) = if path.EndsWith "\\" then path else path + "\\"
......@@ -8,16 +8,19 @@
// /%appdata%/mbackup/mbackup-default.exclude
// /%appdata%/mbackup/local.exclude (optional)
module Mbackup
module Mbackup.Program
open System
open System.IO
open System.Diagnostics
open System.Text.RegularExpressions;
open System.Text.RegularExpressions
open System.Diagnostics.CodeAnalysis
open Argu
open Mbackup.Lib
open Mbackup.ConfigParser
let ExitBadParam = 1
let ExitTimeout = 2
let ExitIOError = 3
......@@ -41,90 +44,28 @@ with
| Node_Name _ -> "local node's name, used in remote logging"
| Ssh_Key _ -> "ssh private key, used when backup to remote ssh node"
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.
// Supported windows path: C:\foo, C:/foo, /c/foo
// MingwPath format: /cygdrive/c/foo
let ToMingwPath (windowsPath: string) =
let pattern = Regex("^/([c-zC-Z])/", RegexOptions.None)
let result =
if pattern.IsMatch(windowsPath) then
"/cygdrive" + windowsPath
else
let pattern = Regex("^([c-zC-Z]):", RegexOptions.None)
if pattern.IsMatch(windowsPath) then
let result = windowsPath.Replace('\\', '/')
"/cygdrive/" + result.Substring(0, 1).ToLower() + result.Substring(2)
else
windowsPath
result
let appDataRoamingDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) |> toMingwPath |> ensureDir
let programDataDirWin = getEnv "PROGRAMDATA" |> ensureWinDir
let programDataDir = toMingwPath programDataDirWin
let appDataLocalDirWin = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) |> ensureWinDir
let appDataLocalDir = appDataLocalDirWin |> toMingwPath
let EnsureDir (path: string) = if path.EndsWith "/" then path else path + "/"
let EnsureWinDir (path: string) = if path.EndsWith "\\" then path else path + "\\"
let appDataRoamingDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) |> ToMingwPath |> EnsureDir
let programDataDirWin = GetEnv "PROGRAMDATA" |> EnsureWinDir
let programDataDir = ToMingwPath programDataDirWin
let appDataLocalDirWin = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) |> EnsureWinDir
let appDataLocalDir = appDataLocalDirWin |> ToMingwPath
let mbackupInstallDirWin = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) |> EnsureDir |> fun s -> s + "mbackup"
let mbackupInstallDir = mbackupInstallDirWin |> ToMingwPath
let mbackupInstallDirWin = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) |> ensureDir |> fun s -> s + "mbackup"
let mbackupInstallDir = mbackupInstallDirWin |> toMingwPath
let userHomeWin =
GetEnvDefault "HOME" (Environment.GetFolderPath(Environment.SpecialFolder.UserProfile))
|> EnsureWinDir
getEnvDefault "HOME" (Environment.GetFolderPath(Environment.SpecialFolder.UserProfile))
|> ensureWinDir
let userHome = userHomeWin |> ToMingwPath
let userHome = userHomeWin |> toMingwPath
//let userConfigDir = appDataRoamingDir + "mbackup/"
let userConfigDirWin = programDataDirWin + "mbackup\\"
let userConfigDir = programDataDir + "mbackup/"
let runtimeDirWin = appDataLocalDirWin + "mbackup\\"
let runtimeDir = appDataLocalDir + "mbackup/"
let mbackupConfigFile = userConfigDirWin + "mbackup.txt"
let isLocalTarget (target: string) = target.StartsWith "/"
// expand user file to mingw64 rsync supported path.
......@@ -134,9 +75,9 @@ let isLocalTarget (target: string) = target.StartsWith "/"
// etc
let expandUserFile (fn: string) =
let fn =
let documentsDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) |> ToMingwPath |> EnsureDir
let picturesDir = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures) |> ToMingwPath |> EnsureDir
let desktopDir = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) |> ToMingwPath |> EnsureDir
let documentsDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) |> toMingwPath |> ensureDir
let picturesDir = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures) |> toMingwPath |> ensureDir
let desktopDir = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) |> toMingwPath |> ensureDir
let fn = Regex.Replace(fn, "^My Documents/", documentsDir, RegexOptions.IgnoreCase)
let fn = Regex.Replace(fn, "^Documents/", documentsDir, RegexOptions.IgnoreCase)
let fn = Regex.Replace(fn, "^我的文档/", documentsDir)
......@@ -162,14 +103,14 @@ let generateMbackupList (logger: Logger) =
let mbackupList = runtimeDirWin + "mbackup.list"
// local functions
let dropEmptyLinesAndComments = Seq.filter (fun (line: string) -> not (line.TrimStart().StartsWith("#") || line.TrimEnd().Equals("")))
let dropEmptyLinesAndComments lines = Seq.filter (fun (line: string) -> not (line.TrimStart().StartsWith("#") || line.TrimEnd().Equals(""))) lines
let readMbackupListFile fn = File.ReadAllLines(fn) |> dropEmptyLinesAndComments
try
let defaultListLines = readMbackupListFile mbackupDefaultList |> Seq.map ToMingwPath
let defaultListLines = readMbackupListFile mbackupDefaultList |> Seq.map toMingwPath
let localListLinesMaybe =
try
let lines = readMbackupListFile mbackupLocalList |> Seq.map ToMingwPath
let lines = readMbackupListFile mbackupLocalList |> Seq.map toMingwPath
(true, lines)
with
| :? System.IO.FileNotFoundException ->
......@@ -186,16 +127,16 @@ let generateMbackupList (logger: Logger) =
// skip and give a warning on non-absolute path.
// For user-default.list, auto prefix user's home dir, auto expand Documents, Downloads etc special folder.
File.WriteAllLines(mbackupList, allLines)
logger.Info "%s written" mbackupList
logger.Info "mbackup.list file written: %s" mbackupList
true
with
| :? System.IO.IOException as ex ->
logger.Error "Read/write file failed: %s %s" ex.Source ex.Message
false
| ex ->
logger.Error "Read/write mbackup list file failed: %s" ex.Message
false
// append string s to list if pred is true
let appendWhen (pred: bool) (lst: string list) (s: string) = if pred then List.append lst [s] else lst
[<EntryPoint>]
let main argv =
let errorHandler = ProcessExiter(colorizer = function ErrorCode.HelpText -> None | _ -> Some ConsoleColor.Red)
......@@ -206,8 +147,8 @@ let main argv =
let logger = Logger()
logger.Info "using user config dir: %s" userConfigDirWin
logger.Info "using runtime dir: %s" runtimeDirWin
logger.Info "user config dir: %s" userConfigDirWin
logger.Info "runtime dir: %s" runtimeDirWin
let rsyncCmd: string list = []
let rsyncCmd = appendWhen dryRun rsyncCmd "--dry-run"
......@@ -229,19 +170,20 @@ let main argv =
// TODO remove usage of test dir.
let mbackupInstallDirWinTest = "D:\\downloads\\apps\\mbackupTest\\"
let mbackupInstallDirTest = mbackupInstallDirWinTest |> ToMingwPath |> EnsureDir
let mbackupInstallDirTest = mbackupInstallDirWinTest |> toMingwPath |> ensureDir
let sshExeFile = mbackupInstallDirTest + "rsync-w64/usr/bin/ssh.exe"
let sshConfigFile = userHome + ".ssh/config"
let sshPrivateKeyFile = results.GetResult(Ssh_Key, defaultValue = userHome + ".ssh/id_rsa") |> ToMingwPath
let sshPrivateKeyFile = results.GetResult(Ssh_Key, defaultValue = userHome + ".ssh/id_rsa") |> toMingwPath
let rsyncCmd = List.append rsyncCmd [sprintf "-e \"%s -F %s -i %s -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\"" sshExeFile sshConfigFile sshPrivateKeyFile]
let backupTarget = results.GetResult (Target, defaultValue = Environment.GetEnvironmentVariable "TARGET")
match backupTarget with
| null ->
logger.Error "TARGET is not defined%s" ""
let mbackupConfig = WellsConfig(mbackupConfigFile)
let backupTargetMaybe = mbackupConfig.GetStr("target")
match backupTargetMaybe with
| None ->
logger.Error "TARGET is not defined"
ExitBadParam
| _ ->
let backupTarget = ToMingwPath backupTarget
| Some backupTarget ->
let backupTarget = toMingwPath backupTarget
let rsyncCmd =
if not (isLocalTarget backupTarget)
then
......@@ -257,21 +199,24 @@ let main argv =
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 = mbackupInstallDirWinTest + "rsync-w64\\usr\\bin\\rsync.exe"
let echoExe = "C:\\Program Files\\Git\\usr\\bin\\echo.exe"
try
IO.Directory.CreateDirectory(runtimeDir) |> ignore
IO.Directory.CreateDirectory(userConfigDir) |> ignore
let proc = Process.Start(rsyncExe, rsyncArgs)
logger.Info "%s" (rsyncExe + " " + rsyncArgs)
logger.Info "Note: if you run the following rsync command yourself, make sure the generated file list (%s) is up-to-date.\n%s" mbackupFile (rsyncExe + " " + rsyncArgs)
if proc.WaitForExit Int32.MaxValue then
logger.Info "mbackup exit"
proc.ExitCode
else
logger.Error "mbackup timed out while waiting for rsync to complete%s" ""
logger.Error "mbackup timed out while waiting for rsync to complete"
ExitTimeout
with
| _ ->
logger.Error "Create runtime dir failed%s" ""
| :? System.IO.IOException as ex ->
logger.Error "IO Error: %s %s" ex.Source ex.Message
ExitIOError
| ex ->
logger.Error "Unexpected Error: %s" ex.Message
ExitIOError
......@@ -7,6 +7,8 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="Lib.fs" />
<Compile Include="ConfigParser.fs" />
<Compile Include="Program.fs" />
</ItemGroup>
......
module MbackupTests
open NUnit.Framework
open Mbackup
open Mbackup.Lib
[<SetUp>]
let Setup () =
......@@ -12,16 +12,48 @@ let TestDumb () =
Assert.Pass()
[<Test>]
let TestToMingwPath () =
Assert.That("/cygdrive/c/", Is.EqualTo(ToMingwPath "c:\\"))
Assert.That("/cygdrive/c/", Is.EqualTo(ToMingwPath "C:\\"))
Assert.That("/cygdrive/c/foo", Is.EqualTo(ToMingwPath "C:\\foo"))
Assert.That("/cygdrive/d/foo", Is.EqualTo(ToMingwPath "D:\\foo"))
Assert.That("/cygdrive/d/Foo", Is.EqualTo(ToMingwPath "D:\\Foo"))
Assert.That("/cygdrive/c/foo/bar/", Is.EqualTo(ToMingwPath "C:\\foo\\bar\\"))
Assert.That("/cygdrive/c/foo/bar/baz.txt", Is.EqualTo(ToMingwPath "C:\\foo\\bar\\baz.txt"))
Assert.That("/cygdrive/c/foo", Is.EqualTo(ToMingwPath "C:/foo"))
Assert.That("/cygdrive/c/foo/bar", Is.EqualTo(ToMingwPath "C:/foo/bar"))
Assert.That("/cygdrive/c/foo", Is.EqualTo(ToMingwPath "/c/foo"))
Assert.That("/cygdrive/D/foo", Is.EqualTo(ToMingwPath "/D/foo"))
Assert.That("/var/log", Is.EqualTo(ToMingwPath "/var/log"))
let TesttoMingwPath () =
Assert.That("/cygdrive/c/", Is.EqualTo(toMingwPath "c:\\"))
Assert.That("/cygdrive/c/", Is.EqualTo(toMingwPath "C:\\"))
Assert.That("/cygdrive/c/foo", Is.EqualTo(toMingwPath "C:\\foo"))
Assert.That("/cygdrive/d/foo", Is.EqualTo(toMingwPath "D:\\foo"))
Assert.That("/cygdrive/d/Foo", Is.EqualTo(toMingwPath "D:\\Foo"))
Assert.That("/cygdrive/c/foo/bar/", Is.EqualTo(toMingwPath "C:\\foo\\bar\\"))
Assert.That("/cygdrive/c/foo/bar/baz.txt", Is.EqualTo(toMingwPath "C:\\foo\\bar\\baz.txt"))
Assert.That("/cygdrive/c/foo", Is.EqualTo(toMingwPath "C:/foo"))
Assert.That("/cygdrive/c/foo/bar", Is.EqualTo(toMingwPath "C:/foo/bar"))
Assert.That("/cygdrive/c/foo", Is.EqualTo(toMingwPath "/c/foo"))
Assert.That("/cygdrive/D/foo", Is.EqualTo(toMingwPath "/D/foo"))
Assert.That("/var/log", Is.EqualTo(toMingwPath "/var/log"))
let mysprintf fmt = sprintf fmt
[<Test>]
let TestMyprintf () =
Assert.That("123", Is.EqualTo(mysprintf "123"))
Assert.That("123", Is.EqualTo(mysprintf "%d" 123))
Assert.That("123 456", Is.EqualTo(mysprintf "%d %d" 123 456))
let mylogger fmt =
let doAfter s =
"INFO " + s
Printf.ksprintf doAfter fmt
[<Test>]
let TestMylogger () =
Assert.That("INFO 123", Is.EqualTo(mylogger "123"))
Assert.That("INFO 123", Is.EqualTo(mylogger "%d" 123))
Assert.That("INFO 123 456", Is.EqualTo(mylogger "%d %d" 123 456))
Assert.That("INFO 123 456 a", Is.EqualTo(mylogger "%d %d %s" 123 456 "a"))
[<Test>]
let TestStringSplit () =
let r = "a=b".Split('=')
Assert.That("a", Is.EqualTo(r.[0]))
Assert.That("b", Is.EqualTo(r.[1]))
let r = "a= b".Split('=')
Assert.That("a", Is.EqualTo(r.[0]))
Assert.That(" b", Is.EqualTo(r.[1]))
let r = "a=b=c".Split('=', 2)
Assert.That("a", Is.EqualTo(r.[0]))
Assert.That("b=c", Is.EqualTo(r.[1]))
* COMMENT -*- mode: org -*-
#+Date: 2019-11-12
Time-stamp: <2019-11-12>
Time-stamp: <2019-11-14>
#+STARTUP: content
* notes :entry:
** 2019-11-13 how to run it in dev env?
......@@ -17,6 +17,9 @@ file list works.
ssh transfer works.
remote logging works.
read target from config file works. now I can just run
dotnet run -- -i
** 2019-11-12 docs
- rsync
https://www.samba.org/ftp/rsync/rsync.html
......@@ -38,6 +41,9 @@ remote logging works.
https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/
F# Collection Types - F# | Microsoft Docs
https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/fsharp-collection-types
Modules - F# | Microsoft Docs
https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/modules
https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/namespaces
fsharp-cheatsheet
https://dungpa.github.io/fsharp-cheatsheet/
Literals - F# | Microsoft Docs
......@@ -54,18 +60,37 @@ remote logging works.
MyPictures
DesktopDirectory
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)
DateTime.ToString Method (System) | Microsoft Docs
https://docs.microsoft.com/en-us/dotnet/api/system.datetime.tostring?view=netframework-4.8
Standard Date and Time Format Strings | Microsoft Docs
https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings?view=netframework-4.8
- FSharpLint
http://fsprojects.github.io/FSharpLint/
http://fsprojects.github.io/FSharpLint/index.html
- FSharp.Configuration
http://fsprojects.github.io/FSharp.Configuration/
dotnet add package FSharp.Configuration --version 1.5.0
http://fsprojects.github.io/FSharp.Configuration/IniTypeProvider.html
I will use the ini provider.
user don't need to escape string or pay attension to spaces in ini file.
this lib doesn't work with dotnet core 3.
- Formatted text using printf | F# for fun and profit
https://fsharpforfunandprofit.com/posts/printf/
-
** 2019-11-13 install dir layout.
C:\Program Files\mbackup\rsync-w64\usr\bin\rsync.exe
C:\Program Files\mbackup\rsync-w64\usr\bin\ssh.exe
C:\ProgramData\mbackup\mbackup-default.exclude
C:\ProgramData\mbackup\mbackup-default.list
C:\ProgramData\mbackup\user-default.list
C:\ProgramData\mbackup\mbackup.ini
* later :entry:
** 2019-11-14 supports expand Downloads dir in user-default.list
** 2019-11-14 vscode f# doesn't support open a module
it can only support open a namespace.
using the vscode Ionide-fsharp extension.
* current :entry:
**
** 2019-11-13 next todos
......@@ -83,6 +108,69 @@ C:\ProgramData\mbackup\user-default.list
command line param > env var > mbackup default value.
support --node-name param.
- test run in console and scheduled task.
run in console works.
try run in scheduled task.
SCHTASKS /Create /?
# run mbackup 15m after user logon.
SCHTASKS /Create /U <username> /SC ONLOGON /TN mbackup-logon /TR "\"<path\to\mbackup.exe>\" \"args\"" /DELAY 15:00
# run mbackup at 10am and 4pm.
SCHTASKS /Create /U <username> /SC DAILY /TN mbackup-morning /TR "\"<path\to\mbackup.exe>\" \"args\"" /ST 10:00 /ET 13:00 /K
SCHTASKS /Create /U <username> /SC DAILY /TN mbackup-afternoon /TR "\"<path\to\mbackup.exe>\" \"args\"" /ST 16:00 /ET 19:00 /K
# debug purpose, one time only
SCHTASKS /Create /U <username> /SC ONCE /TN mbackup-afternoon /TR "\"<path\to\mbackup.exe>\" \"args\"" /ST 12:03
- problems
- how many scheduled task to run on a multi-user PC?
each user have its own user-default.list expansion.
should I iter over all users on PC?
I think only current user can get it's profile dir and special dirs.
- maybe config mbackup to run after user logon. with 15minute delay.
always run as current user.
- how to not require any param when running mbackup.exe?
put TARGET and other option in a config file?
define system level TARGET env variable.
use config file is easier for user to edit and making the change effective.
userConfigDir / mbackup.conf
search: F# read config file
FSharp.Configuration
http://fsprojects.github.io/FSharp.Configuration/
- FSharp.Configuration missing reference to System.Runtime.Caching
search: how to reference System.Runtime.Caching for dotnet core project
https://www.nuget.org/packages/System.Runtime.Caching/
dotnet add package System.Runtime.Caching --version 4.6.0
should I add 4.0.0? Can I use a higher version?
still not compatible.
search: use FSharp.Configuration with dotnet core 3
- give up on FSharp.Configuration.
- try this:
FsConfig
https://www.demystifyfp.com/FsConfig/
AppSettings is only supported in V0.0.6 or below.
try F# AppSettings directly.
If nothing is easy to use, write my own parser.
Support similar config format as python wells lib.
- ConfigurationManager.AppSettings Property (System.Configuration) | Microsoft Docs
https://docs.microsoft.com/en-us/dotnet/api/system.configuration.configurationmanager.appsettings?view=netframework-4.8
where should I save the App.config xml file?
search: what is app.config
App.Config: Basics and Best Practices - SubMain Blog
https://blog.submain.com/app-config-basics-best-practices/
What is App.config in C#.NET? How to use it? - Stack Overflow
https://stackoverflow.com/questions/13043530/what-is-app-config-in-c-net-how-to-use-it
Okay. This is not what I want.
This is for application configuration (rarely change), not for user configuration (can change any time).
dotnet will create <yourapp>.exe.config from your App.config file.
- how to use multiple files in F# dotnet core project?
search: f# module and namespace
search: f# module
Modules - F# | Microsoft Docs
https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/modules
- create an installer. The installer should add scheduled task on install and
delete scheduled task on removal.
......@@ -185,7 +273,7 @@ C:\ProgramData\mbackup\user-default.list
- there is no enough RAM on B75I3. I will stop bogon VM and run win 10 VM.
-
** 2019-11-12 mbackup for windows
** 2019-11-12 mbackup for windows :featurereq:
- features
- windows installer.
bundles mingw rsync and ssh.
......@@ -209,6 +297,32 @@ C:\ProgramData\mbackup\user-default.list
it contains hostname.
-
* done :entry:
** 2019-11-14 support mbackup.txt config.
config file will have wells config file format.
empty lines and comment lines are ignored.
space around = is ignored.
if there are quotes on value side, it's trimmed.
target=user@host:port/path/
# target = user@host:port/path/
abc.def = some value
abc.def.ghi = 1233.45
foo = yes
env variable will be
TARGET
ABC_DEF
ABC_DEF_GHI
FOO
in F# code,
conf = WellsConfig("C:\path\to\file.txt")
conf.GetStr("target")
conf.GetFloat("abc.def.ghi")
conf.GetBool("foo")
it works.
** 2019-11-13 extra user default list.
Documents is replaced by real path.
Downloads
......