Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

407 rader
10 KiB

  1. unit uutlSettings;
  2. { Package: Utils
  3. Prefix: utl - UTiLs
  4. Beschreibung: diese Unit stellt ein Framework zur Verfügung mit dessen Hilfe Einstellungs-Blöcke
  5. in ein MCF File geladen und geschreieben werden können }
  6. {$mode objfpc}{$H+}
  7. interface
  8. uses
  9. Classes, SysUtils,
  10. uutlMCF, uutlGenerics, uutlMessageThread;
  11. type
  12. TutlSettingsBlock = class
  13. constructor Create; virtual;
  14. procedure LoadDefaults; virtual; abstract;
  15. procedure LoadFromConfig(const aMcf: TutlMCFSection); virtual; abstract;
  16. procedure SaveToConfig(const aMcf: TutlMCFSection); virtual; abstract;
  17. end;
  18. TutlSettingsBlockClass = class of TutlSettingsBlock;
  19. TutlSettingsUpdateOp = (opInstanceChanged, opDataChanged);
  20. TutlSettingsUpdateEvent = procedure (const aUpdateOp: TutlSettingsUpdateOp; const aOld, aNew: TutlSettingsBlock) of object;
  21. TutlSettingsUpdateEventCntr = packed record
  22. Callback: TutlSettingsUpdateEvent;
  23. ThreadID: TThreadID;
  24. end;
  25. TutlSettingsUpdateEventCntrEqComp = class(TInterfacedObject, specialize IutlEqualityComparer<TutlSettingsUpdateEventCntr>)
  26. public
  27. function EqualityCompare(const i1, i2: TutlSettingsUpdateEventCntr): Boolean;
  28. end;
  29. TutlSettings = class
  30. private type
  31. TutlSettingsUpdateEventList = specialize TutlCustomList<TutlSettingsUpdateEventCntr>;
  32. TBlockData = class
  33. Instance, OldInstance: TutlSettingsBlock;
  34. Events: TutlSettingsUpdateEventList;
  35. procedure CallEvents(const aOp: TutlSettingsUpdateOp; const aOld, aNew: TutlSettingsBlock);
  36. constructor Create;
  37. destructor Destroy; override;
  38. end;
  39. TBlockList = specialize TutlMap<String, TBlockData>;
  40. private
  41. fBlocks: TBlockList;
  42. fRaiseChangedEventOnLoad: Boolean;
  43. procedure CopyInstance(O, N: TutlSettingsBlock);
  44. public
  45. property RaiseChangedEventOnLoad: Boolean read fRaiseChangedEventOnLoad write fRaiseChangedEventOnLoad;
  46. function RegisterBlock(const aName: String; const aClass: TutlSettingsBlockClass; const aOnUpdateEvent: TutlSettingsUpdateEvent): TutlSettingsBlock;
  47. procedure UnregisterBlockCallback(const aName: String; const aOnUpdateEvent: TutlSettingsUpdateEvent);
  48. procedure UnregisterBlockCallbacks(const aObj: TObject);
  49. function Block(const aName: String; out aBlock): boolean;
  50. procedure Changed(const aBlock: TutlSettingsBlock);
  51. procedure LoadFromConfig(const aMcf: TutlMCFSection);
  52. procedure SaveToConfig(const aMcf: TutlMCFSection);
  53. procedure LoadFromFile(const aFile: string);
  54. procedure SaveToFile(const aFile: string);
  55. constructor Create;
  56. destructor Destroy; override;
  57. end;
  58. operator = (const i1, i2: TutlSettingsUpdateEventCntr): Boolean; inline;
  59. var
  60. utlSettings: TutlSettings;
  61. implementation
  62. uses
  63. uutlExceptions, Forms{$IFDEF USE_VFS}, uvfsManager{$ENDIF}, uutlMessages, syncobjs;
  64. const
  65. SETTINGS_MSG_WAIT_TIME = 1000; //ms
  66. type
  67. TSettingsBlockChangedMsg = class(TutlSyncCallbackMsg)
  68. private
  69. fCallback: TutlSettingsUpdateEvent;
  70. fOperation: TutlSettingsUpdateOp;
  71. fOld: TutlSettingsBlock;
  72. fNew: TutlSettingsBlock;
  73. public
  74. procedure ExecuteCallback; override;
  75. constructor Create(const aCallback: TutlSettingsUpdateEvent; const aOp: TutlSettingsUpdateOp;
  76. const aOld, aNew: TutlSettingsBlock);
  77. end;
  78. operator = (const i1, i2: TutlSettingsUpdateEventCntr): Boolean;
  79. begin
  80. result :=
  81. (i1.Callback = i2.Callback) and
  82. (i2.ThreadID = i2.ThreadID);
  83. end;
  84. function TutlSettingsUpdateEventCntrEqComp.EqualityCompare(const i1, i2: TutlSettingsUpdateEventCntr): Boolean;
  85. begin
  86. result := (i1 = i2);
  87. end;
  88. { TSettingsBlockChangedMsg }
  89. procedure TSettingsBlockChangedMsg.ExecuteCallback;
  90. begin
  91. fCallback(fOperation, fOld, fNew);
  92. end;
  93. constructor TSettingsBlockChangedMsg.Create(const aCallback: TutlSettingsUpdateEvent;
  94. const aOp: TutlSettingsUpdateOp; const aOld, aNew: TutlSettingsBlock);
  95. begin
  96. inherited Create;
  97. fCallback := aCallback;
  98. fOperation := aOp;
  99. fOld := aOld;
  100. fNew := aNew;
  101. end;
  102. { TutlSettings.TBlockData }
  103. procedure TutlSettings.TBlockData.CallEvents(const aOp: TutlSettingsUpdateOp;
  104. const aOld, aNew: TutlSettingsBlock);
  105. var
  106. current: TThreadID;
  107. cntr: TutlSettingsUpdateEventCntr;
  108. msg: TSettingsBlockChangedMsg;
  109. begin
  110. current := GetCurrentThreadId;
  111. for cntr in Events do begin
  112. if (cntr.ThreadID <> current) then begin
  113. msg := TSettingsBlockChangedMsg.Create(cntr.Callback, aOp, aOld, aNew);
  114. if utlSendMessage(cntr.ThreadID, msg, SETTINGS_MSG_WAIT_TIME) = wrSignaled then
  115. msg.Free;
  116. end else
  117. cntr.Callback(aOp, aOld, aNew);
  118. end;
  119. end;
  120. constructor TutlSettings.TBlockData.Create;
  121. begin
  122. inherited;
  123. Events:= TutlSettingsUpdateEventList.Create(TutlSettingsUpdateEventCntrEqComp.Create);
  124. end;
  125. destructor TutlSettings.TBlockData.Destroy;
  126. begin
  127. FreeAndNil(Events);
  128. FreeAndNil(Instance);
  129. FreeAndNil(OldInstance);
  130. inherited Destroy;
  131. end;
  132. { TutlSettingsBlock }
  133. constructor TutlSettingsBlock.Create;
  134. begin
  135. inherited;
  136. LoadDefaults;
  137. end;
  138. { TutlSettings }
  139. function TutlSettings.RegisterBlock(const aName: String; const aClass: TutlSettingsBlockClass;
  140. const aOnUpdateEvent: TutlSettingsUpdateEvent): TutlSettingsBlock;
  141. var
  142. i: integer;
  143. bd: TBlockData;
  144. cntr: TutlSettingsUpdateEventCntr;
  145. begin
  146. Result:= nil;
  147. if aName = '' then
  148. raise EInvalidOperation.Create('Empty Settings section name.');
  149. i:= fBlocks.IndexOf(aName);
  150. if i>=0 then begin
  151. bd:= fBlocks.ValueAt[i];
  152. // gleicher name, instance ist gleiche oder spezifischere klasse
  153. if bd.Instance is aClass then begin
  154. if Assigned(aOnUpdateEvent) then begin
  155. cntr.Callback := aOnUpdateEvent;
  156. cntr.ThreadID := GetCurrentThreadId;
  157. bd.Events.Add(cntr);
  158. end;
  159. Exit(bd.Instance)
  160. end else
  161. // gleicher name, neue klasse ist spezifischer
  162. if aClass.InheritsFrom(bd.Instance.ClassType) then begin
  163. Result:= aClass.Create;
  164. CopyInstance(bd.Instance, Result);
  165. bd.CallEvents(opInstanceChanged, bd.Instance, Result);
  166. bd.Instance.Free;
  167. bd.OldInstance.Free;
  168. bd.Instance:= aClass.Create;
  169. bd.OldInstance:= aClass.Create;
  170. if Assigned(aOnUpdateEvent) then begin
  171. cntr.Callback := aOnUpdateEvent;
  172. cntr.ThreadID := GetCurrentThreadId;
  173. bd.Events.Add(cntr);
  174. end;
  175. Exit;
  176. end
  177. // gleicher name, aber komplett andere klasse
  178. else
  179. raise EInvalidOperation.CreateFmt('Duplicate Settings entry: %s', [aName]);
  180. end;
  181. for bd in fBlocks do
  182. // verwandte klasse aber anderer name (wäre es der gleiche wäre das schon oben abgefangen)
  183. if (bd.Instance is aClass) or (aClass.InheritsFrom(bd.Instance.ClassType)) then
  184. raise EInvalidOperation.CreateFmt('Reused Settings class: %s', [aClass.ClassName]);
  185. // neuer name, neue klasse
  186. bd:= TBlockData.Create;
  187. bd.Instance:= aClass.Create;
  188. bd.OldInstance:= aClass.Create;
  189. if Assigned(aOnUpdateEvent) then begin
  190. cntr.Callback := aOnUpdateEvent;
  191. cntr.ThreadID := GetCurrentThreadId;
  192. bd.Events.Add(cntr);
  193. end;
  194. fBlocks.Add(aName, bd);
  195. Result:= bd.Instance;
  196. end;
  197. procedure TutlSettings.UnregisterBlockCallback(const aName: String; const aOnUpdateEvent: TutlSettingsUpdateEvent);
  198. var
  199. i: integer;
  200. bd: TBlockData;
  201. begin
  202. i:= fBlocks.IndexOf(aName);
  203. if i >= 0 then begin
  204. bd := fBlocks.ValueAt[i];
  205. for i := bd.Events.Count-1 downto 0 do
  206. if (bd.Events.Items[i].Callback = aOnUpdateEvent) then
  207. bd.Events.Delete(i);
  208. end;
  209. end;
  210. procedure TutlSettings.UnregisterBlockCallbacks(const aObj: TObject);
  211. var
  212. bd: TBlockData;
  213. i: integer;
  214. begin
  215. for bd in fBlocks do
  216. for i:= bd.Events.Count-1 downto 0 do
  217. if TMethod(bd.Events[i].Callback).Data = Pointer(aObj) then
  218. bd.Events.Delete(i);
  219. end;
  220. procedure TutlSettings.CopyInstance(O, N: TutlSettingsBlock);
  221. var
  222. tmp: TutlMCFSection;
  223. begin
  224. tmp:= TutlMCFSection.Create;
  225. try
  226. O.SaveToConfig(tmp);
  227. N.LoadFromConfig(tmp);
  228. finally
  229. FreeAndNil(tmp);
  230. end;
  231. end;
  232. function TutlSettings.Block(const aName: String; out aBlock): boolean;
  233. var
  234. i: integer;
  235. bd: TBlockData;
  236. begin
  237. i := fBlocks.IndexOf(aName);
  238. Result := (i >= 0);
  239. if Result then begin
  240. bd:= fBlocks.ValueAt[i];
  241. CopyInstance(bd.Instance, bd.OldInstance);
  242. TutlSettingsBlock(aBlock):= bd.Instance;
  243. end;
  244. end;
  245. procedure TutlSettings.Changed(const aBlock: TutlSettingsBlock);
  246. var
  247. bd: TBlockData;
  248. begin
  249. for bd in fBlocks do
  250. if bd.Instance = aBlock then begin
  251. bd.CallEvents(opDataChanged, bd.OldInstance, bd.Instance);
  252. exit;
  253. end;
  254. end;
  255. procedure TutlSettings.LoadFromConfig(const aMcf: TutlMCFSection);
  256. var
  257. i: Integer;
  258. b: TBlockData;
  259. begin
  260. for i := 0 to fBlocks.Count-1 do begin
  261. b := fBlocks.ValueAt[i];
  262. b.Instance.LoadFromConfig(aMcf.Section(fBlocks.Keys[i]));
  263. if fRaiseChangedEventOnLoad then
  264. Changed(b.Instance);
  265. end;
  266. end;
  267. procedure TutlSettings.SaveToConfig(const aMcf: TutlMCFSection);
  268. var
  269. i: integer;
  270. begin
  271. for i:= 0 to fBlocks.Count-1 do
  272. fBlocks.ValueAt[i].Instance.SaveToConfig(aMcf.Section(fBlocks.Keys[i]));
  273. end;
  274. {$IFDEF USE_VFS}
  275. procedure TutlSettings.LoadFromFile(const aFile: string);
  276. var
  277. sh: IStreamHandle;
  278. mcf: TutlMCFFile;
  279. begin
  280. if vfsManager.ReadFile(aFile, sh) then begin
  281. mcf:= TutlMCFFile.Create(sh);
  282. try
  283. LoadFromConfig(mcf);
  284. finally
  285. FreeAndNil(mcf);
  286. end;
  287. end;
  288. end;
  289. {$ELSE}
  290. procedure TutlSettings.LoadFromFile(const aFile: string);
  291. var
  292. fs: TFileStream;
  293. mcf: TutlMCFFile;
  294. begin
  295. fs := TFileStream.Create(aFile, fmOpenRead);
  296. mcf := TutlMCFFile.Create(nil);
  297. try
  298. mcf.LoadFromStream(fs);
  299. LoadFromConfig(mcf);
  300. finally
  301. FreeAndNil(fs);
  302. end;
  303. end;
  304. {$ENDIF}
  305. {$IFDEF USE_VFS}
  306. procedure TutlSettings.SaveToFile(const aFile: string);
  307. var
  308. sh: IStreamHandle;
  309. mcf: TutlMCFFile;
  310. begin
  311. if vfsManager.CreateFile(aFile, sh) then begin
  312. mcf:= TutlMCFFile.Create(nil);
  313. try
  314. SaveToConfig(mcf);
  315. mcf.SaveToStream(sh);
  316. finally
  317. FreeAndNil(mcf);
  318. end;
  319. end;
  320. end;
  321. {$ELSE}
  322. procedure TutlSettings.SaveToFile(const aFile: string);
  323. var
  324. fs: TFileStream;
  325. mcf: TutlMCFFile;
  326. begin
  327. fs := TFileStream.Create(aFile, fmCreate);
  328. mcf := TutlMCFFile.Create(nil);
  329. try
  330. SaveToConfig(mcf);
  331. mcf.SaveToStream(fs);
  332. finally
  333. FreeAndNil(mcf);
  334. FreeAndNil(fs);
  335. end;
  336. end;
  337. {$ENDIF}
  338. constructor TutlSettings.Create;
  339. begin
  340. inherited Create;
  341. fBlocks:= TBlockList.Create(true);
  342. fRaiseChangedEventOnLoad := true;
  343. end;
  344. destructor TutlSettings.Destroy;
  345. begin
  346. FreeAndNil(fBlocks);
  347. inherited Destroy;
  348. end;
  349. initialization
  350. utlSettings := TutlSettings.Create;
  351. finalization
  352. FreeAndNil(utlSettings);
  353. end.