• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

djsuperchief / Kyameru / 8401706343

23 Mar 2024 12:18PM UTC coverage: 89.113% (-7.0%) from 96.15%
8401706343

push

github

web-flow
Merge pull request #104 from djsuperchief/103-release-config

103 release config

308 of 387 branches covered (79.59%)

Branch coverage included in aggregate %.

1812 of 1992 relevant lines covered (90.96%)

21.98 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.44
/source/components/Kyameru.Component.File/Implementation/FileWatcher.cs
1
using System;
2
using System.Collections.Generic;
3
using System.IO;
4
using System.Linq;
5
using System.Threading;
6
using System.Threading.Tasks;
7
using Kyameru.Component.File.Utilities;
8
using Kyameru.Core;
9
using Kyameru.Core.Entities;
10

11
using Microsoft.Extensions.Logging;
12

13
namespace Kyameru.Component.File
14
{
15
    /// <summary>
16
    /// From component.
17
    /// </summary>
18
    public class FileWatcher : IFileWatcher
19
    {
20
        /// <summary>
21
        /// List of delegates to perform actions.
22
        /// </summary>
23
        private readonly Dictionary<string, Action> fswSetup = new Dictionary<string, Action>();
11✔
24

25
        /// <summary>
26
        /// Valid headers.
27
        /// </summary>
28
        private readonly Dictionary<string, string> config;
29

30
        /// <summary>
31
        /// List of directories to ignore.
32
        /// </summary>
33
        private readonly string[] directoriesToIgnore;
34

35
        /// <summary>
36
        /// List of strings that should be ignored as part of the file name and extension.
37
        /// </summary>
38
        private readonly string[] stringsToIgnore;
39

40
        /// <summary>
41
        /// Value indicating whether an initial scan will occur of the directory.
42
        /// </summary>
43
        private bool willScan = false;
11✔
44

45
        /// <summary>
46
        /// File system watcher.
47
        /// </summary>
48
        private IFileSystemWatcher fsw;
49

50
        /// <summary>
51
        /// Value indicating whether the route starts as async.
52
        /// </summary>
53
        private bool runAsync = false;
11✔
54

55
        /// <summary>
56
        /// Initializes a new instance of the <see cref="FileWatcher"/> class.
57
        /// </summary>
58
        /// <param name="headers">Incoming Headers.</param>
59
        public FileWatcher(Dictionary<string, string> headers, IFileSystemWatcher fileSystemWatcher)
11✔
60
        {
11✔
61
            this.config = headers.ToFromConfig();
11✔
62
            this.SetupInternalActions();
11✔
63
            this.Setup();
11✔
64
            this.fsw = fileSystemWatcher;
9✔
65
            this.DetermineScan(this.config["InitialScan"]);
9✔
66
            this.directoriesToIgnore = this.config["Ignore"].SplitPiped();
9✔
67
            this.stringsToIgnore = this.config["IgnoreStrings"].SplitPiped();
9✔
68
        }
9✔
69

70
        /// <summary>
71
        /// Event raised when file picked up.
72
        /// </summary>
73
        public event EventHandler<Routable> OnAction;
74

75
        public event AsyncEventHandler<RoutableEventData> OnActionAsync;
76

77
        /// <summary>
78
        /// Event raised when needing to log.
79
        /// </summary>
80
        public event EventHandler<Log> OnLog;
81

82
        /// <summary>
83
        /// Sets up the component.
84
        /// </summary>
85
        public void Setup()
86
        {
19✔
87
            this.VerifyArguments();
19✔
88
        }
17✔
89

90
        /// <summary>
91
        /// Starts the component.
92
        /// </summary>
93
        public void Start()
94
        {
8✔
95
            this.SetupFsw();
8✔
96
            this.ScanFiles();
8✔
97
            this.SetupSubDirectories();
8✔
98
            this.fsw.EnableRaisingEvents = true;
8✔
99
            this.fsw.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
8✔
100
           | NotifyFilters.FileName | NotifyFilters.DirectoryName;
8✔
101
            foreach (string item in this.config["Notifications"].Split(','))
40✔
102
            {
8✔
103
                if (this.fswSetup.ContainsKey(item))
8✔
104
                {
8✔
105
                    this.fswSetup[item]();
8✔
106
                }
8✔
107
            }
8✔
108
        }
8✔
109

110
        /// <summary>
111
        /// Stops the component.
112
        /// </summary>
113
        public void Stop()
114
        {
2✔
115
            this.fsw.Dispose();
2✔
116
        }
2✔
117

118
        public async Task StartAsync(CancellationToken cancellationToken)
119
        {
2✔
120
            runAsync = true;
2✔
121
            Start();
2✔
122
            await Task.CompletedTask;
2✔
123
        }
2✔
124

125
        public async Task StopAsync(CancellationToken cancellationToken)
126
        {
1✔
127
            Stop();
1✔
128
            await Task.CompletedTask;
1✔
129
        }
1✔
130

131
        /// <summary>
132
        /// Sets up the file watcher.
133
        /// </summary>
134
        /// <returns>Returns a new <see cref="FileSystemWatcher"/>.</returns>
135
        private void SetupFsw()
136
        {
8✔
137
            this.fsw.Path = this.config["Target"];
8✔
138
            this.fsw.Filter = this.config["Filter"];
8✔
139
        }
8✔
140

141
        /// <summary>
142
        /// Sets up sub directories.
143
        /// </summary>
144
        private void SetupSubDirectories()
145
        {
8✔
146
            // Optional but always supplied.
147
            this.fsw.IncludeSubdirectories = bool.Parse(this.config["SubDirectories"]);
8✔
148
        }
8✔
149

150
        /// <summary>
151
        /// Abstraction for logging event.
152
        /// </summary>
153
        /// <param name="logLevel">Log level to raise.</param>
154
        /// <param name="message">Log message.</param>
155
        /// <param name="exception">Log exception.</param>
156
        private void Log(LogLevel logLevel, string message, Exception exception = null)
157
        {
91✔
158
            this.OnLog?.Invoke(this, new Core.Entities.Log(logLevel, message, exception));
91!
159
        }
91✔
160

161
        /// <summary>
162
        /// Verifies the setup.
163
        /// </summary>
164
        private void VerifyArguments()
165
        {
19✔
166
            if (this.IsConfigBlank("Target"))
19✔
167
            {
1✔
168
                this.Log(LogLevel.Error, Resources.ERROR_TARGET_UNSPECIFIED);
1✔
169
                throw new ArgumentException(Resources.ERROR_TARGET_UNSPECIFIED);
1✔
170
            }
171

172
            if (this.IsConfigBlank("Notifications"))
18✔
173
            {
1✔
174
                this.Log(LogLevel.Error, Resources.ERROR_NOTIFICATIONS_UNSPECIFIED);
1✔
175
                throw new ArgumentException(Resources.ERROR_NOTIFICATIONS_UNSPECIFIED);
1✔
176
            }
177
        }
17✔
178

179
        private bool IsConfigBlank(string key)
180
        {
37✔
181
            return !this.config.ContainsKey(key) || string.IsNullOrWhiteSpace(this.config[key]);
37✔
182
        }
37✔
183

184
        /// <summary>
185
        /// Sets up internal delegates.
186
        /// </summary>
187
        private void SetupInternalActions()
188
        {
11✔
189
            this.fswSetup.Add("Created", this.AddCreated);
11✔
190
            this.fswSetup.Add("Changed", this.AddChanged);
11✔
191
            this.fswSetup.Add("Renamed", this.AddRenamed);
11✔
192
        }
11✔
193

194
        /// <summary>
195
        /// Add renamed event.
196
        /// </summary>
197
        private void AddRenamed()
198
        {
×
199
            this.fsw.Renamed += this.Fsw_Renamed;
×
200
        }
×
201

202
        /// <summary>
203
        /// Raised when file is renamed.
204
        /// </summary>
205
        /// <param name="sender">Class sending event.</param>
206
        /// <param name="e">Event arguments.</param>
207
        private void Fsw_Renamed(object sender, RenamedEventArgs e)
208
        {
×
209
            this.CreateMessage("Rename", e.FullPath);
×
210
        }
×
211

212
        /// <summary>
213
        /// Adds changed event.
214
        /// </summary>
215
        private void AddChanged()
216
        {
6✔
217
            this.fsw.Changed += this.Fsw_Changed;
6✔
218
        }
6✔
219

220
        /// <summary>
221
        /// Raised when file is changed.
222
        /// </summary>
223
        /// <param name="sender">Class sending event.</param>
224
        /// <param name="e">Event arguments.</param>
225
        private void Fsw_Changed(object sender, FileSystemEventArgs e)
226
        {
2✔
227
            this.CreateMessage("Changed", e.FullPath);
2✔
228
        }
2✔
229

230
        /// <summary>
231
        /// Adds created event.
232
        /// </summary>
233
        private void AddCreated()
234
        {
2✔
235
            this.fsw.Created += this.Fsw_Created;
2✔
236
        }
2✔
237

238
        /// <summary>
239
        /// Raised when file is created.
240
        /// </summary>
241
        /// <param name="sender">Class sending event.</param>
242
        /// <param name="e">Event arguments.</param>
243
        private void Fsw_Created(object sender, FileSystemEventArgs e)
244
        {
2✔
245
            this.CreateMessage("Created", e.FullPath);
2✔
246
        }
2✔
247

248
        /// <summary>
249
        /// Creates a message to start the process.
250
        /// </summary>
251
        /// <param name="method">Method used to raise event.</param>
252
        /// <param name="sourceFile">Source file found.</param>
253
        private void CreateMessage(string method, string sourceFile)
254
        {
76✔
255
            try
256
            {
76✔
257
                if (this.IsProcessable(sourceFile))
76✔
258
                {
74✔
259
                    FileInfo info = new FileInfo(sourceFile);
74✔
260
                    sourceFile = sourceFile.Replace("\\", "/");
74✔
261
                    Dictionary<string, string> headers = new Dictionary<string, string>();
74✔
262
                    headers.Add("&SourceDirectory", System.IO.Path.GetDirectoryName(sourceFile));
74✔
263
                    headers.Add("SourceFile", System.IO.Path.GetFileName(sourceFile));
74✔
264
                    headers.Add("&FullSource", sourceFile);
74✔
265
                    headers.Add("&DateCreated", info.CreationTimeUtc.ToLongDateString());
74✔
266
                    headers.Add("Readonly", info.IsReadOnly.ToString());
74✔
267
                    headers.Add("Method", method);
74✔
268
                    headers.Add("DataType", "Byte");
74✔
269
                    Routable dataItem = new Routable(headers, System.IO.File.ReadAllBytes(sourceFile));
74✔
270
                    if (runAsync)
74✔
271
                    {
26✔
272
                        var token = new CancellationTokenSource();
26✔
273
                        // fire forget and move on
274
                        _ = Task.Factory.StartNew(async () =>
26✔
275
                        {
26✔
276
                            var routableEventData = new RoutableEventData(dataItem,
26✔
277
                                token.Token);
26✔
278
                            if (this.OnActionAsync != null)
26✔
279
                            {
26✔
280
                                await this.OnActionAsync?.Invoke(this, routableEventData);    
26✔
281
                            }
26✔
282
                        }, token.Token);
52✔
283
                    }
26✔
284
                    else
285
                    {
48✔
286
                        this.OnAction?.Invoke(this, dataItem);
48!
287
                    }
48✔
288
                }
74✔
289
            }
76✔
290
            catch (Exception ex)
×
291
            {
×
292
                throw new Kyameru.Core.Exceptions.ComponentException("Error creating message, see inner exception.", ex);
×
293
            }
294
        }
76✔
295

296
        /// <summary>
297
        /// Determine if we should scan the directory.
298
        /// </summary>
299
        /// <param name="v"></param>
300
        /// <returns></returns>
301
        private void DetermineScan(string header)
302
        {
9✔
303
            if (bool.TryParse(header, out this.willScan))
9✔
304
            {
9✔
305
                this.Log(LogLevel.Debug, "Scanning directory Enabled");
9✔
306
            }
9✔
307
        }
9✔
308

309
        private void ScanFiles()
310
        {
8✔
311
            this.Log(LogLevel.Debug, "Scanning directories");
8✔
312
            SearchOption searchOption = SearchOption.AllDirectories;
8✔
313
            if (!bool.Parse(this.config["SubDirectories"]))
8✔
314
            {
1✔
315
                searchOption = SearchOption.TopDirectoryOnly;
1✔
316
            }
1✔
317
            string[] files = Directory.GetFiles(this.config["Target"], this.config["Filter"], searchOption);
8✔
318
            if (files.Any())
8✔
319
            {
8✔
320
                for (int i = 0; i < files.Count(); i++)
160✔
321
                {
72✔
322
                    this.Log(LogLevel.Information, $"Checking file {files[i]}");
72✔
323
                    this.CreateMessage("Scanned", files[i]);
72✔
324
                }
72✔
325
            }
8✔
326
        }
8✔
327

328
        private bool IsProcessable(string file)
329
        {
76✔
330
            bool response = false;
76✔
331
            if (System.IO.File.Exists(file))
76✔
332
            {
76✔
333
                response = true;
76✔
334
                string toCheck = file.ToLower();
76✔
335

336
                if (this.ContainsIgnoreDirectories(file) || this.stringsToIgnore.Any(x => toCheck.Contains(x)))
77✔
337
                {
2✔
338
                    response = false;
2✔
339
                }
2✔
340
            }
76✔
341

342
            return response;
76✔
343
        }
76✔
344

345
        private bool ContainsIgnoreDirectories(string file)
346
        {
76✔
347
            string[] folders = file.ToLower().Split(Path.DirectorySeparatorChar);
76✔
348
            return folders.Select(x => x).Intersect(this.directoriesToIgnore).Any();
617✔
349
        }
76✔
350
    }
351
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc