Quick.MemoryCache.pas 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765
  1. { ***************************************************************************
  2. Copyright (c) 2016-2021 Kike Pérez
  3. Unit : Quick.MemoryCache
  4. Description : Cache objects with expiration control
  5. Author : Kike Pérez
  6. Version : 1.0
  7. Created : 14/07/2019
  8. Modified : 17/05/2021
  9. This file is part of QuickLib: https://github.com/exilon/QuickLib
  10. ***************************************************************************
  11. Licensed under the Apache License, Version 2.0 (the "License");
  12. you may not use this file except in compliance with the License.
  13. You may obtain a copy of the License at
  14. http://www.apache.org/licenses/LICENSE-2.0
  15. Unless required by applicable law or agreed to in writing, software
  16. distributed under the License is distributed on an "AS IS" BASIS,
  17. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18. See the License for the specific language governing permissions and
  19. limitations under the License.
  20. *************************************************************************** }
  21. unit Quick.MemoryCache;
  22. {$i QuickLib.inc}
  23. interface
  24. uses
  25. System.SysUtils,
  26. System.Generics.Collections,
  27. System.DateUtils,
  28. System.TypInfo,
  29. RTTI,
  30. Quick.Commons,
  31. Quick.Value,
  32. Quick.Threads,
  33. Quick.Cache.Intf,
  34. Quick.MemoryCache.Types,
  35. Quick.MemoryCache.Serializer.Json,
  36. Quick.MemoryCache.Compressor.GZip;
  37. type
  38. TCacheFlushedEvent = reference to procedure(aRemovedEntries : Integer);
  39. TBeginPurgerJobEvent = reference to procedure;
  40. TEndPurgerJobEvent = reference to procedure(aPurgedEntries : Integer);
  41. TPurgerJobErrorEvent = reference to procedure(const aErrorMsg : string);
  42. IMemoryCache<T> = interface
  43. ['{57927AD7-C993-4C3C-B552-43A39F99E73A}']
  44. function GetCompression: Boolean;
  45. procedure SetCompression(const Value: Boolean);
  46. function GetCachedObjects: Integer;
  47. function GetCacheSize: Integer;
  48. procedure SetOnBeginPurgerJob(const Value: TBeginPurgerJobEvent);
  49. procedure SetOnCacheFlushed(const Value: TCacheFlushedEvent);
  50. procedure SetOnEndPurgerJob(const Value: TEndPurgerJobEvent);
  51. procedure SetOnPurgerJobError(const Value: TPurgerJobErrorEvent);
  52. property Compression : Boolean read GetCompression write SetCompression;
  53. property CachedObjects : Integer read GetCachedObjects;
  54. property CacheSize : Integer read GetCacheSize;
  55. property OnCacheFlushed : TCacheFlushedEvent write SetOnCacheFlushed;
  56. property OnBeginPurgerJob : TBeginPurgerJobEvent write SetOnBeginPurgerJob;
  57. property OnEndPurgerJob : TEndPurgerJobEvent write SetOnEndPurgerJob;
  58. property OnPurgeJobError : TPurgerJobErrorEvent write SetOnPurgerJobError;
  59. procedure SetValue(const aKey : string; aValue : T; aExpirationMillisecons : Integer = 0); overload;
  60. procedure SetValue(const aKey : string; aValue : T; aExpirationDate : TDateTime); overload;
  61. function GetValue(const aKey : string) : T;
  62. function TryGetValue(const aKey : string; out aValue : T) : Boolean;
  63. procedure RemoveValue(const aKey : string);
  64. procedure Refresh(const aKey: string; aExpirationMilliseconds : Integer);
  65. procedure Flush;
  66. end;
  67. IMemoryCache = interface(ICache)
  68. ['{F109AE78-43D7-4983-B8ED-52A41533EEED}']
  69. function GetCompression: Boolean;
  70. procedure SetCompression(const Value: Boolean);
  71. function GetCachedObjects: Integer;
  72. function GetCacheSize: Integer;
  73. procedure SetOnBeginPurgerJob(const Value: TBeginPurgerJobEvent);
  74. procedure SetOnCacheFlushed(const Value: TCacheFlushedEvent);
  75. procedure SetOnEndPurgerJob(const Value: TEndPurgerJobEvent);
  76. procedure SetOnPurgerJobError(const Value: TPurgerJobErrorEvent);
  77. property Compression : Boolean read GetCompression write SetCompression;
  78. property CachedObjects : Integer read GetCachedObjects;
  79. property CacheSize : Integer read GetCacheSize;
  80. property OnCacheFlushed : TCacheFlushedEvent write SetOnCacheFlushed;
  81. property OnBeginPurgerJob : TBeginPurgerJobEvent write SetOnBeginPurgerJob;
  82. property OnEndPurgerJob : TEndPurgerJobEvent write SetOnEndPurgerJob;
  83. property OnPurgeJobError : TPurgerJobErrorEvent write SetOnPurgerJobError;
  84. procedure SetValue(const aKey : string; aValue : TObject; aExpirationMilliseconds : Integer = 0); overload;
  85. procedure SetValue(const aKey : string; aValue : TObject; aExpirationDate : TDateTime); overload;
  86. procedure SetValue(const aKey, aValue : string; aExpirationMilliseconds : Integer = 0); overload;
  87. procedure SetValue(const aKey, aValue : string; aExpirationDate : TDateTime); overload;
  88. procedure SetValue(const aKey : string; aValue : TArray<string>; aExpirationMilliseconds : Integer = 0); overload;
  89. procedure SetValue(const aKey : string; aValue : TArray<string>; aExpirationDate : TDateTime); overload;
  90. procedure SetValue(const aKey : string; aValue : TArray<TObject>; aExpirationMilliseconds : Integer = 0); overload;
  91. procedure SetValue(const aKey : string; aValue : TArray<TObject>; aExpirationDate : TDateTime); overload;
  92. function GetValue(const aKey : string) : string; overload;
  93. function TryGetValue(const aKey : string; aValue : TObject) : Boolean; overload;
  94. function TryGetValue(const aKey : string; out aValue : string) : Boolean; overload;
  95. function TryGetValue(const aKey : string; out aValue : TArray<string>) : Boolean; overload;
  96. function TryGetValue(const aKey : string; out aValue : TArray<TObject>) : Boolean; overload;
  97. procedure RemoveValue(const aKey : string);
  98. procedure Refresh(const aKey: string; aExpirationMilliseconds : Integer);
  99. procedure Flush;
  100. end;
  101. TCacheEntry = class(TInterfacedObject,ICacheEntry)
  102. private
  103. fCreationDate : TDateTime;
  104. fExpiration : Cardinal;
  105. fExpirationDate : TDateTime;
  106. fCompression : Boolean;
  107. fCompressor : ICacheCompressor;
  108. fData : string;
  109. fIsCompressed : Boolean;
  110. function GetCreationDate: TDateTime;
  111. function GetData: string;
  112. function GetExpiration: Cardinal;
  113. procedure SetCreationDate(const Value: TDateTime);
  114. procedure SetData(const Value: string);
  115. procedure SetExpiration(aMilliseconds : Cardinal);
  116. function GetExpirationDate: TDateTime;
  117. procedure SetExpirationDate(const Value: TDateTime);
  118. public
  119. constructor Create(aCompression : Boolean; aCacheCompressor : ICacheCompressor);
  120. property CreationDate : TDateTime read GetCreationDate write SetCreationDate;
  121. property Expiration : Cardinal read GetExpiration write SetExpiration;
  122. property ExpirationDate : TDateTime read GetExpirationDate write SetExpirationDate;
  123. property Data : string read GetData write SetData;
  124. function Size : Integer;
  125. function IsExpired : Boolean;
  126. end;
  127. TMemoryCacheBase = class(TInterfacedObject)
  128. private
  129. fPurgerInterval : Integer;
  130. fMaxSize : Integer;
  131. fCachedObjects : Integer;
  132. fCacheSize : Integer;
  133. fCompression : Boolean;
  134. fLock : TMultiReadExclusiveWriteSynchronizer;
  135. fCacheJobs : TScheduledTasks;
  136. fOnCacheFlushed : TCacheFlushedEvent;
  137. fOnPurgerJobError : TPurgerJobErrorEvent;
  138. fOnBeginPurgerJob : TBeginPurgerJobEvent;
  139. fOnEndPurgerJob : TEndPurgerJobEvent;
  140. procedure CreatePurgerJobs;
  141. procedure RemoveExpiredCacheEntries; virtual;
  142. procedure SetPurgerInterval(const Value: Integer);
  143. protected
  144. fItems : TDictionary<string,ICacheEntry>;
  145. fSerializer : ICacheSerializer;
  146. fCompressor : ICacheCompressor;
  147. function GetCachedObjects: Integer;
  148. function GetCacheSize: Integer;
  149. procedure SetOnBeginPurgerJob(const Value: TBeginPurgerJobEvent);
  150. procedure SetOnCacheFlushed(const Value: TCacheFlushedEvent);
  151. procedure SetOnEndPurgerJob(const Value: TEndPurgerJobEvent);
  152. procedure SetOnPurgerJobError(const Value: TPurgerJobErrorEvent);
  153. function GetCompression: Boolean;
  154. procedure SetCompression(const Value: Boolean);
  155. public
  156. constructor Create(aPurgerInterval : Integer = 20; aCacheSerializer : ICacheSerializer = nil; aCacheCompressor : ICacheCompressor = nil); virtual;
  157. destructor Destroy; override;
  158. property MaxSize : Integer read fMaxSize write fMaxSize;
  159. property PurgerInterval : Integer read fPurgerInterval;
  160. property Compression : Boolean read GetCompression write SetCompression;
  161. property CachedObjects : Integer read GetCachedObjects;
  162. property CacheSize : Integer read GetCacheSize;
  163. property OnCacheFlushed : TCacheFlushedEvent read fOnCacheFlushed write SetOnCacheFlushed;
  164. property OnBeginPurgerJob : TBeginPurgerJobEvent read fOnBeginPurgerJob write SetOnBeginPurgerJob;
  165. property OnEndPurgerJob : TEndPurgerJobEvent read fOnEndPurgerJob write SetOnEndPurgerJob;
  166. property OnPurgeJobError : TPurgerJobErrorEvent read fOnPurgerJobError write SetOnPurgerJobError;
  167. procedure RemoveValue(const aKey : string); virtual;
  168. procedure Refresh(const aKey: string; aExpirationMilliseconds : Integer);
  169. procedure Flush; virtual;
  170. end;
  171. TMemoryCache<T> = class(TMemoryCacheBase,IMemoryCache<T>)
  172. private
  173. procedure SetValue(const aKey : string; aValue : T; aExpirationMilliseconds : Integer; aExpirationDate : TDateTime); overload;
  174. public
  175. constructor Create(aPurgerInterval : Integer = 20; aCacheSerializer : ICacheSerializer = nil; aCacheCompressor : ICacheCompressor = nil); override;
  176. procedure SetValue(const aKey : string; aValue : T; aExpirationMillisecons : Integer = 0); overload;
  177. procedure SetValue(const aKey : string; aValue : T; aExpirationDate : TDateTime); overload;
  178. function GetValue(const aKey : string) : T;
  179. function TryGetValue(const aKey : string; out oValue : T) : Boolean;
  180. procedure RemoveValue(const aKey : string); override;
  181. end;
  182. TMemoryCache = class(TMemoryCacheBase,IMemoryCache)
  183. private
  184. procedure SetValue(const aKey: string; aValue: TObject; aExpirationMilliseconds : Integer; aExpirationDate : TDateTime); overload;
  185. procedure SetValue(const aKey, aValue: string; aExpirationMilliseconds : Integer; aExpirationDate : TDateTime); overload;
  186. public
  187. procedure SetValue(const aKey, aValue : string; aExpirationMilliseconds : Integer = 0); overload;
  188. procedure SetValue(const aKey, aValue : string; aExpirationDate : TDateTime); overload;
  189. procedure SetValue(const aKey : string; aValue : TObject; aExpirationMilliseconds : Integer = 0); overload;
  190. procedure SetValue(const aKey : string; aValue : TObject; aExpirationDate : TDateTime); overload;
  191. procedure SetValue(const aKey : string; aValue : TArray<string>; aExpirationMilliseconds : Integer = 0); overload;
  192. procedure SetValue(const aKey : string; aValue : TArray<string>; aExpirationDate : TDateTime); overload;
  193. procedure SetValue(const aKey : string; aValue : TArray<TObject>; aExpirationMilliseconds : Integer = 0); overload;
  194. procedure SetValue(const aKey : string; aValue : TArray<TObject>; aExpirationDate : TDateTime); overload;
  195. function GetValue(const aKey : string) : string; overload;
  196. function TryGetValue(const aKey : string; out aValue : string) : Boolean; overload;
  197. function TryGetValue(const aKey : string; aValue : TObject) : Boolean; overload;
  198. function TryGetValue<T>(const aKey : string; out oValue : T) : Boolean; overload;
  199. function TryGetValue(const aKey : string; out aValue : TArray<string>) : Boolean; overload;
  200. function TryGetValue(const aKey : string; out aValue : TArray<TObject>) : Boolean; overload;
  201. end;
  202. EMemoryCacheConfigError = class(Exception);
  203. EMemoryCacheSetError = class(Exception);
  204. EMemoryCacheGetError = class(Exception);
  205. EMemoryCacheFlushError = class(Exception);
  206. implementation
  207. { TMemoryCacheBase }
  208. constructor TMemoryCacheBase.Create(aPurgerInterval : Integer = 20; aCacheSerializer : ICacheSerializer = nil; aCacheCompressor : ICacheCompressor = nil);
  209. begin
  210. fCompression := True;
  211. SetPurgerInterval(aPurgerInterval);
  212. fCachedObjects := 0;
  213. fCacheSize := 0;
  214. fLock := TMultiReadExclusiveWriteSynchronizer.Create;
  215. if aCacheSerializer <> nil then fSerializer := aCacheSerializer
  216. else fSerializer := TCacheJsonSerializer.Create;
  217. if aCacheCompressor <> nil then fCompressor := aCacheCompressor
  218. else fCompressor := TCacheCompressorGZip.Create;
  219. fItems := TDictionary<string,ICacheEntry>.Create;
  220. fCacheJobs := TScheduledTasks.Create;
  221. CreatePurgerJobs;
  222. fCacheJobs.Start;
  223. end;
  224. procedure TMemoryCacheBase.CreatePurgerJobs;
  225. begin
  226. fCacheJobs.AddTask('RemoveExpired',procedure (task : ITask)
  227. begin
  228. RemoveExpiredCacheEntries;
  229. end
  230. ).OnException(procedure(task : ITask; aException : Exception)
  231. begin
  232. if Assigned(fOnPurgerJobError) then fOnPurgerJobError(aException.Message);
  233. end
  234. ).StartInSeconds(fPurgerInterval).RepeatEvery(fPurgerInterval,TTimeMeasure.tmSeconds);
  235. end;
  236. destructor TMemoryCacheBase.Destroy;
  237. begin
  238. fItems.Free;
  239. fLock.Free;
  240. fCacheJobs.Stop;
  241. fCacheJobs.Free;
  242. inherited;
  243. end;
  244. procedure TMemoryCacheBase.Flush;
  245. begin
  246. fLock.BeginWrite;
  247. try
  248. fItems.Clear;
  249. if Assigned(fOnCacheFlushed) then fOnCacheFlushed(fCachedObjects);
  250. fCachedObjects := 0;
  251. fCacheSize := 0;
  252. finally
  253. fLock.EndWrite;
  254. end;
  255. end;
  256. procedure TMemoryCacheBase.Refresh(const aKey: string; aExpirationMilliseconds : Integer);
  257. var
  258. cacheitem : ICacheEntry;
  259. begin
  260. if fItems.TryGetValue(aKey,cacheitem) then
  261. begin
  262. cacheitem.CreationDate := Now;
  263. cacheitem.Expiration := aExpirationMilliseconds;
  264. end;
  265. end;
  266. procedure TMemoryCacheBase.RemoveExpiredCacheEntries;
  267. var
  268. pair : TPair<string,ICacheEntry>;
  269. removedentries : Integer;
  270. begin
  271. if Assigned(fOnBeginPurgerJob) then fOnBeginPurgerJob;
  272. removedentries := 0;
  273. fLock.BeginRead;
  274. try
  275. for pair in fItems do
  276. begin
  277. if pair.Value.IsExpired then
  278. begin
  279. fLock.BeginWrite;
  280. try
  281. //decrease cacheitem size to cachesize
  282. AtomicDecrement(fCacheSize,pair.Value.Size);
  283. //remove cacheitem from cache
  284. fItems.Remove(pair.Key);
  285. //decrease cachedobjects
  286. AtomicDecrement(fCachedObjects,1);
  287. Inc(removedentries);
  288. finally
  289. fLock.EndWrite;
  290. end;
  291. end;
  292. end;
  293. finally
  294. fLock.EndRead;
  295. if Assigned(fOnEndPurgerJob) then fOnEndPurgerJob(removedentries);
  296. end;
  297. end;
  298. procedure TMemoryCacheBase.RemoveValue(const aKey: string);
  299. var
  300. cacheitem : ICacheEntry;
  301. begin
  302. if fItems.TryGetValue(aKey,cacheitem) then
  303. begin
  304. //decrease cacheitem size to cachesize
  305. AtomicDecrement(fCacheSize,cacheitem.Size);
  306. //remove cacheitem from cache
  307. fItems.Remove(aKey);
  308. //decrease cachedobjects
  309. AtomicDecrement(fCachedObjects,1);
  310. end;
  311. end;
  312. function TMemoryCacheBase.GetCachedObjects: Integer;
  313. begin
  314. Result := fCachedObjects;
  315. end;
  316. function TMemoryCacheBase.GetCacheSize: Integer;
  317. begin
  318. Result := fCacheSize;
  319. end;
  320. function TMemoryCacheBase.GetCompression: Boolean;
  321. begin
  322. Result := fCompression;
  323. end;
  324. procedure TMemoryCacheBase.SetCompression(const Value: Boolean);
  325. begin
  326. fCompression := Value;
  327. end;
  328. procedure TMemoryCacheBase.SetOnBeginPurgerJob(const Value: TBeginPurgerJobEvent);
  329. begin
  330. fOnBeginPurgerJob := Value;
  331. end;
  332. procedure TMemoryCacheBase.SetOnCacheFlushed(const Value: TCacheFlushedEvent);
  333. begin
  334. fOnCacheFlushed := Value;
  335. end;
  336. procedure TMemoryCacheBase.SetOnEndPurgerJob(const Value: TEndPurgerJobEvent);
  337. begin
  338. fOnEndPurgerJob := Value;
  339. end;
  340. procedure TMemoryCacheBase.SetOnPurgerJobError(const Value: TPurgerJobErrorEvent);
  341. begin
  342. fOnPurgerJobError := Value;
  343. end;
  344. procedure TMemoryCacheBase.SetPurgerInterval(const Value: Integer);
  345. begin
  346. if Value > 5 then
  347. begin
  348. fPurgerInterval := Value;
  349. end
  350. else raise EMemoryCacheConfigError.Create('Purger Interval must be greater than 5 seconds');
  351. end;
  352. { TCacheItem }
  353. constructor TCacheEntry.Create(aCompression : Boolean; aCacheCompressor : ICacheCompressor);
  354. begin
  355. fIsCompressed := False;
  356. fCompression := aCompression;
  357. fCompressor := aCacheCompressor;
  358. end;
  359. function TCacheEntry.GetCreationDate: TDateTime;
  360. begin
  361. Result := fCreationDate;
  362. end;
  363. function TCacheEntry.GetData: string;
  364. begin
  365. if fIsCompressed then Result := fCompressor.Decompress(fData)
  366. else Result := fData;
  367. end;
  368. function TCacheEntry.GetExpiration: Cardinal;
  369. begin
  370. Result := fExpiration;
  371. end;
  372. function TCacheEntry.GetExpirationDate: TDateTime;
  373. begin
  374. Result := fExpirationDate;
  375. end;
  376. procedure TCacheEntry.SetCreationDate(const Value: TDateTime);
  377. begin
  378. fCreationDate := Value;
  379. end;
  380. procedure TCacheEntry.SetExpiration(aMilliseconds: Cardinal);
  381. begin
  382. fExpiration := aMilliseconds;
  383. fExpirationDate := IncMilliSecond(fCreationDate,fExpiration);
  384. end;
  385. procedure TCacheEntry.SetExpirationDate(const Value: TDateTime);
  386. begin
  387. fExpiration := MilliSecondOf(Value);
  388. fExpirationDate := Value;
  389. end;
  390. function TCacheEntry.IsExpired: Boolean;
  391. begin
  392. if fExpiration = 0 then Result := False
  393. else Result := Now() > fExpirationDate;
  394. end;
  395. procedure TCacheEntry.SetData(const Value: string);
  396. begin
  397. fIsCompressed := False;
  398. //var a := value;
  399. //var b := value.Length;
  400. if fCompression then
  401. begin
  402. if ((Value.Length + 1) * 2) > 1024 then
  403. begin
  404. fData := fCompressor.Compress(Value);
  405. fIsCompressed := True;
  406. end
  407. else
  408. begin
  409. fData := Value;
  410. end;
  411. end
  412. else fData := Value;
  413. end;
  414. function TCacheEntry.Size: Integer;
  415. begin
  416. //Result := (fData.Length + 1) * SizeOf(Char);
  417. Result := (fData.Length + 1) * StringElementSize(fData);
  418. end;
  419. { TMemoryCache<T> }
  420. constructor TMemoryCache<T>.Create(aPurgerInterval: Integer; aCacheSerializer: ICacheSerializer; aCacheCompressor: ICacheCompressor);
  421. begin
  422. inherited Create(aPurgerInterval,aCacheSerializer,aCacheCompressor);
  423. end;
  424. function TMemoryCache<T>.GetValue(const aKey: string): T;
  425. begin
  426. TryGetValue(aKey,Result);
  427. end;
  428. procedure TMemoryCache<T>.RemoveValue(const aKey: string);
  429. begin
  430. inherited RemoveValue(aKey);
  431. end;
  432. procedure TMemoryCache<T>.SetValue(const aKey: string; aValue: T; aExpirationDate: TDateTime);
  433. begin
  434. SetValue(aKey,aValue,0,aExpirationDate);
  435. end;
  436. procedure TMemoryCache<T>.SetValue(const aKey: string; aValue: T; aExpirationMillisecons: Integer);
  437. begin
  438. SetValue(aKey,aValue,aExpirationMillisecons,0.0);
  439. end;
  440. procedure TMemoryCache<T>.SetValue(const aKey: string; aValue: T; aExpirationMilliseconds : Integer; aExpirationDate : TDateTime);
  441. var
  442. serialized : string;
  443. cacheitem : TCacheEntry;
  444. begin
  445. fLock.BeginWrite;
  446. try
  447. cacheitem := TCacheEntry.Create(fCompression,fCompressor);
  448. cacheitem.CreationDate := Now();
  449. cacheitem.Expiration := aExpirationMilliseconds;
  450. if aExpirationDate > 0.0 then cacheitem.ExpirationDate := aExpirationDate;
  451. //add object to cache
  452. case PTypeInfo(TypeInfo(T))^.Kind of
  453. tkClass, tkPointer :
  454. begin
  455. //object type need to be serialized
  456. cacheitem.Data := fSerializer.Serialize(PObject(@aValue)^);
  457. end;
  458. tkString, tkWideString, tkUString, tkChar, tkWideChar : cacheitem.Data := string((@aValue)^);
  459. {$IFNDEF NEXTGEN}
  460. tkAnsiString : cacheitem.Data := string(AnsiString((@aValue)^));
  461. {$ENDIF}
  462. else
  463. begin
  464. raise EMemoryCacheSetError.Create('Type not supported as cache');
  465. end;
  466. end;
  467. RemoveValue(aKey);
  468. fItems.Add(aKey,cacheitem);
  469. //add cacheitem size to cachesize
  470. AtomicIncrement(fCacheSize,cacheitem.Size);
  471. //increment cacheobjects
  472. AtomicIncrement(fCachedObjects,1);
  473. finally
  474. fLock.EndWrite;
  475. end;
  476. end;
  477. function TMemoryCache<T>.TryGetValue(const aKey: string; out oValue: T): Boolean;
  478. var
  479. cacheitem : ICacheEntry;
  480. flexvalue : TFlexValue;
  481. obj : TObject;
  482. begin
  483. fLock.BeginRead;
  484. try
  485. Result := fItems.TryGetValue(aKey,cacheitem);
  486. //check if cacheitem already expired
  487. if Result and cacheitem.IsExpired then Exit(False);
  488. finally
  489. fLock.EndRead;
  490. end;
  491. if Result then
  492. begin
  493. flexvalue.AsString := cacheitem.Data;
  494. case PTypeInfo(TypeInfo(T))^.Kind of
  495. tkInteger : oValue := TValue.From(flexvalue.AsInteger).AsType<T>;
  496. tkInt64 : oValue := TValue.From(flexvalue.AsInt64).AsType<T>;
  497. tkFloat :
  498. begin
  499. if TypeInfo(T) = TypeInfo(TDateTime) then oValue := TValue.From(flexvalue.AsDateTime).AsType<T>
  500. else oValue := TValue.From(flexvalue.AsExtended).AsType<T>;
  501. end;
  502. tkString,
  503. tkUString : oValue := TValue.From(flexvalue.AsString).AsType<T>;
  504. {$IFDEF MSWINDOWS}
  505. tkAnsiString : oValue := TValue.From(flexvalue.AsAnsiString).AsType<T>;
  506. tkWideString : oValue := TValue.From(flexvalue.AsWideString).AsType<T>;
  507. {$ENDIF}
  508. tkEnumeration :
  509. begin
  510. if TypeInfo(T) = TypeInfo(Boolean) then oValue := TValue.From(flexvalue.AsBoolean).AsType<T>
  511. else oValue := TValue.From(flexvalue.AsInteger).AsType<T>;
  512. end;
  513. tkClass, tkPointer :
  514. begin
  515. obj := PTypeInfo(TypeInfo(T))^.TypeData.ClassType.Create;
  516. fSerializer.Deserialize(cacheitem.Data,obj);
  517. oValue := TValue.From(obj).AsType<T>;
  518. //oValue := T((@obj)^);
  519. end
  520. else raise EMemoryCacheGetError.Create('Error casting value from cache');
  521. end;
  522. end;
  523. end;
  524. { TMemoryCache }
  525. function TMemoryCache.GetValue(const aKey: string): string;
  526. begin
  527. TryGetValue(aKey,Result);
  528. end;
  529. procedure TMemoryCache.SetValue(const aKey, aValue: string; aExpirationMilliseconds: Integer);
  530. begin
  531. SetValue(aKey,aValue,aExpirationMilliseconds,0.0);
  532. end;
  533. procedure TMemoryCache.SetValue(const aKey, aValue: string; aExpirationDate: TDateTime);
  534. begin
  535. SetValue(aKey,aValue,0,aExpirationDate);
  536. end;
  537. procedure TMemoryCache.SetValue(const aKey: string; aValue: TObject; aExpirationMilliseconds: Integer);
  538. begin
  539. SetValue(aKey,aValue,aExpirationMilliseconds,0.0);
  540. end;
  541. procedure TMemoryCache.SetValue(const aKey: string; aValue: TObject; aExpirationDate: TDateTime);
  542. begin
  543. SetValue(aKey,aValue,0,aExpirationDate);
  544. end;
  545. procedure TMemoryCache.SetValue(const aKey: string; aValue: TObject; aExpirationMilliseconds : Integer; aExpirationDate : TDateTime);
  546. begin
  547. SetValue(aKey,fSerializer.Serialize(aValue),aExpirationMilliseconds,aExpirationDate);
  548. end;
  549. procedure TMemoryCache.SetValue(const aKey, aValue: string; aExpirationMilliseconds : Integer; aExpirationDate : TDateTime);
  550. var
  551. cacheitem : TCacheEntry;
  552. begin
  553. fLock.BeginWrite;
  554. try
  555. cacheitem := TCacheEntry.Create(fCompression,fCompressor);
  556. cacheitem.CreationDate := Now();
  557. cacheitem.Expiration := aExpirationMilliseconds;
  558. if aExpirationDate > 0.0 then cacheitem.ExpirationDate := aExpirationDate;
  559. //add object to cache
  560. cacheitem.Data := aValue;
  561. RemoveValue(aKey);
  562. fItems.Add(aKey,cacheitem);
  563. //add cacheitem size to cachesize
  564. AtomicIncrement(fCacheSize,cacheitem.Size);
  565. //increment cacheobjects
  566. AtomicIncrement(fCachedObjects,1);
  567. finally
  568. fLock.EndWrite;
  569. end;
  570. end;
  571. procedure TMemoryCache.SetValue(const aKey: string; aValue: TArray<string>; aExpirationDate: TDateTime);
  572. begin
  573. SetValue(aKey,fSerializer.Serialize(aValue),0,aExpirationDate);
  574. end;
  575. procedure TMemoryCache.SetValue(const aKey: string; aValue: TArray<string>; aExpirationMilliseconds: Integer);
  576. begin
  577. SetValue(aKey,fSerializer.Serialize(aValue),aExpirationMilliseconds,0.0);
  578. end;
  579. procedure TMemoryCache.SetValue(const aKey: string; aValue: TArray<TObject>; aExpirationDate: TDateTime);
  580. begin
  581. SetValue(aKey,fSerializer.Serialize(aValue),0,aExpirationDate);
  582. end;
  583. procedure TMemoryCache.SetValue(const aKey: string; aValue: TArray<TObject>; aExpirationMilliseconds: Integer);
  584. begin
  585. SetValue(aKey,fSerializer.Serialize(aValue),aExpirationMilliseconds,0.0);
  586. end;
  587. function TMemoryCache.TryGetValue(const aKey: string; aValue : TObject): Boolean;
  588. var
  589. cacheitem : ICacheEntry;
  590. begin
  591. fLock.BeginRead;
  592. try
  593. if aValue = nil then raise EMemoryCacheGetError.Create('Cannot passed a nil object as param');
  594. Result := fItems.TryGetValue(aKey,cacheitem);
  595. //check if cacheitem already expired
  596. if (not Result) or (cacheitem.IsExpired) then Exit(False);
  597. finally
  598. fLock.EndRead;
  599. end;
  600. fSerializer.Deserialize(cacheitem.Data,aValue);
  601. end;
  602. function TMemoryCache.TryGetValue(const aKey: string; out aValue: string): Boolean;
  603. begin
  604. Result := TryGetValue<string>(aKey,aValue);
  605. end;
  606. function TMemoryCache.TryGetValue<T>(const aKey: string; out oValue: T): Boolean;
  607. var
  608. cacheitem : ICacheEntry;
  609. flexvalue : TFlexValue;
  610. obj : TObject;
  611. begin
  612. fLock.BeginRead;
  613. try
  614. Result := fItems.TryGetValue(aKey,cacheitem);
  615. //check if cacheitem already expired
  616. if Result and cacheitem.IsExpired then Exit(False);
  617. finally
  618. fLock.EndRead;
  619. end;
  620. if Result then
  621. begin
  622. flexvalue.AsString := cacheitem.Data;
  623. case PTypeInfo(TypeInfo(T))^.Kind of
  624. tkInteger : oValue := TValue.From(flexvalue.AsInteger).AsType<T>;
  625. tkInt64 : oValue := TValue.From(flexvalue.AsInt64).AsType<T>;
  626. tkFloat :
  627. begin
  628. if TypeInfo(T) = TypeInfo(TDateTime) then oValue := TValue.From(flexvalue.AsDateTime).AsType<T>
  629. else oValue := TValue.From(flexvalue.AsExtended).AsType<T>;
  630. end;
  631. tkString,
  632. tkUString : oValue := TValue.From(flexvalue.AsString).AsType<T>;
  633. {$IFDEF MSWINDOWS}
  634. tkAnsiString : oValue := TValue.From(flexvalue.AsAnsiString).AsType<T>;
  635. tkWideString : oValue := TValue.From(flexvalue.AsWideString).AsType<T>;
  636. {$ENDIF}
  637. tkEnumeration :
  638. begin
  639. if TypeInfo(T) = TypeInfo(Boolean) then oValue := TValue.From(flexvalue.AsBoolean).AsType<T>
  640. else oValue := TValue.From(flexvalue.AsInteger).AsType<T>;
  641. end;
  642. tkClass, tkPointer :
  643. begin
  644. obj := PTypeInfo(TypeInfo(T))^.TypeData.ClassType.Create;
  645. fSerializer.Deserialize(flexvalue.AsString,obj);
  646. oValue := TValue.From(obj).AsType<T>;
  647. end;
  648. else raise EMemoryCacheGetError.Create('Error casting value from cache');
  649. end;
  650. end;
  651. end;
  652. function TMemoryCache.TryGetValue(const aKey: string; out aValue: TArray<string>): Boolean;
  653. var
  654. cacheitem : ICacheEntry;
  655. begin
  656. fLock.BeginRead;
  657. try
  658. Result := fItems.TryGetValue(aKey,cacheitem);
  659. //check if cacheitem already expired
  660. if Result and cacheitem.IsExpired then Exit(False);
  661. finally
  662. fLock.EndRead;
  663. end;
  664. if Result then fSerializer.Deserialize(cacheitem.Data,aValue);
  665. end;
  666. function TMemoryCache.TryGetValue(const aKey: string; out aValue: TArray<TObject>): Boolean;
  667. var
  668. cacheitem : ICacheEntry;
  669. begin
  670. fLock.BeginRead;
  671. try
  672. Result := fItems.TryGetValue(aKey,cacheitem);
  673. //check if cacheitem already expired
  674. if Result and cacheitem.IsExpired then Exit(False);
  675. finally
  676. fLock.EndRead;
  677. end;
  678. if Result then fSerializer.Deserialize(cacheitem.Data,aValue);
  679. end;
  680. end.