1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
|
/*
* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Runtime
* File: AssemblyWatcher.cs
*
* AssemblyWatcher.cs is part of VNLib.Plugins.Runtime which is part
* of the larger VNLib collection of libraries and utilities.
*
* VNLib.Plugins.Runtime is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 2 of the License,
* or (at your option) any later version.
*
* VNLib.Plugins.Runtime is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with VNLib.Plugins.Runtime. If not, see http://www.gnu.org/licenses/.
*/
using System.IO;
using System.Threading;
using System.Collections.Generic;
using VNLib.Utils;
using VNLib.Utils.Extensions;
namespace VNLib.Plugins.Runtime
{
internal sealed class AssemblyWatcher : IPluginAssemblyWatcher
{
private readonly object _lock = new ();
private readonly Dictionary<IPluginReloadEventHandler, AsmFileWatcher> _watchers;
public AssemblyWatcher()
{
_watchers = new();
}
///<inheritdoc/>
public void StopWatching(IPluginReloadEventHandler handler)
{
lock (_lock)
{
//Find old watcher by its handler, then dispose it
if (_watchers.Remove(handler, out AsmFileWatcher? watcher))
{
//dispose the watcher
watcher.Dispose();
}
}
}
///<inheritdoc/>
public void WatchAssembly(IPluginReloadEventHandler handler, IPluginAssemblyLoader loader)
{
lock(_lock)
{
if(_watchers.Remove(handler, out AsmFileWatcher? watcher))
{
//dispose the watcher
watcher.Dispose();
}
//Queue up a new watcher
watcher = new(loader, handler);
//Store watcher
_watchers.Add(handler, watcher);
}
}
private sealed class AsmFileWatcher : VnDisposeable
{
public IPluginReloadEventHandler Handler { get; }
private readonly IPluginAssemblyLoader _loaderSource;
private readonly Timer _delayTimer;
private readonly FileSystemWatcher _watcher;
private bool _pause;
public AsmFileWatcher(IPluginAssemblyLoader LoaderSource, IPluginReloadEventHandler handler)
{
Handler = handler;
_loaderSource = LoaderSource;
string dir = Path.GetDirectoryName(LoaderSource.Config.AssemblyFile)!;
//Configure watcher to notify only when the assembly file changes
_watcher = new FileSystemWatcher(dir)
{
Filter = "*.dll",
EnableRaisingEvents = false,
IncludeSubdirectories = true,
NotifyFilter = NotifyFilters.LastWrite,
};
//Configure listener
_watcher.Changed += OnFileChanged;
_watcher.Created += OnFileChanged;
_watcher.EnableRaisingEvents = true;
//setup delay timer to wait on the config
_delayTimer = new(OnTimeout, this, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan);
}
void OnFileChanged(object sender, FileSystemEventArgs e)
{
//if were already waiting to process an event, we dont need to stage another
if (_pause)
{
return;
}
//Set pause flag
_pause = true;
//Restart the timer to trigger reload event on elapsed
_delayTimer.Restart(_loaderSource.Config.ReloadDelay);
}
private void OnTimeout(object? state)
{
_delayTimer.Stop();
//Fire event, let exception crash app
Handler.OnPluginUnloaded(_loaderSource);
//Clear pause flag
_pause = false;
}
protected override void Free()
{
_delayTimer.Dispose();
//Detach event handler and dispose watcher
_watcher.Changed -= OnFileChanged;
_watcher.Dispose();
}
}
}
}
|