Menu

[r4761]: / branches / parsnip / Src / SWAG.UReader.pas  Maximize  Restore  History

Download this file

419 lines (387 with data), 14.9 kB

  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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
{
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at http://mozilla.org/MPL/2.0/
*
* Copyright (C) 2013, Peter Johnson (www.delphidabbler.com).
*
* $Rev$
* $Date$
*
* Implements a class that provides cached access to the SWAG database.
}
unit SWAG.UReader;
interface
uses
// Delphi
SysUtils,
Generics.Collections,
// Project
SWAG.UCommon,
SWAG.USnippetCache,
UExceptions,
Web.USWAGRESTMgr;
type
/// <summary>Class that provides cached access to the online SWAG database.
/// </summary>
/// <remarks>Whenever this class needs to get data from the SWAG database it
/// passes a closure back to the caller that it uses to get the required
/// data. This enables the calling code to display a suitable UI while
/// database accesses take place without having to know how to retrieve the
/// required data.</remarks>
TSWAGReader = class(TObject)
public
type
/// <summary>Type of closure passed back to calling code whenever the
/// SWAG database is to be accessed.</summary>
/// <param name="CallProc">TProc [in] Closure that calling code must call
/// to perform the required database access.</param>
TRESTCallWrapper = reference to procedure (CallProc: TProc);
strict private
const
/// <summary>Maximum size of cache of complete snippets downloaded from
/// the SWAG database.</summary>
MaxSnippetCacheSize = 50;
var
/// <summary>List of all categories in the SWAG database.</summary>
fCategories: TList<TSWAGCategory>;
/// <summary>Map of category IDs onto the snippets that belong to the
/// category.</summary>
/// <remarks>
/// <para>This object is used to cache the snippets for each category to
/// avoid having to download them more than once.</para>
/// <para>Only partial information that summarises each snippet is
/// stored.</para>
/// </remarks>
fSnippetsByCategory: TDictionary<string,TList<TSWAGSnippet>>;
/// <summary>Object that interfaces with the SWAG database REST API.
/// </summary>
fSWAGRESTMgr: TSWAGRESTMgr;
/// <summary>Default wrapper for calls to the online SWAG database.
/// </summary>
/// <remarks>This wrapper is supplied in the constructor and is used
/// unless a different wrapper is specified by the caller in certain
/// method calls.</remarks>
fDefaultCallWrapper: TRESTCallWrapper;
/// <summary>Cache of complete snippets downloaded from the SWAG
/// database.</summary>
fSnippetCache: TSWAGSnippetCache;
/// <summary>Handles the given exception by converting HTTP and web service
/// related exceptions into for informative ESWAGReader exceptions.
/// </summary>
/// <exception>Always raises an exception. ESWAGReader is raised if E is an
/// HTTP or web service exception. Otherwise E is re-raised.</exception>
procedure HandleException(E: Exception);
/// <summary>Downloads all categories in the SWAG database and stores their
/// data in the list Cats.</summary>
procedure DownloadCategories(const Cats: TList<TSWAGCategory>);
/// <summary>Downloads partial (i.e. summary) snippet information for all
/// snippets in the category with ID CatID and stores them in the list
/// SnipList.</summary>
procedure DownloadPartialSnippets(const CatID: string;
const SnipList: TList<TSWAGSnippet>);
/// <summary>Downloads and returns all the data for the snippet with the
/// given ID.</summary>
function DownloadCompleteSnippet(const SnipID: Cardinal): TSWAGSnippet;
/// <summary>Passes a closure back to the caller that it must call when a
/// list of all categories must be downloaded from the SWAG database.
/// </summary>
/// <param name="CallWrapper">TRESTCallWrapper [in] Callback function
/// provided by caller to which the category download closure is passed. If
/// nil then the default callback function is used.</param>
/// <remarks>The fetched categories are cached in fCategories.</remarks>
procedure FetchCategories(CallWrapper: TRESTCallWrapper);
/// <summary>Passes a closure back to the caller that it must call when a
/// the summaries of all snippets in a category is to be downloaded from
/// the SWAG database.</summary>
/// <param name="CatID">string [in] ID of category for which snippet
/// summaries are required.</param>
/// <param name="CallWrapper">TRESTCallWrapper [in] Callback function
/// provided by caller to which the snippets download closure is passed. If
/// nil then the default callback function is used.</param>
/// <remarks>The fetched snippet summaries are cached in
/// fSnippetsByCategory.</remarks>
procedure FetchPartialSnippets(const CatID: string;
CallWrapper: TRESTCallWrapper);
/// <summary>Passes a closure back to the caller that it must call when a
/// complete snippet must be downloaded from the SWAG database.</summary>
/// <param name="SnippetID">Cardinal [in] ID of required snippet.</param>
/// <param name="CallWrapper">TRESTCallWrapper [in] Callback function
/// provided by caller to which the snippet download closure is passed. If
/// nil then the default callback function is used.</param>
/// <returns>TSWAGSnippet. The downloaded snippet.</returns>
function FetchCompleteSnippet(const SnippetID: Cardinal;
CallWrapper: TRESTCallWrapper): TSWAGSnippet;
/// <summary>Passes a closure back to the caller that it must call when a
/// list of all complete snippets must be downloaded from the SWAG
/// database.</summary>
/// <param name="SnipIDs">TList&lt;Cardinal&gt; [in] List of IDs of the
/// required snippets.</param>
/// <param name="Snippets">TList&lt;TSWAGSnippet&gt; [in] Receives the list
/// of downloaded snippets.</param>
/// <param name="CallWrapper">TRESTCallWrapper [in] Callback function
/// provided by caller to which the snippets download closure is passed. If
/// nil then the default callback function is used.</param>
procedure FetchCompleteSnippets(const SnipIDs: TList<Cardinal>;
Snippets: TList<TSWAGSnippet>; CallWrapper: TRESTCallWrapper);
public
/// <summary>Creates a new object instance.</summary>
/// <param name="DefaultRESTCallWrapper">TRESTCallWrapper [in] Callback to
/// be passed a database access closure in all database operations where no
/// specific callback is provided.</param>
constructor Create(const DefaultRESTCallWrapper: TRESTCallWrapper);
/// <summary>Destroys object instance.</summary>
destructor Destroy; override;
/// <summary>Gets all the categories in the SWAG database.</summary>
/// <param name="Cats">TList&lt;TSWAGCategory&gt; [in] Receives the
/// required list of categories.</param>
/// <param name="CallWrapper">TRESTCallWrapper [in] Callback that is called
/// and passed a closure which can download categories from the database.
/// If nil then the default callback passed to the constructor is used.
/// </param>
/// <remarks>The first time this method is called CallWrapper is called to
/// download the categories from the SWAG database. On subsequent calls
/// the categories are read from a cache and CallWrapper is not called.
/// </remarks>
procedure GetCategories(const Cats: TList<TSWAGCategory>;
CallWrapper: TRESTCallWrapper = nil);
/// <summary>Gets summaries of all the snippets containing in a given
/// SWAG database category.</summary>
/// <param name="CatID">string [in] ID of the required category.</param>
/// <param name="Snippets">TList&lt;TSWAGSnippet&gt; [in] Receives the
/// required list of snippet summaries.</param>
/// <param name="CallWrapper">TRESTCallWrapper [in] Callback that is called
/// and passed a closure which can download the required snippet summaries
/// from the database. If nil then the default callback passed to the
/// constructor is used.</param>
/// <remarks>The first time this method is called for any category
/// CallWrapper is be called to download the required snippets. On
/// subsequent calls for that category the snippets summaries are read from
/// a cache and CallWrapper is not called.</remarks>
procedure GetPartialSnippets(const CatID: string;
const Snippets: TList<TSWAGSnippet>; CallWrapper: TRESTCallWrapper = nil);
function GetCompleteSnippet(const ID: Cardinal;
CallWrapper: TRESTCallWrapper = nil): TSWAGSnippet;
procedure GetCompleteSnippets(SnipIDs: TList<Cardinal>;
Snippets: TList<TSWAGSnippet>; CallWrapper: TRESTCallWrapper = nil);
end;
type
ESWAGReader = class(ECodeSnip);
implementation
uses
// Project
UComparers,
UConsts,
Web.UExceptions;
{ TSWAGReader }
constructor TSWAGReader.Create(
const DefaultRESTCallWrapper: TRESTCallWrapper);
begin
inherited Create;
fDefaultCallWrapper := DefaultRESTCallWrapper;
fCategories := TList<TSWAGCategory>.Create;
fSnippetsByCategory := TObjectDictionary<string,TList<TSWAGSnippet>>.Create(
[doOwnsValues], TStringComparator.Create
);
fSnippetCache := TSWAGSnippetCache.Create(MaxSnippetCacheSize);
fSWAGRESTMgr := TSWAGRESTMgr.Create;
end;
destructor TSWAGReader.Destroy;
begin
fSWAGRESTMgr.Free;
fSnippetCache.Free;
fSnippetsByCategory.Free;
fCategories.Free;
inherited;
end;
procedure TSWAGReader.DownloadCategories(const Cats: TList<TSWAGCategory>);
begin
try
fSWAGRESTMgr.GetCategories(Cats);
except
HandleException(ExceptObject as Exception);
end;
end;
function TSWAGReader.DownloadCompleteSnippet(const SnipID: Cardinal):
TSWAGSnippet;
begin
try
fSWAGRESTMgr.GetSnippet(
SnipID,
[
'id', 'category', 'file_name', 'datestamp', 'title', 'author',
'source_code', 'is_document'
],
Result
);
except
HandleException(ExceptObject as Exception);
end;
end;
procedure TSWAGReader.DownloadPartialSnippets(const CatID: string;
const SnipList: TList<TSWAGSnippet>);
begin
try
fSWAGRESTMgr.GetSnippets(CatID, ['id', 'title'], SnipList);
except
HandleException(ExceptObject as Exception);
end;
end;
procedure TSWAGReader.FetchCategories(CallWrapper: TRESTCallWrapper);
begin
fCategories.Clear;
if not Assigned(CallWrapper) then
CallWrapper := fDefaultCallWrapper;
CallWrapper(
procedure
var
CatList: TList<TSWAGCategory>;
begin
CatList := TList<TSWAGCategory>.Create;
try
DownloadCategories(CatList);
fCategories.AddRange(CatList);
finally
CatList.Free;
end
end
);
end;
function TSWAGReader.FetchCompleteSnippet(const SnippetID: Cardinal;
CallWrapper: TRESTCallWrapper): TSWAGSnippet;
var
Snippet: TSWAGSnippet;
begin
if not Assigned(CallWrapper) then
CallWrapper := fDefaultCallWrapper;
CallWrapper(
procedure
begin
Snippet := DownloadCompleteSnippet(SnippetID);
end
);
Result := Snippet;
end;
procedure TSWAGReader.FetchCompleteSnippets(const SnipIDs: TList<Cardinal>;
Snippets: TList<TSWAGSnippet>; CallWrapper: TRESTCallWrapper);
begin
if not Assigned(CallWrapper) then
CallWrapper := fDefaultCallWrapper;
CallWrapper(
procedure
var
SnipID: Cardinal;
Snippet: TSWAGSnippet;
begin
for SnipID in SnipIDs do
begin
Snippet := DownloadCompleteSnippet(SnipID);
Snippets.Add(Snippet);
end;
end
);
end;
procedure TSWAGReader.FetchPartialSnippets(const CatID: string;
CallWrapper: TRESTCallWrapper);
begin
if not Assigned(CallWrapper) then
CallWrapper := fDefaultCallWrapper;
CallWrapper(
procedure
var
SnipList: TList<TSWAGSnippet>;
begin
SnipList := TList<TSWAGSnippet>.Create;
DownloadPartialSnippets(CatID, SnipList);
fSnippetsByCategory.Add(CatID, SnipList);
end
);
end;
procedure TSWAGReader.GetCategories(const Cats: TList<TSWAGCategory>;
CallWrapper: TRESTCallWrapper);
begin
if fCategories.Count = 0 then
FetchCategories(CallWrapper);
Cats.AddRange(fCategories);
end;
function TSWAGReader.GetCompleteSnippet(const ID: Cardinal;
CallWrapper: TRESTCallWrapper): TSWAGSnippet;
begin
if not fSnippetCache.Retrieve(ID, Result) then
begin
Result := FetchCompleteSnippet(ID, CallWrapper);
fSnippetCache.Add(Result);
end;
end;
procedure TSWAGReader.GetCompleteSnippets(SnipIDs: TList<Cardinal>;
Snippets: TList<TSWAGSnippet>; CallWrapper: TRESTCallWrapper);
var
RemoteSnippetIDs: TList<Cardinal>;
DownloadedSnippets: TList<TSWAGSnippet>;
Snippet: TSWAGSnippet;
SnipID: Cardinal;
begin
RemoteSnippetIDs := TList<Cardinal>.Create;
try
for SnipID in SnipIDs do
begin
if fSnippetCache.Retrieve(SnipID, Snippet) then
Snippets.Add(Snippet)
else
RemoteSnippetIDs.Add(SnipID);
end;
if RemoteSnippetIDs.Count > 0 then
begin
DownloadedSnippets := TList<TSWAGSnippet>.Create;
try
FetchCompleteSnippets(
RemoteSnippetIDs, DownloadedSnippets, CallWrapper
);
for Snippet in DownloadedSnippets do
begin
fSnippetCache.Add(Snippet);
Snippets.Add(Snippet);
end;
finally
DownloadedSnippets.Free;
end;
end;
finally
RemoteSnippetIDs.Free;
end;
end;
procedure TSWAGReader.GetPartialSnippets(const CatID: string;
const Snippets: TList<TSWAGSnippet>; CallWrapper: TRESTCallWrapper);
begin
if not fSnippetsByCategory.ContainsKey(CatID) then
FetchPartialSnippets(CatID, CallWrapper);
Snippets.AddRange(fSnippetsByCategory[CatID]);
end;
procedure TSWAGReader.HandleException(E: Exception);
resourcestring
sHTTPError = 'The SWAG web server returned the following error:'
+ EOL2
+ '%s';
sWebTransmissionError = 'The data received from the SWAG web service was '
+ 'corrupt';
sWebServiceFailure = 'The following error was detected in the response '
+ 'received from the SWAG web service:'
+ EOL2
+ '%s';
sWebServiceError = 'The SWAG web service reported the following error:'
+ EOL2
+ '%s';
begin
if E is EHTTPError then
raise ESWAGReader.CreateFmt(sHTTPError, [E.Message]);
if E is EWebConnectionError then
raise ESWAGReader.Create(E);
if E is EWebTransmissionError then
raise ESWAGReader.Create(sWebTransmissionError);
if E is EWebServiceFailure then
raise ESWAGReader.CreateFmt(sWebServiceFailure, [E.Message]);
if E is EWebServiceError then
raise ESWAGReader.CreateFmt(sWebServiceError, [E.Message]);
raise E;
end;
end.
Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.