Commit 06c2e2fb authored by Yuanle Song's avatar Yuanle Song
Browse files

moved some functions to Lib.fs;

logger functions now support variable number of arguments.
parent f13bbecd
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 + "\\"
let parseMbackupConfig fn =
()
......@@ -8,16 +8,18 @@
// /%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
let ExitBadParam = 1
let ExitTimeout = 2
let ExitIOError = 3
......@@ -41,69 +43,6 @@ 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 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
......@@ -119,12 +58,14 @@ let userHomeWin =
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.ini"
let mbackupConfig: unit = parseMbackupConfig mbackupConfigFile
let isLocalTarget (target: string) = target.StartsWith "/"
// expand user file to mingw64 rsync supported path.
......@@ -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"
......@@ -257,16 +198,16 @@ 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" ""
......
......@@ -7,6 +7,7 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="Lib.fs" />
<Compile Include="Program.fs" />
</ItemGroup>
......
module MbackupTests
open NUnit.Framework
open Mbackup
open Mbackup.Lib
[<SetUp>]
let Setup () =
......@@ -25,3 +25,23 @@ let TestToMingwPath () =
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"))
......@@ -38,6 +38,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,20 +57,41 @@ 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
* current :entry:
**
** 2019-11-14 support mbackup.ini config.
options to support in ini file.
[mbackup]
target=user@host:port/path/
** 2019-11-13 next todos
- DONE fix TODOs in F# code
- DONE add rsync command and arguments for running in windows.
......@@ -83,6 +107,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.
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment