Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
StorageService.cs
1using System.Diagnostics;
2using System.Text;
3using Waher.Events;
9
11{
12 [Singleton]
13 internal sealed class StorageService : IStorageService, IDisposableAsync
14 {
15 private readonly LinkedList<TaskCompletionSource<bool>> tasksWaiting = new();
16 private readonly string dataFolder;
17 private FilesProvider? databaseProvider;
18 private PersistedEventLog? persistedEventLog;
19 private bool? initialized = null;
20 private bool started = false;
21
25 public StorageService()
26 {
27 string AppDataFolder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
28 this.dataFolder = Path.Combine(AppDataFolder, "Data");
29
30#if DEBUG && WINDOWS
31 string name = Environment.GetEnvironmentVariable("NEUROACCESS_DATA_PROFILE") ?? "";
32 this.dataFolder = Path.Combine(AppDataFolder, "Data" + name);
33#endif
34 }
35
39 public string DataFolder => this.dataFolder;
40
41 #region LifeCycle management
42
44 public async Task Init(CancellationToken? cancellationToken)
45 {
46 lock (this.tasksWaiting)
47 {
48 if (this.started)
49 return;
50
51 this.started = true;
52 }
53
54 try
55 {
57 this.databaseProvider = Database.Provider as FilesProvider;
58
59 if (this.databaseProvider is null)
60 {
61 this.databaseProvider = await this.CreateDatabaseFile();
62
63 await this.databaseProvider.RepairIfInproperShutdown(string.Empty);
64 await this.databaseProvider.Start();
65 }
66
67 if (this.databaseProvider is not null)
68 {
69 Database.Register(this.databaseProvider, false);
70 Log.Register(this.persistedEventLog = new PersistedEventLog(90));
71 this.InitDone(true);
72 return;
73 }
74 }
75 catch (Exception e1)
76 {
77 e1 = Log.UnnestException(e1);
78 ServiceRef.LogService.LogException(e1);
79 }
80
82 /* On iOS the UI is not initialized at this point, need to find another solution
83 if (await ServiceRef.UiSerializer.DisplayAlert(ServiceRef.Localizer[nameof(AppResources.DatabaseIssue"], ServiceRef.Localizer[nameof(AppResources.DatabaseCorruptInfoText"], ServiceRef.Localizer[nameof(AppResources.RepairAndContinue"], ServiceRef.Localizer[nameof(AppResources.ContinueAnyway"]))
84 */
85 //TODO: when UI is ready, show an alert that the database was reset due to unrecoverable error
86 //TODO: say to close the application in a controlled manner
87 {
88 try
89 {
90 Directory.Delete(this.dataFolder, true);
91
92 this.databaseProvider = await this.CreateDatabaseFile();
93
94 await this.databaseProvider.RepairIfInproperShutdown(string.Empty);
95
96 await this.databaseProvider.Start();
97
99 {
100 Database.Register(this.databaseProvider, false);
101 Log.Register(this.persistedEventLog = new PersistedEventLog(90));
102 this.InitDone(true);
103 return;
104 }
105 }
106 catch (Exception e3)
107 {
108 e3 = Log.UnnestException(e3);
109 ServiceRef.LogService.LogException(e3);
110
111 await App.StopAsync();
112 /*
113 Thread?.NewState("UI");
114 await ServiceRef.UiSerializer.DisplayAlert(ServiceRef.Localizer[nameof(AppResources.DatabaseIssue"], ServiceRef.Localizer[nameof(AppResources.DatabaseRepairFailedInfoText"], ServiceRef.Localizer[nameof(AppResources.Ok"]);
115 */
116 }
117 }
118
119 this.InitDone(false);
120 }
121
122 private void InitDone(bool Result)
123 {
124 lock (this.tasksWaiting)
125 {
126 this.initialized = Result;
127
128 foreach (TaskCompletionSource<bool> Wait in this.tasksWaiting)
129 Wait.TrySetResult(Result);
130
131 this.tasksWaiting.Clear();
132 }
133 }
134
136 public Task<bool> WaitInitDone()
137 {
138 lock (this.tasksWaiting)
139 {
140 if (this.initialized.HasValue)
141 return Task.FromResult<bool>(this.initialized.Value);
142
143 TaskCompletionSource<bool> Wait = new();
144 this.tasksWaiting.AddLast(Wait);
145
146 return Wait.Task;
147 }
148 }
149
151 public async Task Shutdown()
152 {
153 lock (this.tasksWaiting)
154 {
155 this.initialized = null;
156 this.started = false;
157 }
158
159 try
160 {
161 if (this.persistedEventLog is not null)
162 {
163 Log.Unregister(this.persistedEventLog);
164 await this.persistedEventLog.DisposeAsync();
165 this.persistedEventLog = null;
166 }
167
168 if (this.databaseProvider is not null)
169 {
171 await this.databaseProvider.Flush();
172 await this.databaseProvider.Stop();
173 this.databaseProvider = null;
174 }
175 }
176 catch (Exception ex)
177 {
178 ServiceRef.LogService.LogException(ex);
179 }
180 }
181
182 private Task<FilesProvider> CreateDatabaseFile()
183 {
184 FilesProvider.AsyncFileIo = true; // Asynchronous file I/O induces a long delay during startup on mobile platforms. Why??
185 return FilesProvider.CreateAsync(this.dataFolder, "Default", 8192, 10000, 8192, Encoding.UTF8,
186 (int)Constants.Timeouts.Database.TotalMilliseconds, ServiceRef.CryptoService.GetCustomKey);
187 }
188
192 [Obsolete("Use DisposeAsync() instead.")]
193 public void Dispose()
194 {
195 this.DisposeAsync().Wait();
196 }
197
201 public async Task DisposeAsync()
202 {
203 if (this.persistedEventLog is not null)
204 {
205 await this.persistedEventLog.DisposeAsync();
206 this.persistedEventLog = null;
207 }
208
209 if (this.databaseProvider is not null)
210 {
211 await this.databaseProvider.DisposeAsync();
212 this.databaseProvider = null;
213 }
214 }
215
216 #endregion
217
218 public async Task Insert(object obj)
219 {
220 await Database.Insert(obj);
221 await Database.Provider.Flush();
222 }
223
224 public async Task Update(object obj)
225 {
226 await Database.Update(obj);
227 await Database.Provider.Flush();
228 }
229
230 public Task<T> FindFirstDeleteRest<T>() where T : class
231 {
232 return Database.FindFirstDeleteRest<T>();
233 }
234
235 public Task<T> FindFirstIgnoreRest<T>() where T : class
236 {
237 return Database.FindFirstIgnoreRest<T>();
238 }
239
240 public Task Export(IDatabaseExport exportOutput)
241 {
242 return Database.Export(exportOutput);
243 }
244
248 public void FlagForRepair()
249 {
250 this.DeleteFile("Start.txt");
251 this.DeleteFile("Stop.txt");
252 }
253
254 private void DeleteFile(string FileName)
255 {
256 try
257 {
258 FileName = Path.Combine(this.dataFolder, FileName);
259
260 if (File.Exists(FileName))
261 File.Delete(FileName);
262 }
263 catch (Exception)
264 {
265 // Ignore, to avoid infinite loops if event log has an inconsistency.
266 }
267 }
268 }
269}
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
static void Register(IEventSink EventSink)
Registers an event sink with the event log. Call Unregister(IEventSink) to unregister it,...
Definition: Log.cs:30
static Exception UnnestException(Exception Exception)
Unnests an exception, to extract the relevant inner exception.
Definition: Log.cs:828
static bool Unregister(IEventSink EventSink)
Unregisters an event sink from the event log.
Definition: Log.cs:47
Creates an even sink that stores incoming (logged) events in the local object database,...
override Task DisposeAsync()
IDisposableAsync.DisposeAsync()
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs:21
static bool HasProvider
If a database provider is registered.
Definition: Database.cs:81
static void Register(IDatabaseProvider DatabaseProvider)
Registers a database provider for use from the static Database class, throughout the lifetime of the ...
Definition: Database.cs:33
static IDatabaseProvider Provider
Registered database provider.
Definition: Database.cs:59
static Task< bool > Export(IDatabaseExport Output)
Performs an export of the database.
Definition: Database.cs:1893
static async Task Update(object Object)
Updates an object in the database.
Definition: Database.cs:1211
static async Task Insert(object Object)
Inserts an object into the default collection of the database.
Definition: Database.cs:97
Persists objects into binary files.
async Task DisposeAsync()
IDisposableAsync.DisposeAsync
async Task Stop()
Called when processing ends.
async Task Flush()
Persists any pending changes.
async Task< string[]> RepairIfInproperShutdown(string XsltPath)
Checks if the database needs repairing. This is done by checking the last start and stop timetamps to...
Task Start()
Called when processing starts.
static Task< FilesProvider > CreateAsync(string Folder, string DefaultCollectionName, int BlockSize, int BlocksInCache, int BlobBlockSize, Encoding Encoding, int TimeoutMilliseconds, CustomKeyHandler CustomKeyMethod)
Persists objects into binary files.
Interface for asynchronously disposable objects.
Task Flush()
Persists any pending changes.