Update the Logger component.
authorGreg Burri <greg.burri@gmail.com>
Thu, 4 May 2017 19:13:53 +0000 (21:13 +0200)
committerGreg Burri <greg.burri@gmail.com>
Thu, 4 May 2017 19:13:53 +0000 (21:13 +0200)
Parasitemia/Logger/Logger.fs
Parasitemia/ParasitemiaCore/Analysis.fs
Parasitemia/ParasitemiaCore/ParasitemiaCore.fsproj
Parasitemia/ParasitemiaCore/packages.config
Parasitemia/ParasitemiaUI/Analysis.fs
Parasitemia/ParasitemiaUI/GUI.fs
Parasitemia/ParasitemiaUI/ParasitemiaUI.fsproj
Parasitemia/ParasitemiaUI/Program.fs
Parasitemia/ParasitemiaUI/Utils.fs
Parasitemia/ParasitemiaUI/packages.config

index 1b4c332..df2144f 100644 (file)
@@ -3,30 +3,61 @@
 open System
 open System.Text
 open System.IO
+open System.IO.Compression
 open System.Diagnostics
 open System.Threading
 open System.Collections.Generic
 
-type Severity = DEBUG = 1 | USER = 2 | WARNING = 3 | ERROR = 4 | FATAL = 5
+type Severity = DEBUG = 1 | INFO = 2 | WARNING = 3 | ERROR = 4 | FATAL = 5
 
-type IListener = abstract NewEntry : Severity -> string -> unit
+type IListener =
+    abstract NewEntry : severity : Severity -> header : string -> message : string -> unit
+
+type private Message =
+    {
+        Message : string
+        ThreadName : string
+        ThreadId : int
+        ModuleCaller : string
+        Severity : Severity
+    }
+
+type private Command =
+    | Write of Message
+    | Stop of AsyncReplyChannel<unit>
 
 [<Sealed>]
 type Log () =
-    let maxSizeFile = 10L * 1024L * 1024L // [byte] (10 MB).
-    let nbEntriesCheckSize = 100; // Each 100 entries added we check the size of the log file to test if it is greater than 'MAX_SIZE_FILE'.
-    let LogDefaultDirectory = "Parasitemia\\Log"
-    let filenameFormat = "{0:D4}.log"
+
+    let extractNumberFromLogfilepath (path : string) : int option =
+        if isNull path then
+            None
+        else
+            let filename = path.Substring(path.LastIndexOf(Path.DirectorySeparatorChar) + 1)
+            let filenameWithoutExtension = filename.Remove(filename.IndexOf('.'))
+            match Int32.TryParse(filenameWithoutExtension) with
+            | (true, n) -> Some n
+            | _ -> None
+
+    let [<Literal>] MAX_SIZE_FILE = 52428800L // [byte] (50 MB).
+    let [<Literal>] NB_ENTRIES_CHECK_SIZE = 100; // Each 100 entries added we check the size of the log file to test if it is greater than 'MAX_SIZE_FILE'.
+    let [<Literal>] COMPRESS_ARCHIVED_FILES = true
+    let [<Literal>] FILENAME_FORMAT = "{0:D4}.log"
+    let [<Literal>] COMPRESSED_FILE_POSTFIX = ".gzip"
     let encoding = Encoding.GetEncoding("UTF-8")
 
+    let compress (filename : string) =
+        use inputStream = new FileStream(filename, FileMode.Open, FileAccess.Read)
+        let filenameCompressed = filename + COMPRESSED_FILE_POSTFIX
+        use compressedStream = new GZipStream(new FileStream(filenameCompressed, FileMode.Create, FileAccess.Write), CompressionLevel.Optimal)
+        inputStream.CopyTo(compressedStream)
+
     let moduleName = System.Diagnostics.StackFrame(1).GetMethod().Module.Name
 
     let mutable stream : StreamWriter = null
+    let mutable filename : string = null
 
     let mutable logDir : string = null
-    let mutable absoluteDir : string = null
-
-    let mutable nbEntries = 0L
 
     let monitor = Object()
 
@@ -41,120 +72,174 @@ type Log () =
 
     static let instance = new Log()
 
+    let openLogFile (entryNumber : int64) =
+        if not (isNull logDir) then
+            try
+                if isNull stream || (entryNumber % (int64 NB_ENTRIES_CHECK_SIZE) = 0L) && stream.BaseStream.Length > MAX_SIZE_FILE
+                then
+                    if not (isNull stream)
+                    then
+                        stream.Close()
+                        if COMPRESS_ARCHIVED_FILES then
+                            compress filename
+                            File.Delete(filename)
+
+                    // Search the last id among the log files.
+                    let mutable n = 1
+                    for existingFile in Directory.GetFiles(logDir) do
+                        match extractNumberFromLogfilepath existingFile with
+                        | Some n' when n' > n -> n <- n'
+                        | _ -> ()
+
+                    filename <- Path.Combine(logDir, String.Format(FILENAME_FORMAT, n))
+                    try
+                        if File.Exists(filename + COMPRESSED_FILE_POSTFIX) || FileInfo(filename).Length > MAX_SIZE_FILE
+                        then
+                            filename <- Path.Combine(logDir, String.Format(FILENAME_FORMAT, n + 1))
+                    with
+                    | :? FileNotFoundException -> () // The file may not exist.
+
+                    stream <- new StreamWriter(filename, true, encoding)
+            with
+            | ex -> Console.Error.WriteLine("Can't open the file log: {0}", ex)
+
+    let write (msg : Message) (entryNumber : int64) =
+        openLogFile entryNumber
+
+        let header =
+            String.Format(
+                "{0:yyyy-MM-dd HH:mm:ss.fff} [{1}] {{{2}}} ({3})",
+                TimeZone.CurrentTimeZone.ToLocalTime(DateTime.UtcNow),
+                string msg.Severity,
+                msg.ModuleCaller,
+                (if String.IsNullOrEmpty(msg.ThreadName) then string msg.ThreadId else sprintf "%s-%i" msg.ThreadName msg.ThreadId)
+            )
+
+        for listener in listeners do
+            listener.NewEntry msg.Severity header msg.Message
+
+        if not (isNull stream)
+        then
+            try
+                stream.WriteLine("{0} : {1}", header, msg.Message)
+                stream.Flush()
+            with
+            | :? IOException as ex -> Console.Error.WriteLine("Unable to write to the log file: {0}", ex)
+
+    let writeAgent =
+        new MailboxProcessor<Command>(
+            fun inbox ->
+                let rec loop (nbEntries : int64) =
+                    async {
+                        let! command = inbox.Receive()
+                        match command with
+                        | Write message ->
+                            write message nbEntries
+                            return! loop (nbEntries + 1L)
+                        | Stop replyChannel ->
+                            replyChannel.Reply ()
+                    }
+                loop 1L
+        )
+
+    do
+        writeAgent.Start()
+
     let setLogDirectory (dir : string) =
         lock monitor (fun () ->
             logDir <- dir
-            absoluteDir <- Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), logDir)
 
-            if stream <> null then
+            if not <| isNull stream then
                 stream.Close()
                 stream <- null
 
             try
-                if not <| Directory.Exists(absoluteDir) then
-                    Directory.CreateDirectory(absoluteDir) |> ignore
+                if not <| Directory.Exists(logDir)
+                then
+                    Directory.CreateDirectory(logDir) |> ignore
             with
-            | _ as ex -> Console.Error.WriteLine("Unable to create the log directory: {0}", absoluteDir))
-
-    let openLogFile () =
-        try
-            if stream = null || (nbEntries % (int64 nbEntriesCheckSize) = 0L) && stream.BaseStream.Length > maxSizeFile then
-                if stream <> null then
-                    stream.Close()
-
-                let mutable n = 1
-                for existingFile in Directory.GetFiles(absoluteDir) do
-                    let current_n = ref 0
-                    if Int32.TryParse(existingFile.Remove(existingFile.LastIndexOf('.')), current_n) && !current_n > n then
-                        n <- !current_n
-
-                let mutable filename = Path.Combine(absoluteDir, String.Format(filenameFormat, n))
-                try
-                    if (FileInfo(filename).Length > maxSizeFile) then
-                        filename <- Path.Combine(absoluteDir, String.Format(filenameFormat, n + 1))
-                with
-                | :? FileNotFoundException -> () // The file may not exist.
-
-                stream <- new StreamWriter(filename, true, encoding)
-        with
-        | _ as ex -> Console.Error.WriteLine("Can't open the file log: {0}", ex)
-
-    do
-        setLogDirectory LogDefaultDirectory
+            | _ -> Console.Error.WriteLine("Unable to create the log directory: {0}", logDir))
 
     interface IDisposable with
         member this.Dispose () =
-            if stream <> null then
+            if not (isNull stream)
+            then
                 stream.Dispose()
-
-    member private this.Write (message : string, severity : Severity) =
-        lock monitor (fun () ->
-            nbEntries <- nbEntries + 1L
-            openLogFile ()
-
-            if stream <> null then
-                let mutable moduleNameCaller = moduleName
-                match StackTrace().GetFrames() |> Array.tryPick (fun frame -> let name = frame.GetMethod().Module.Name
-                                                                              if name <> moduleName then Some name else None) with
-                | Some name -> moduleNameCaller <- name
-                | _ -> ()
-
-                let threadName = Thread.CurrentThread.Name
-
-                for listener in listeners do
-                    listener.NewEntry severity message
-
-                try
-                    stream.WriteLine(
-                        "{0:yyyy-MM-dd HH:mm:ss.fff} [{1}] {{{2}}} ({3}) : {4}",
-                        TimeZone.CurrentTimeZone.ToLocalTime(DateTime.UtcNow),
-                        severity.ToString(),
-                        moduleNameCaller,
-                        (if String.IsNullOrEmpty(threadName) then Thread.CurrentThread.ManagedThreadId.ToString() else String.Format("{0}-{1}", threadName, Thread.CurrentThread.ManagedThreadId)),
-                        message
-                    )
-                    stream.Flush()
-                with
-                | :? IOException as ex -> Console.Error.WriteLine("Unable to write to the log file: {0}", ex))
-
-
-    member private this.AddListener (listener : IListener) =
+            (writeAgent :> IDisposable).Dispose()
+
+    member private this.Write (message : string) (severity : Severity) =
+        let moduleNameCaller =
+            match StackTrace().GetFrames() |> Array.tryPick (fun frame -> let name = frame.GetMethod().Module.Name
+                                                                          if name <> moduleName then Some name else None) with
+            | Some name -> name
+            | _ -> moduleName
+
+        let command =
+            Write
+                {
+                    Message = message
+                    ThreadName = Thread.CurrentThread.Name
+                    ThreadId = Thread.CurrentThread.ManagedThreadId
+                    ModuleCaller = moduleNameCaller
+                    Severity = severity
+                }
+
+        writeAgent.Post command
+
+    /// <summary>
+    /// Will stop and wait a reply. Used to flush the remaining messages.
+    /// </summary>
+    member private this.Stop () =
+        writeAgent.PostAndReply(
+            fun replyChannel ->
+                Stop replyChannel
+        )
+
+    member this.LogDirectory
+        with get () = logDir
+        and set value = setLogDirectory value
+
+    static member SetLogDirectory (dir : string) =
+        instance.LogDirectory <- dir
+
+    member this.AddListener (listener : IListener) =
         lock monitor (fun () ->
-            if not <| listeners.Contains(listener) then
+            if not <| listeners.Contains(listener)
+            then
                 listeners.Add(listener))
 
-    member private this.RmListener (listener : IListener) =
+    member this.RmListener (listener : IListener) =
         lock monitor (fun () ->
             listeners.Remove(listener) |> ignore)
 
     static member AddListener (listener : IListener) = instance.AddListener(listener)
     static member RmListener (listener : IListener) = instance.RmListener(listener)
 
-    static member LogWithTime (message : string, severity : Severity, f : unit -> 'a option, [<ParamArray>] args: Object[]) : 'a option =
+    static member LogWithTime (severity : Severity) (f : unit -> 'a) (format : Printf.StringFormat<'b, 'a>) : 'b =
         let sw = Stopwatch()
         sw.Start()
         let res = f ()
         sw.Stop()
-        if res.IsSome then
-            instance.Write(String.Format(message, args) + sprintf " (time: %d ms)" sw.ElapsedMilliseconds, severity)
-        res
+        Printf.kprintf (fun s -> instance.Write (s + sprintf " (time: %d ms)" sw.ElapsedMilliseconds) severity; res) format
 
-    static member Debug (message : string, [<ParamArray>] args : Object[]) =
+    static member Debug format =
 #if DEBUG
-        instance.Write(String.Format(message, args), Severity.DEBUG)
+        Printf.kprintf (fun s -> instance.Write s Severity.DEBUG) format
 #else
-        ()
+        Printf.kprintf (fun _ -> ()) format // TODO: can it be simplify?
 #endif
 
-    static member User (message : string, [<ParamArray>] args : Object[]) =
-        instance.Write(String.Format(message, args), Severity.USER)
+    static member Info format =
+        Printf.kprintf (fun s -> instance.Write s Severity.INFO) format
 
-    static member Warning (message : string, [<ParamArray>] args : Object[]) =
-        instance.Write(String.Format(message, args), Severity.WARNING)
+    static member Warning format =
+        Printf.kprintf (fun s -> instance.Write s Severity.WARNING) format
 
-    static member Error (message : string, [<ParamArray>] args : Object[]) =
-        instance.Write(String.Format(message, args), Severity.ERROR)
+    static member Error format =
+        Printf.kprintf (fun s -> instance.Write s Severity.ERROR) format
 
-    static member Fatal (message : string, [<ParamArray>] args : Object[]) =
-        instance.Write(String.Format(message, args), Severity.FATAL)
+    static member Fatal format =
+        Printf.kprintf (fun s -> instance.Write s Severity.FATAL) format
 
+    static member Shutdown () =
+        instance.Stop()
index afcf4df..f607760 100644 (file)
@@ -40,8 +40,8 @@ let doAnalysis (img : Image<Bgr, byte>) (name : string) (config : Config) (repor
         reportWithVal percent ()
 
     let inline buildLogWithName (text : string) = sprintf "№ %s: %s" name text
-    let logWithName mess = Log.User(buildLogWithName mess)
-    let inline logTimeWithName (text : string) (f : unit -> 'a option) : 'a option = Log.LogWithTime((buildLogWithName text), Severity.USER, f)
+    let logWithName mess = Log.Info "%s" (buildLogWithName mess)
+    let inline logTimeWithName (text : string) (f : unit -> 'a option) : 'a option = Log.LogWithTime Severity.INFO f "%s" (buildLogWithName text)
 
     // Monadic construction to be able to abort the progress when running.
     maybe {
@@ -186,7 +186,7 @@ let doMultipleAnalysis (imgs : (string * Config * Image<Bgr, byte>) list) (repor
                     | None -> None
                 with
                 | ex ->
-                    Log.Error("Analysis {0} failed: {1}", id, ex)
+                    Log.Error "Analysis %s failed: %O" id ex
                     None
         )
         |> PSeq.withDegreeOfParallelism n
index 8275c95..77ee775 100644 (file)
       <Private>True</Private>
     </Reference>
     <Reference Include="FSharp.Core">
-      <HintPath>..\packages\FSharp.Core.4.1.2\lib\net45\FSharp.Core.dll</HintPath>
+      <HintPath>..\packages\FSharp.Core.4.1.17\lib\net45\FSharp.Core.dll</HintPath>
     </Reference>
     <Reference Include="MathNet.Numerics">
-      <HintPath>..\packages\MathNet.Numerics.3.17.0\lib\net40\MathNet.Numerics.dll</HintPath>
+      <HintPath>..\packages\MathNet.Numerics.3.19.0\lib\net40\MathNet.Numerics.dll</HintPath>
     </Reference>
     <Reference Include="MathNet.Numerics.FSharp">
-      <HintPath>..\packages\MathNet.Numerics.FSharp.3.17.0\lib\net40\MathNet.Numerics.FSharp.dll</HintPath>
+      <HintPath>..\packages\MathNet.Numerics.FSharp.3.19.0\lib\net40\MathNet.Numerics.FSharp.dll</HintPath>
     </Reference>
     <Reference Include="mscorlib" />
     <Reference Include="System" />
index cbdda29..d8ab14a 100644 (file)
@@ -1,8 +1,8 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
   <package id="FSharp.Collections.ParallelSeq" version="1.0.2" targetFramework="net452" />
-  <package id="FSharp.Core" version="4.1.2" targetFramework="net462" />
-  <package id="MathNet.Numerics" version="3.17.0" targetFramework="net462" />
-  <package id="MathNet.Numerics.FSharp" version="3.17.0" targetFramework="net462" />
+  <package id="FSharp.Core" version="4.1.17" targetFramework="net452" />
+  <package id="MathNet.Numerics" version="3.19.0" targetFramework="net452" />
+  <package id="MathNet.Numerics.FSharp" version="3.19.0" targetFramework="net452" />
   <package id="System.ValueTuple" version="4.3.0" targetFramework="net462" />
 </packages>
\ No newline at end of file
index ceb61be..e276be6 100644 (file)
@@ -27,7 +27,7 @@ let showWindow (parent : Window) (state : State.State) : bool =
     let logListener =
         {
             new Logger.IListener with
-                member this.NewEntry severity mess =
+                member this.NewEntry severity _header mess =
                     win.Dispatcher.Invoke(fun () ->
                         win.textLog.Inlines.Add(Documents.Run(mess))
                         win.textLog.Inlines.Add(Documents.LineBreak())
@@ -140,11 +140,11 @@ let showWindow (parent : Window) (state : State.State) : bool =
                             | Some results ->
                                 for id, cells in results do
                                     state.SetResult (int id) cells
-                                Logger.Log.User("All analyses terminated successfully")
+                                Logger.Log.Info "All analyses terminated successfully"
                                 atLeastOneAnalysisPerformed <- true
                                 analysisPerformed <- true
                             | None ->
-                                Logger.Log.User("Analysis aborted")
+                                Logger.Log.Info "Analysis aborted"
 
                             win.Dispatcher.Invoke(fun () ->
                                 win.progress.Value <- if maybeResults.IsSome then 100. else 0.
index 0a93d90..35bc2ad 100644 (file)
@@ -266,7 +266,7 @@ let run (defaultConfig : Config) (fileToOpen : string option) =
             displayStatusMessage "Document saved"
         with
         | :? IOException as ex ->
-            Log.Error(ex.ToString())
+            Log.Error "%O" ex
             MessageBox.Show(sprintf "The document cannot be save in \"%s\"" state.FilePath, "Error saving the document", MessageBoxButton.OK, MessageBoxImage.Error) |> ignore
 
     let saveCurrentDocumentAsNewFile () =
@@ -384,7 +384,7 @@ let run (defaultConfig : Config) (fileToOpen : string option) =
             updateGUI ()
         with
         | :? IOException as ex ->
-            Log.Error(ex.ToString())
+            Log.Error "%O" ex
             state.FilePath <- previousFilePath
             MessageBox.Show(sprintf "The document cannot be loaded from \"%s\"" filepath, "Error loading the document", MessageBoxButton.OK, MessageBoxImage.Error) |> ignore
 
@@ -414,7 +414,7 @@ let run (defaultConfig : Config) (fileToOpen : string option) =
                 Export.exportResults state dialog.FileName
             with
             | :? IOException as ex ->
-                Log.Error(ex.ToString())
+                Log.Error "%O" ex
                 MessageBox.Show(sprintf "The results cannot be exported in \"%s\"" state.FilePath, "Error exporting the files", MessageBoxButton.OK, MessageBoxImage.Error) |> ignore
 
     let importImage () =
@@ -429,7 +429,7 @@ let run (defaultConfig : Config) (fileToOpen : string option) =
                     addPreview srcImg
                 with
                 | _ as ex ->
-                    Log.Error(ex.ToString())
+                    Log.Error "%O" ex
                     MessageBox.Show(sprintf "Unable to read the image from \"%s\"" filename, "Error adding an image", MessageBoxButton.OK, MessageBoxImage.Error) |> ignore
 
             updateGlobalParasitemia ()
index c5bb1e2..801eac2 100644 (file)
       <HintPath>..\..\..\Emgu\emgucv-windesktop 3.1.0.2282\bin\Emgu.CV.World.dll</HintPath>
     </Reference>
     <Reference Include="FSharp.Core">
-      <HintPath>..\packages\FSharp.Core.4.1.2\lib\net45\FSharp.Core.dll</HintPath>
+      <HintPath>..\packages\FSharp.Core.4.1.17\lib\net45\FSharp.Core.dll</HintPath>
     </Reference>
     <Reference Include="FSharp.ViewModule">
       <HintPath>..\packages\FSharp.ViewModule.Core.1.0.7.0\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid1+MonoTouch1\FSharp.ViewModule.dll</HintPath>
     </Reference>
     <Reference Include="mscorlib" />
     <Reference Include="Newtonsoft.Json">
-      <HintPath>..\packages\Newtonsoft.Json.10.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
+      <HintPath>..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
     </Reference>
     <Reference Include="PresentationCore" />
     <Reference Include="PresentationFramework" />
index 4cc4840..0f6df12 100644 (file)
@@ -67,7 +67,7 @@ let main args =
         0
     else
         try
-            Log.User("Starting of Parasitemia UI ...")
+            Log.Info "Starting of Parasitemia UI ..."
 
             let result =
                 match parseArgs args with
@@ -82,10 +82,10 @@ let main args =
                         Directory.CreateDirectory output |> ignore
 
                         use logFile = new StreamWriter(new FileStream(Path.Combine(output, "log.txt"), FileMode.Append, FileAccess.Write))
-                        let listener = { new IListener with member this.NewEntry severity mess = logFile.WriteLine(mess) }
+                        let listener = { new IListener with member this.NewEntry severity header mess = logFile.WriteLine(header + " : " + mess) }
                         Log.AddListener(listener)
 
-                        Log.User (sprintf "=== New run : %O %s ===" DateTime.Now  (if debug then "[DEBUG]" else "[RELEASE]"))
+                        Log.Info "=== New run : %O %s ===" DateTime.Now  (if debug then "[DEBUG]" else "[RELEASE]")
 
                         let files = match input with
                                     | File file -> [ file ]
@@ -95,7 +95,7 @@ let main args =
 
                         let images = [ for file in files -> Path.GetFileNameWithoutExtension(FileInfo(file).Name), config.Copy(), new Image<Bgr, byte>(file) ]
 
-                        Log.LogWithTime("Whole analyze", Severity.USER, (fun () ->
+                        Log.LogWithTime Severity.INFO (fun () ->
                             match ParasitemiaCore.Analysis.doMultipleAnalysis images None with
                             | Some results ->
                                 for id, cells in results do
@@ -105,7 +105,7 @@ let main args =
                                     fprintf resultFile "File: %s %d %d %.2f (diameter: %O)\n" id total infected (100. * (float infected) / (float total)) config.RBCRadius
                             | None ->
                                 fprintf resultFile "Analysis aborted"
-                            Some ())) |> ignore
+                            Some ()) "Whole analyze" |> ignore
 
                         Log.RmListener(listener)
                         0
@@ -114,10 +114,10 @@ let main args =
                         if debug then config.Debug <- DebugOn "."
                         GUI.run config fileToOpen
 
-            Log.User("Parasitemia UI closed")
+            Log.Info "Parasitemia UI closed"
             result
 
         with
         | ex ->
-            Log.Fatal("Error: {0}", ex)
+            Log.Fatal "Error: %A" ex
             1
\ No newline at end of file
index 5b3ba71..fa4af25 100644 (file)
@@ -32,7 +32,7 @@ let private savePredefinedPPIToFile (predefinedPPI : PredefinedPPI list) =
         file.Write(JsonConvert.SerializeObject(predefinedPPI, JsonSerializerSettings(Formatting = Formatting.Indented)))
     with
         ex ->
-            Logger.Log.Error("Unable to save predefined PPI to file \"{0}\": {1}", predefinedPPIFilepath, ex)
+            Logger.Log.Error "Unable to save predefined PPI to file \"%s\": %O" predefinedPPIFilepath ex
 
 let private saveSensorSizesToFile (sensorSizes : SensorSize list) =
     try
@@ -40,7 +40,7 @@ let private saveSensorSizesToFile (sensorSizes : SensorSize list) =
         file.Write(JsonConvert.SerializeObject(sensorSizes, JsonSerializerSettings(Formatting = Formatting.Indented)))
     with
         ex ->
-            Logger.Log.Error("Unable to save sensor sizes to file \"{0}\": {1}", sensorSizesFilepath, ex)
+            Logger.Log.Error "Unable to save sensor sizes to file \"%s\": %O" sensorSizesFilepath ex
 
 let predefinedPPI : PredefinedPPI list =
     try
index 432eb3c..a2d59d7 100644 (file)
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
   <package id="Expression.Blend.Sdk" version="1.0.2" targetFramework="net46" />
-  <package id="FSharp.Core" version="4.1.2" targetFramework="net462" />
+  <package id="FSharp.Core" version="4.1.17" targetFramework="net452" />
   <package id="FSharp.ViewModule.Core" version="1.0.7.0" targetFramework="net462" />
   <package id="FsXaml.Wpf" version="3.1.6" targetFramework="net462" />
-  <package id="Newtonsoft.Json" version="10.0.1" targetFramework="net462" />
+  <package id="Newtonsoft.Json" version="10.0.2" targetFramework="net452" />
   <package id="System.ValueTuple" version="4.3.0" targetFramework="net462" />
 </packages>
\ No newline at end of file