aboutsummaryrefslogtreecommitdiff
path: root/lib/Plugins.Runtime/src/AssemblyWatcher.cs
blob: 09b49dc604a34055e79efc0dbae924c52197b6b9 (plain)
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();
            }
        }
    }
}