Quick.MemoryCache.pas 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774
  1. { ***************************************************************************
  2. Copyright (c) 2016-2019 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 : 02/11/2019
  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. var
  426. cacheitem : ICacheEntry;
  427. begin
  428. fLock.BeginRead;
  429. try
  430. fItems.TryGetValue(aKey,cacheitem);
  431. finally
  432. fLock.EndRead;
  433. end;
  434. end;
  435. procedure TMemoryCache<T>.RemoveValue(const aKey: string);
  436. begin
  437. inherited RemoveValue(aKey);
  438. end;
  439. procedure TMemoryCache<T>.SetValue(const aKey: string; aValue: T; aExpirationDate: TDateTime);
  440. begin
  441. SetValue(aKey,aValue,0,aExpirationDate);
  442. end;
  443. procedure TMemoryCache<T>.SetValue(const aKey: string; aValue: T; aExpirationMillisecons: Integer);
  444. begin
  445. SetValue(aKey,aValue,aExpirationMillisecons,0.0);
  446. end;
  447. procedure TMemoryCache<T>.SetValue(const aKey: string; aValue: T; aExpirationMilliseconds : Integer; aExpirationDate : TDateTime);
  448. var
  449. serialized : string;
  450. cacheitem : TCacheEntry;
  451. begin
  452. fLock.BeginWrite;
  453. try
  454. cacheitem := TCacheEntry.Create(fCompression,fCompressor);
  455. cacheitem.CreationDate := Now();
  456. cacheitem.Expiration := aExpirationMilliseconds;
  457. if aExpirationDate > 0.0 then cacheitem.ExpirationDate := aExpirationDate;
  458. //add object to cache
  459. case PTypeInfo(TypeInfo(T))^.Kind of
  460. tkClass, tkPointer :
  461. begin
  462. //object type need to be serialized
  463. cacheitem.Data := fSerializer.Serialize(PObject(@aValue)^);
  464. end;
  465. tkString, tkWideString, tkUString, tkChar, tkWideChar : cacheitem.Data := string((@aValue)^);
  466. {$IFNDEF NEXTGEN}
  467. tkAnsiString : cacheitem.Data := string(AnsiString((@aValue)^));
  468. {$ENDIF}
  469. else
  470. begin
  471. raise EMemoryCacheSetError.Create('Type not supported as cache');
  472. end;
  473. end;
  474. RemoveValue(aKey);
  475. fItems.Add(aKey,cacheitem);
  476. //add cacheitem size to cachesize
  477. AtomicIncrement(fCacheSize,cacheitem.Size);
  478. //increment cacheobjects
  479. AtomicIncrement(fCachedObjects,1);
  480. finally
  481. fLock.EndWrite;
  482. end;
  483. end;
  484. function TMemoryCache<T>.TryGetValue(const aKey: string; out oValue: T): Boolean;
  485. var
  486. cacheitem : ICacheEntry;
  487. flexvalue : TFlexValue;
  488. obj : TObject;
  489. begin
  490. fLock.BeginRead;
  491. try
  492. Result := fItems.TryGetValue(aKey,cacheitem);
  493. //check if cacheitem already expired
  494. if Result and cacheitem.IsExpired then Exit(False);
  495. finally
  496. fLock.EndRead;
  497. end;
  498. if Result then
  499. begin
  500. flexvalue.AsString := cacheitem.Data;
  501. case PTypeInfo(TypeInfo(T))^.Kind of
  502. tkInteger : oValue := TValue.From(flexvalue.AsInteger).AsType<T>;
  503. tkInt64 : oValue := TValue.From(flexvalue.AsInt64).AsType<T>;
  504. tkFloat :
  505. begin
  506. if TypeInfo(T) = TypeInfo(TDateTime) then oValue := TValue.From(flexvalue.AsDateTime).AsType<T>
  507. else oValue := TValue.From(flexvalue.AsExtended).AsType<T>;
  508. end;
  509. tkString,
  510. tkUString : oValue := TValue.From(flexvalue.AsString).AsType<T>;
  511. {$IFDEF MSWINDOWS}
  512. tkAnsiString : oValue := TValue.From(flexvalue.AsAnsiString).AsType<T>;
  513. tkWideString : oValue := TValue.From(flexvalue.AsWideString).AsType<T>;
  514. {$ENDIF}
  515. tkEnumeration :
  516. begin
  517. if TypeInfo(T) = TypeInfo(Boolean) then oValue := TValue.From(flexvalue.AsBoolean).AsType<T>
  518. else oValue := TValue.From(flexvalue.AsInteger).AsType<T>;
  519. end;
  520. tkClass, tkPointer :
  521. begin
  522. obj := PTypeInfo(TypeInfo(T))^.TypeData.ClassType.Create;
  523. fSerializer.Deserialize(cacheitem.Data,obj);
  524. oValue := TValue.From(obj).AsType<T>;
  525. //oValue := T((@obj)^);
  526. end
  527. else raise EMemoryCacheGetError.Create('Error casting value from cache');
  528. end;
  529. end;
  530. end;
  531. { TMemoryCache }
  532. function TMemoryCache.GetValue(const aKey: string): string;
  533. var
  534. cacheitem : ICacheEntry;
  535. begin
  536. if fItems.TryGetValue(aKey,cacheitem) then Result := cacheitem.Data;
  537. end;
  538. procedure TMemoryCache.SetValue(const aKey, aValue: string; aExpirationMilliseconds: Integer);
  539. begin
  540. SetValue(aKey,aValue,aExpirationMilliseconds,0.0);
  541. end;
  542. procedure TMemoryCache.SetValue(const aKey, aValue: string; aExpirationDate: TDateTime);
  543. begin
  544. SetValue(aKey,aValue,0,aExpirationDate);
  545. end;
  546. procedure TMemoryCache.SetValue(const aKey: string; aValue: TObject; aExpirationMilliseconds: Integer);
  547. begin
  548. SetValue(aKey,aValue,aExpirationMilliseconds,0.0);
  549. end;
  550. procedure TMemoryCache.SetValue(const aKey: string; aValue: TObject; aExpirationDate: TDateTime);
  551. begin
  552. SetValue(aKey,aValue,0,aExpirationDate);
  553. end;
  554. procedure TMemoryCache.SetValue(const aKey: string; aValue: TObject; aExpirationMilliseconds : Integer; aExpirationDate : TDateTime);
  555. begin
  556. SetValue(aKey,fSerializer.Serialize(aValue),aExpirationMilliseconds,aExpirationDate);
  557. end;
  558. procedure TMemoryCache.SetValue(const aKey, aValue: string; aExpirationMilliseconds : Integer; aExpirationDate : TDateTime);
  559. var
  560. cacheitem : TCacheEntry;
  561. begin
  562. fLock.BeginWrite;
  563. try
  564. cacheitem := TCacheEntry.Create(fCompression,fCompressor);
  565. cacheitem.CreationDate := Now();
  566. cacheitem.Expiration := aExpirationMilliseconds;
  567. if aExpirationDate > 0.0 then cacheitem.ExpirationDate := aExpirationDate;
  568. //add object to cache
  569. cacheitem.Data := aValue;
  570. RemoveValue(aKey);
  571. fItems.Add(aKey,cacheitem);
  572. //add cacheitem size to cachesize
  573. AtomicIncrement(fCacheSize,cacheitem.Size);
  574. //increment cacheobjects
  575. AtomicIncrement(fCachedObjects,1);
  576. finally
  577. fLock.EndWrite;
  578. end;
  579. end;
  580. procedure TMemoryCache.SetValue(const aKey: string; aValue: TArray<string>; aExpirationDate: TDateTime);
  581. begin
  582. SetValue(aKey,fSerializer.Serialize(aValue),0,aExpirationDate);
  583. end;
  584. procedure TMemoryCache.SetValue(const aKey: string; aValue: TArray<string>; aExpirationMilliseconds: Integer);
  585. begin
  586. SetValue(aKey,fSerializer.Serialize(aValue),aExpirationMilliseconds,0.0);
  587. end;
  588. procedure TMemoryCache.SetValue(const aKey: string; aValue: TArray<TObject>; aExpirationDate: TDateTime);
  589. begin
  590. SetValue(aKey,fSerializer.Serialize(aValue),0,aExpirationDate);
  591. end;
  592. procedure TMemoryCache.SetValue(const aKey: string; aValue: TArray<TObject>; aExpirationMilliseconds: Integer);
  593. begin
  594. SetValue(aKey,fSerializer.Serialize(aValue),aExpirationMilliseconds,0.0);
  595. end;
  596. function TMemoryCache.TryGetValue(const aKey: string; aValue : TObject): Boolean;
  597. var
  598. cacheitem : ICacheEntry;
  599. begin
  600. fLock.BeginRead;
  601. try
  602. if aValue = nil then raise EMemoryCacheGetError.Create('Cannot passed a nil object as param');
  603. Result := fItems.TryGetValue(aKey,cacheitem);
  604. //check if cacheitem already expired
  605. if (not Result) or (cacheitem.IsExpired) then Exit(False);
  606. finally
  607. fLock.EndRead;
  608. end;
  609. fSerializer.Deserialize(cacheitem.Data,aValue);
  610. end;
  611. function TMemoryCache.TryGetValue(const aKey: string; out aValue: string): Boolean;
  612. begin
  613. Result := TryGetValue<string>(aKey,aValue);
  614. end;
  615. function TMemoryCache.TryGetValue<T>(const aKey: string; out oValue: T): Boolean;
  616. var
  617. cacheitem : ICacheEntry;
  618. flexvalue : TFlexValue;
  619. obj : TObject;
  620. begin
  621. fLock.BeginRead;
  622. try
  623. Result := fItems.TryGetValue(aKey,cacheitem);
  624. //check if cacheitem already expired
  625. if Result and cacheitem.IsExpired then Exit(False);
  626. finally
  627. fLock.EndRead;
  628. end;
  629. if Result then
  630. begin
  631. flexvalue.AsString := cacheitem.Data;
  632. case PTypeInfo(TypeInfo(T))^.Kind of
  633. tkInteger : oValue := TValue.From(flexvalue.AsInteger).AsType<T>;
  634. tkInt64 : oValue := TValue.From(flexvalue.AsInt64).AsType<T>;
  635. tkFloat :
  636. begin
  637. if TypeInfo(T) = TypeInfo(TDateTime) then oValue := TValue.From(flexvalue.AsDateTime).AsType<T>
  638. else oValue := TValue.From(flexvalue.AsExtended).AsType<T>;
  639. end;
  640. tkString,
  641. tkUString : oValue := TValue.From(flexvalue.AsString).AsType<T>;
  642. {$IFDEF MSWINDOWS}
  643. tkAnsiString : oValue := TValue.From(flexvalue.AsAnsiString).AsType<T>;
  644. tkWideString : oValue := TValue.From(flexvalue.AsWideString).AsType<T>;
  645. {$ENDIF}
  646. tkEnumeration :
  647. begin
  648. if TypeInfo(T) = TypeInfo(Boolean) then oValue := TValue.From(flexvalue.AsBoolean).AsType<T>
  649. else oValue := TValue.From(flexvalue.AsInteger).AsType<T>;
  650. end;
  651. tkClass, tkPointer :
  652. begin
  653. obj := PTypeInfo(TypeInfo(T))^.TypeData.ClassType.Create;
  654. fSerializer.Deserialize(flexvalue.AsString,obj);
  655. oValue := TValue.From(obj).AsType<T>;
  656. end;
  657. else raise EMemoryCacheGetError.Create('Error casting value from cache');
  658. end;
  659. end;
  660. end;
  661. function TMemoryCache.TryGetValue(const aKey: string; out aValue: TArray<string>): Boolean;
  662. var
  663. cacheitem : ICacheEntry;
  664. begin
  665. fLock.BeginRead;
  666. try
  667. Result := fItems.TryGetValue(aKey,cacheitem);
  668. //check if cacheitem already expired
  669. if Result and cacheitem.IsExpired then Exit(False);
  670. finally
  671. fLock.EndRead;
  672. end;
  673. if Result then fSerializer.Deserialize(cacheitem.Data,aValue);
  674. end;
  675. function TMemoryCache.TryGetValue(const aKey: string; out aValue: TArray<TObject>): Boolean;
  676. var
  677. cacheitem : ICacheEntry;
  678. begin
  679. fLock.BeginRead;
  680. try
  681. Result := fItems.TryGetValue(aKey,cacheitem);
  682. //check if cacheitem already expired
  683. if Result and cacheitem.IsExpired then Exit(False);
  684. finally
  685. fLock.EndRead;
  686. end;
  687. if Result then fSerializer.Deserialize(cacheitem.Data,aValue);
  688. end;
  689. end.