Mercurial > servermonitor
annotate ServerMonitor/Objects/Checks/FileCheck.cs @ 23:3866c19535fd
Fix NullReferenceException when checks are executed on a brand new server.
author | Brad Greco <brad@bgreco.net> |
---|---|
date | Thu, 30 May 2019 21:40:27 -0400 |
parents | 68d7834dc28e |
children | 88ca7e4fc023 |
rev | line source |
---|---|
3 | 1 using System; |
2 using System.Collections.Generic; | |
3 using System.ComponentModel; | |
4 using System.Linq; | |
5 using System.Text.RegularExpressions; | |
6 | |
7 namespace ServerMonitorApp | |
8 { | |
17 | 9 /// <summary>Checks file metadata on a remote server.</summary> |
3 | 10 [DisplayName("File check"), Description("Check file size or modified time"), DisplayWeight(12)] |
11 public class FileCheck : SshCheck | |
12 { | |
17 | 13 /// <summary>The command to execute. Must return the output format of GNU ls(1) with the long-iso time style.</summary> |
14 /// <remarks>Would be better to not rely on the output of ls.</remarks> | |
13
a36cc5c123f4
Fix SSH command sublclasses holding on to old command strings after a program update.
Brad Greco <brad@bgreco.net>
parents:
9
diff
changeset
|
15 public override string Command => string.Format(FileCommand, Regex.Replace(File, "^~", "$HOME")); |
a36cc5c123f4
Fix SSH command sublclasses holding on to old command strings after a program update.
Brad Greco <brad@bgreco.net>
parents:
9
diff
changeset
|
16 |
17 | 17 /// <summary>The command to execute, with a placeholder of {0} for the file to check.</summary> |
13
a36cc5c123f4
Fix SSH command sublclasses holding on to old command strings after a program update.
Brad Greco <brad@bgreco.net>
parents:
9
diff
changeset
|
18 protected string FileCommand |
a36cc5c123f4
Fix SSH command sublclasses holding on to old command strings after a program update.
Brad Greco <brad@bgreco.net>
parents:
9
diff
changeset
|
19 { |
a36cc5c123f4
Fix SSH command sublclasses holding on to old command strings after a program update.
Brad Greco <brad@bgreco.net>
parents:
9
diff
changeset
|
20 get |
a36cc5c123f4
Fix SSH command sublclasses holding on to old command strings after a program update.
Brad Greco <brad@bgreco.net>
parents:
9
diff
changeset
|
21 { |
17 | 22 // Invert because POSIX says so. |
23 int timeZoneOffset = TimeZoneInfo.Local.GetUtcOffset(DateTime.Now).Hours * -1; | |
24 // Set the date format to long-iso since it's easy to parse. | |
25 // Set the time zone to the match the client time zone so comparisons are reliable. | |
13
a36cc5c123f4
Fix SSH command sublclasses holding on to old command strings after a program update.
Brad Greco <brad@bgreco.net>
parents:
9
diff
changeset
|
26 return "export TIME_STYLE=long-iso; export TZ=UTC" + timeZoneOffset + "; ls -l \"{0}\""; |
a36cc5c123f4
Fix SSH command sublclasses holding on to old command strings after a program update.
Brad Greco <brad@bgreco.net>
parents:
9
diff
changeset
|
27 } |
a36cc5c123f4
Fix SSH command sublclasses holding on to old command strings after a program update.
Brad Greco <brad@bgreco.net>
parents:
9
diff
changeset
|
28 } |
3 | 29 |
17 | 30 /// <summary>The path of the file to check.</summary> |
3 | 31 public string File { get; set; } |
32 | |
17 | 33 /// <summary>Whether the file size will be checked.</summary> |
3 | 34 public bool CheckFileSize { get; set; } |
35 | |
17 | 36 /// <summary>Whether the file is expected to be less than or greater than a certain size.</summary> |
3 | 37 public bool FileSizeLessThan { get; set; } |
38 | |
17 | 39 /// <summary>The size to compare the file against in bytes.</summary> |
4 | 40 public int FileSize { get; set; } |
3 | 41 |
17 | 42 /// <summary>The size to compare the file against in the selected size units.</summary> |
3 | 43 public double FileSizeInSelectedUnits |
44 { | |
4 | 45 get => Math.Round(ConvertBytesToSelectedUnits(FileSize), 1); |
46 set => FileSize = ConvertSelectedUnitsToBytes(value); | |
3 | 47 } |
48 | |
17 | 49 /// <summary>The units of the file size.</summary> |
3 | 50 public SizeUnits FileSizeUnits { get; set; } |
51 | |
17 | 52 /// <summary>Whether the file modified time will be checked.</summary> |
3 | 53 public bool CheckDateModified { get; set; } |
54 | |
17 | 55 /// <summary>Whether the file is expected to be older than or newer than a certain date.</summary> |
3 | 56 public bool DateModifiedOlderThan { get; set; } |
57 | |
17 | 58 /// <summary>The number of time units to compare then file modified time against.</summary> |
3 | 59 public double DateModified { get; set; } |
60 | |
17 | 61 /// <summary>The units of the file date modified.</summary> |
3 | 62 public TimeUnits DateModifiedUnits { get; set; } |
63 | |
64 public FileCheck() | |
65 { | |
17 | 66 // Set general SSH check settings for file checks. |
3 | 67 CheckExitCode = true; |
68 ExitCode = 0; | |
69 } | |
70 | |
17 | 71 /// <summary>Processes the output of the file check command.</summary> |
3 | 72 protected override List<CheckResult> ProcessCommandResult(string output, int exitCode) |
73 { | |
17 | 74 // Check for general SSH failures. |
3 | 75 List<CheckResult> results = base.ProcessCommandResult(output, exitCode); |
17 | 76 // If there was an error running the command, fail immediately. |
4 | 77 if (results.Any(r => r.Failed)) |
3 | 78 return results; |
79 | |
17 | 80 // Make sure there is only a single line of output. |
3 | 81 if (output.Split('\n').Length > 1) |
82 { | |
83 results.Add(Fail("ls output was more than one line: " + output)); | |
84 } | |
85 else | |
86 { | |
17 | 87 // Split the string into tokens on whitespace. |
3 | 88 string[] tokens = output.Split(new char[0], StringSplitOptions.RemoveEmptyEntries); |
89 if (CheckFileSize) | |
90 { | |
17 | 91 // Check file size. |
3 | 92 if (int.TryParse(tokens[4], out int bytes)) |
93 { | |
17 | 94 // Prepare a human-readable message with the comparison results. |
4 | 95 string message = string.Format("File size is {0} {1}", Math.Round(ConvertBytesToSelectedUnits(bytes), 1), FileSizeUnits); |
3 | 96 if ((bytes < FileSize && FileSizeLessThan) || (bytes > FileSize && !FileSizeLessThan)) |
97 results.Add(Pass(message)); | |
98 else | |
99 results.Add(Fail(message)); | |
100 } | |
101 else | |
102 { | |
103 results.Add(Fail("Unable to parse ls output as integer: " + tokens[4])); | |
104 } | |
105 } | |
106 if (CheckDateModified) | |
107 { | |
17 | 108 // Check date modified. |
109 // Tokens[5] contains the date, tokens[6] contains the time. | |
3 | 110 string dateString = tokens[5] + " " + tokens[6]; |
111 if (DateTime.TryParse(dateString, out DateTime modified)) | |
112 { | |
113 string message = string.Format("Last modified date is {0}", modified); | |
17 | 114 // Determine how old the file is. The command output is in the client time zone. |
3 | 115 TimeSpan age = DateTime.Now.Subtract(modified); |
116 double ageCompare = DateModifiedUnits == TimeUnits.Minute ? age.TotalMinutes : | |
117 DateModifiedUnits == TimeUnits.Hour ? age.TotalHours : | |
118 age.TotalDays; | |
119 if ((ageCompare > DateModified && DateModifiedOlderThan) || (ageCompare < DateModified && !DateModifiedOlderThan)) | |
120 results.Add(Pass(message)); | |
121 else | |
122 results.Add(Fail(message)); | |
123 } | |
124 else | |
125 { | |
126 results.Add(Fail("Unable to parse ls output as date: " + dateString)); | |
127 } | |
128 } | |
129 } | |
130 return results; | |
131 } | |
132 | |
17 | 133 /// <summary>Validates file check options.</summary> |
3 | 134 public override string Validate(bool saving = true) |
135 { | |
136 string message = base.Validate(); | |
137 if (File.IsNullOrEmpty()) | |
138 message += "File is required." + Environment.NewLine; | |
139 if (!CheckFileSize && !CheckDateModified) | |
140 message += "At least one check must be enabled." + Environment.NewLine; | |
9 | 141 if (CheckFileSize && FileSize < 0) |
142 message += "File size must be at least 0." + Environment.NewLine; | |
3 | 143 if (CheckDateModified && DateModified <= 0) |
144 message += "Date modified must be greater than 0." + Environment.NewLine; | |
145 return message; | |
146 } | |
4 | 147 |
17 | 148 /// <summary>Converts bytes to a different size unit.</summary> |
149 /// <param name="sizeInBytes">The size to convert in bytes.</param> | |
150 /// <returns>The size in the units used by this file check.</returns> | |
4 | 151 private double ConvertBytesToSelectedUnits(int sizeInBytes) |
152 { | |
153 return sizeInBytes / Math.Pow(1024, (double)FileSizeUnits); | |
154 } | |
155 | |
17 | 156 /// <summary>Converts a size in a different unit to bytes.</summary> |
157 /// <param name="sizeInSelectedUnits">The size to convert in the units used by this file check.</param> | |
158 /// <returns>The size in bytes.</returns> | |
4 | 159 private int ConvertSelectedUnitsToBytes(double sizeInSelectedUnits) |
160 { | |
161 return (int)(sizeInSelectedUnits * Math.Pow(1024, (int)FileSizeUnits)); | |
162 } | |
3 | 163 } |
164 } |