1 | unit BGRAMultiFileType;
|
---|
2 |
|
---|
3 | {$mode objfpc}{$H+}
|
---|
4 | {$MODESWITCH ADVANCEDRECORDS}
|
---|
5 |
|
---|
6 | interface
|
---|
7 |
|
---|
8 | uses
|
---|
9 | Classes, SysUtils, fgl;
|
---|
10 |
|
---|
11 | type
|
---|
12 |
|
---|
13 | { TEntryFilename }
|
---|
14 |
|
---|
15 | TEntryFilename = record
|
---|
16 | private
|
---|
17 | FExtension: utf8string;
|
---|
18 | FName: utf8string;
|
---|
19 | function GetFilename: utf8string;
|
---|
20 | function GetIsEmpty: boolean;
|
---|
21 | procedure SetExtension(AValue: utf8string);
|
---|
22 | procedure SetFilename(AValue: utf8string);
|
---|
23 | procedure SetName(AValue: utf8string);
|
---|
24 | public
|
---|
25 | class operator =(const AValue1,AValue2: TEntryFilename): boolean;
|
---|
26 | property Filename: utf8string read GetFilename write SetFilename;
|
---|
27 | property Name: utf8string read FName write SetName;
|
---|
28 | property Extension: utf8string read FExtension write SetExtension;
|
---|
29 | property IsEmpty: boolean read GetIsEmpty;
|
---|
30 | end;
|
---|
31 |
|
---|
32 | function EntryFilename(AName,AExtension: string): TEntryFilename; overload;
|
---|
33 | function EntryFilename(AFilename: string): TEntryFilename; overload;
|
---|
34 |
|
---|
35 | type
|
---|
36 | TMultiFileContainer = class;
|
---|
37 |
|
---|
38 | { TMultiFileEntry }
|
---|
39 |
|
---|
40 | TMultiFileEntry = class
|
---|
41 | protected
|
---|
42 | FContainer: TMultiFileContainer;
|
---|
43 | function GetName: utf8string; virtual; abstract;
|
---|
44 | procedure SetName(AValue: utf8string); virtual; abstract;
|
---|
45 | function GetFileSize: int64; virtual;
|
---|
46 | function GetExtension: utf8string; virtual;
|
---|
47 | public
|
---|
48 | constructor Create(AContainer: TMultiFileContainer);
|
---|
49 | function CopyTo({%H-}ADestination: TStream): int64; virtual;
|
---|
50 | property Name: utf8string read GetName write SetName;
|
---|
51 | property Extension: utf8string read GetExtension;
|
---|
52 | property FileSize: int64 read GetFileSize;
|
---|
53 | property Container: TMultiFileContainer read FContainer;
|
---|
54 | end;
|
---|
55 |
|
---|
56 | TMultiFileEntryList = specialize TFPGList<TMultiFileEntry>;
|
---|
57 |
|
---|
58 | { TMultiFileContainer }
|
---|
59 |
|
---|
60 | TMultiFileContainer = class
|
---|
61 | private
|
---|
62 | FEntries: TMultiFileEntryList;
|
---|
63 | protected
|
---|
64 | procedure Init; virtual;
|
---|
65 | function AddEntry(AEntry: TMultiFileEntry; AIndex: integer = -1): integer;
|
---|
66 | function GetCount: integer;
|
---|
67 | function GetEntry(AIndex: integer): TMultiFileEntry;
|
---|
68 | function CreateEntry(AName: utf8string; AExtension: utf8string; AContent: TStream): TMultiFileEntry; virtual; abstract;
|
---|
69 | function GetRawString(AIndex: integer): RawByteString;
|
---|
70 | function GetRawStringByFilename(AFilename: string): RawByteString;
|
---|
71 | procedure SetRawString(AIndex: integer; AValue: RawByteString);
|
---|
72 | procedure SetRawStringByFilename(AFilename: string; AValue: RawByteString);
|
---|
73 | public
|
---|
74 | constructor Create; overload;
|
---|
75 | constructor Create(AFilename: utf8string); overload;
|
---|
76 | constructor Create(AStream: TStream); overload;
|
---|
77 | constructor Create(AStream: TStream; AStartPos: Int64); overload;
|
---|
78 | function Add(AName: utf8string; AExtension: utf8string; AContent: TStream; AOverwrite: boolean = false; AOwnStream: boolean = true): integer; overload;
|
---|
79 | function Add(AName: utf8string; AExtension: utf8string; AContent: RawByteString; AOverwrite: boolean = false): integer; overload;
|
---|
80 | function Add(AFilename: TEntryFilename; AContent: TStream; AOverwrite: boolean = false; AOwnStream: boolean = true): integer; overload;
|
---|
81 | function Add(AFilename: TEntryFilename; AContent: RawByteString; AOverwrite: boolean = false): integer; overload;
|
---|
82 | procedure Clear; virtual;
|
---|
83 | destructor Destroy; override;
|
---|
84 | procedure LoadFromFile(AFilename: utf8string);
|
---|
85 | procedure LoadFromStream(AStream: TStream); virtual; abstract;
|
---|
86 | procedure LoadFromResource(AFilename: string); virtual;
|
---|
87 | procedure SaveToFile(AFilename: utf8string);
|
---|
88 | procedure SaveToStream(ADestination: TStream); virtual; abstract;
|
---|
89 | procedure Remove(AEntry: TMultiFileEntry); virtual;
|
---|
90 | procedure Delete(AIndex: integer); overload; virtual;
|
---|
91 | function Delete(AName: utf8string; AExtension: utf8string; ACaseSensitive: boolean = True): boolean; overload;
|
---|
92 | function Delete(AFilename: TEntryFilename; ACaseSensitive: boolean = True): boolean; overload;
|
---|
93 | function IndexOf(AEntry: TMultiFileEntry): integer; overload;
|
---|
94 | function IndexOf(AName: utf8string; AExtenstion: utf8string; ACaseSensitive: boolean = True): integer; overload; virtual;
|
---|
95 | function IndexOf(AFilename: TEntryFilename; ACaseSensitive: boolean = True): integer; overload;
|
---|
96 | property Count: integer read GetCount;
|
---|
97 | property Entry[AIndex: integer]: TMultiFileEntry read GetEntry;
|
---|
98 | property RawString[AIndex: integer]: RawByteString read GetRawString write SetRawString;
|
---|
99 | property RawStringByFilename[AFilename: string]: RawByteString read GetRawStringByFilename write SetRawStringByFilename;
|
---|
100 | end;
|
---|
101 |
|
---|
102 | implementation
|
---|
103 |
|
---|
104 | uses BGRAUTF8, strutils, BGRABitmapTypes;
|
---|
105 |
|
---|
106 | { TEntryFilename }
|
---|
107 |
|
---|
108 | function TEntryFilename.GetFilename: utf8string;
|
---|
109 | begin
|
---|
110 | if Extension = '' then
|
---|
111 | result := Name
|
---|
112 | else
|
---|
113 | result := Name+'.'+Extension;
|
---|
114 | end;
|
---|
115 |
|
---|
116 | function TEntryFilename.GetIsEmpty: boolean;
|
---|
117 | begin
|
---|
118 | result := (FName='') and (FExtension = '');
|
---|
119 | end;
|
---|
120 |
|
---|
121 | procedure TEntryFilename.SetExtension(AValue: utf8string);
|
---|
122 | var
|
---|
123 | i: Integer;
|
---|
124 | begin
|
---|
125 | if FExtension=AValue then Exit;
|
---|
126 | for i := 1 to length(AValue) do
|
---|
127 | if AValue[i] in ['.','/'] then
|
---|
128 | raise Exception.Create('Invalid extension');
|
---|
129 | FExtension:=AValue;
|
---|
130 | end;
|
---|
131 |
|
---|
132 | procedure TEntryFilename.SetFilename(AValue: utf8string);
|
---|
133 | var
|
---|
134 | idxDot: SizeInt;
|
---|
135 | begin
|
---|
136 | idxDot := RPos('.',AValue);
|
---|
137 | if idxDot = 0 then
|
---|
138 | begin
|
---|
139 | Name := AValue;
|
---|
140 | Extension := '';
|
---|
141 | end
|
---|
142 | else
|
---|
143 | begin
|
---|
144 | Name := copy(AValue,1,idxDot-1);
|
---|
145 | Extension := copy(AValue,idxDot+1,length(AValue)-idxDot);
|
---|
146 | end;
|
---|
147 | end;
|
---|
148 |
|
---|
149 | procedure TEntryFilename.SetName(AValue: utf8string);
|
---|
150 | var
|
---|
151 | i: Integer;
|
---|
152 | begin
|
---|
153 | if FName=AValue then Exit;
|
---|
154 | for i := 1 to length(AValue) do
|
---|
155 | if AValue[i] = '/' then
|
---|
156 | raise Exception.Create('Invalid name');
|
---|
157 | FName:=AValue;
|
---|
158 | end;
|
---|
159 |
|
---|
160 | function EntryFilename(AName, AExtension: string): TEntryFilename;
|
---|
161 | begin
|
---|
162 | result.Name := AName;
|
---|
163 | result.Extension:= AExtension;
|
---|
164 | end;
|
---|
165 |
|
---|
166 | function EntryFilename(AFilename: string): TEntryFilename;
|
---|
167 | begin
|
---|
168 | result.Filename:= AFilename;
|
---|
169 | end;
|
---|
170 |
|
---|
171 | class operator TEntryFilename.=(const AValue1, AValue2: TEntryFilename): boolean;
|
---|
172 | begin
|
---|
173 | result := (AValue1.Name = AValue2.Name) and (AValue1.Extension = AValue2.Extension);
|
---|
174 | end;
|
---|
175 |
|
---|
176 | { TMultiFileEntry }
|
---|
177 |
|
---|
178 | function TMultiFileEntry.GetFileSize: int64;
|
---|
179 | begin
|
---|
180 | result := 0;
|
---|
181 | end;
|
---|
182 |
|
---|
183 | function TMultiFileEntry.GetExtension: utf8string;
|
---|
184 | begin
|
---|
185 | result := '';
|
---|
186 | end;
|
---|
187 |
|
---|
188 | constructor TMultiFileEntry.Create(AContainer: TMultiFileContainer);
|
---|
189 | begin
|
---|
190 | FContainer := AContainer;
|
---|
191 | end;
|
---|
192 |
|
---|
193 | function TMultiFileEntry.CopyTo(ADestination: TStream): int64;
|
---|
194 | begin
|
---|
195 | result := 0;
|
---|
196 | end;
|
---|
197 |
|
---|
198 | { TMultiFileContainer }
|
---|
199 |
|
---|
200 | function TMultiFileContainer.GetCount: integer;
|
---|
201 | begin
|
---|
202 | if Assigned(FEntries) then
|
---|
203 | result := FEntries.Count
|
---|
204 | else
|
---|
205 | result := 0;
|
---|
206 | end;
|
---|
207 |
|
---|
208 | function TMultiFileContainer.GetEntry(AIndex: integer): TMultiFileEntry;
|
---|
209 | begin
|
---|
210 | result := FEntries[AIndex];
|
---|
211 | end;
|
---|
212 |
|
---|
213 | function TMultiFileContainer.GetRawString(AIndex: integer): RawByteString;
|
---|
214 | var s: TStringStream;
|
---|
215 | begin
|
---|
216 | s := TStringStream.Create('');
|
---|
217 | try
|
---|
218 | Entry[AIndex].CopyTo(s);
|
---|
219 | result := s.DataString;
|
---|
220 | finally
|
---|
221 | s.Free;
|
---|
222 | end;
|
---|
223 | end;
|
---|
224 |
|
---|
225 | function TMultiFileContainer.GetRawStringByFilename(AFilename: string
|
---|
226 | ): RawByteString;
|
---|
227 | var
|
---|
228 | idx: Integer;
|
---|
229 | begin
|
---|
230 | idx := IndexOf(EntryFilename(AFilename));
|
---|
231 | if idx = -1 then
|
---|
232 | result := ''
|
---|
233 | else
|
---|
234 | result := GetRawString(idx);
|
---|
235 | end;
|
---|
236 |
|
---|
237 | procedure TMultiFileContainer.SetRawString(AIndex: integer;
|
---|
238 | AValue: RawByteString);
|
---|
239 | begin
|
---|
240 | with Entry[AIndex] do
|
---|
241 | Add(Name, Extension, AValue, true);
|
---|
242 | end;
|
---|
243 |
|
---|
244 | procedure TMultiFileContainer.SetRawStringByFilename(AFilename: string;
|
---|
245 | AValue: RawByteString);
|
---|
246 | var
|
---|
247 | f: TEntryFilename;
|
---|
248 | begin
|
---|
249 | f := EntryFilename(AFilename);
|
---|
250 | Add(f.Name,f.Extension,AValue,true);
|
---|
251 | end;
|
---|
252 |
|
---|
253 | procedure TMultiFileContainer.Init;
|
---|
254 | begin
|
---|
255 | FEntries := TMultiFileEntryList.Create;
|
---|
256 | end;
|
---|
257 |
|
---|
258 | function TMultiFileContainer.AddEntry(AEntry: TMultiFileEntry; AIndex: integer): integer;
|
---|
259 | begin
|
---|
260 | if not Assigned(FEntries) then
|
---|
261 | raise exception.Create('Entry list not created');
|
---|
262 | if (AIndex >= 0) and (AIndex < FEntries.Count) then
|
---|
263 | begin
|
---|
264 | FEntries.Insert(AIndex, AEntry);
|
---|
265 | result := AIndex;
|
---|
266 | end
|
---|
267 | else
|
---|
268 | result := FEntries.Add(AEntry);
|
---|
269 | end;
|
---|
270 |
|
---|
271 | constructor TMultiFileContainer.Create;
|
---|
272 | begin
|
---|
273 | Init;
|
---|
274 | end;
|
---|
275 |
|
---|
276 | constructor TMultiFileContainer.Create(AFilename: utf8string);
|
---|
277 | begin
|
---|
278 | Init;
|
---|
279 | LoadFromFile(AFilename);
|
---|
280 | end;
|
---|
281 |
|
---|
282 | constructor TMultiFileContainer.Create(AStream: TStream);
|
---|
283 | begin
|
---|
284 | Init;
|
---|
285 | LoadFromStream(AStream);
|
---|
286 | end;
|
---|
287 |
|
---|
288 | constructor TMultiFileContainer.Create(AStream: TStream; AStartPos: Int64);
|
---|
289 | begin
|
---|
290 | Init;
|
---|
291 | AStream.Position := AStartPos;
|
---|
292 | LoadFromStream(AStream);
|
---|
293 | end;
|
---|
294 |
|
---|
295 | function TMultiFileContainer.Add(AName: utf8string; AExtension: utf8string;
|
---|
296 | AContent: TStream; AOverwrite: boolean; AOwnStream: boolean): integer;
|
---|
297 | var
|
---|
298 | index: Integer;
|
---|
299 | newEntry: TMultiFileEntry;
|
---|
300 | contentCopy: TMemoryStream;
|
---|
301 | begin
|
---|
302 | index := IndexOf(AName,AExtension);
|
---|
303 | if index <> -1 then
|
---|
304 | begin
|
---|
305 | if AOverwrite then
|
---|
306 | Delete(index)
|
---|
307 | else
|
---|
308 | raise Exception.Create('Duplicate entry');
|
---|
309 | end;
|
---|
310 | if not AOwnStream then
|
---|
311 | begin
|
---|
312 | AContent.Position:= 0;
|
---|
313 | contentCopy := TMemoryStream.Create;
|
---|
314 | contentCopy.CopyFrom(AContent, AContent.Size);
|
---|
315 | newEntry := CreateEntry(AName, AExtension, contentCopy);
|
---|
316 | end else
|
---|
317 | newEntry := CreateEntry(AName, AExtension, AContent);
|
---|
318 | if Assigned(newEntry) then
|
---|
319 | result := AddEntry(newEntry, index)
|
---|
320 | else
|
---|
321 | raise exception.Create('Unable to create entry');
|
---|
322 | end;
|
---|
323 |
|
---|
324 | function TMultiFileContainer.Add(AName: utf8string; AExtension: utf8string;
|
---|
325 | AContent: RawByteString; AOverwrite: boolean): integer;
|
---|
326 | var stream: TMemoryStream;
|
---|
327 | begin
|
---|
328 | stream := TMemoryStream.Create;
|
---|
329 | if length(AContent) > 0 then stream.Write(AContent[1],length(AContent));
|
---|
330 | result := Add(AName,AExtension,stream,AOverwrite);
|
---|
331 | end;
|
---|
332 |
|
---|
333 | function TMultiFileContainer.Add(AFilename: TEntryFilename; AContent: TStream;
|
---|
334 | AOverwrite: boolean; AOwnStream: boolean): integer;
|
---|
335 | begin
|
---|
336 | result := Add(AFilename.Name,AFilename.Extension, AContent, AOverwrite, AOwnStream);
|
---|
337 | end;
|
---|
338 |
|
---|
339 | function TMultiFileContainer.Add(AFilename: TEntryFilename;
|
---|
340 | AContent: RawByteString; AOverwrite: boolean): integer;
|
---|
341 | begin
|
---|
342 | result := Add(AFilename.Name,AFilename.Extension, AContent, AOverwrite);
|
---|
343 | end;
|
---|
344 |
|
---|
345 | destructor TMultiFileContainer.Destroy;
|
---|
346 | begin
|
---|
347 | Clear;
|
---|
348 | FreeAndNil(FEntries);
|
---|
349 | inherited Destroy;
|
---|
350 | end;
|
---|
351 |
|
---|
352 | procedure TMultiFileContainer.LoadFromFile(AFilename: utf8string);
|
---|
353 | var stream: TFileStream;
|
---|
354 | begin
|
---|
355 | stream := TFileStream.Create(Utf8ToAnsi(AFilename), fmOpenRead);
|
---|
356 | LoadFromStream(stream);
|
---|
357 | stream.Free;
|
---|
358 | end;
|
---|
359 |
|
---|
360 | procedure TMultiFileContainer.LoadFromResource(AFilename: string);
|
---|
361 | var
|
---|
362 | stream: TStream;
|
---|
363 | begin
|
---|
364 | stream := BGRAResource.GetResourceStream(AFilename);
|
---|
365 | try
|
---|
366 | LoadFromStream(stream);
|
---|
367 | finally
|
---|
368 | stream.Free;
|
---|
369 | end;
|
---|
370 | end;
|
---|
371 |
|
---|
372 | procedure TMultiFileContainer.SaveToFile(AFilename: utf8string);
|
---|
373 | var stream: TFileStream;
|
---|
374 | begin
|
---|
375 | stream := TFileStream.Create(Utf8ToAnsi(AFilename), fmCreate);
|
---|
376 | SaveToStream(stream);
|
---|
377 | stream.Free;
|
---|
378 | end;
|
---|
379 |
|
---|
380 | procedure TMultiFileContainer.Remove(AEntry: TMultiFileEntry);
|
---|
381 | var
|
---|
382 | index: Integer;
|
---|
383 | begin
|
---|
384 | index := IndexOf(AEntry);
|
---|
385 | if index = -1 then
|
---|
386 | raise exception.Create('Entry not found');
|
---|
387 | Delete(index);
|
---|
388 | end;
|
---|
389 |
|
---|
390 | procedure TMultiFileContainer.Delete(AIndex: integer);
|
---|
391 | begin
|
---|
392 | if (AIndex >= 0) and (AIndex < Count) then
|
---|
393 | begin
|
---|
394 | Entry[AIndex].Free;
|
---|
395 | FEntries.Delete(AIndex);
|
---|
396 | end else
|
---|
397 | raise ERangeError.Create('Index out of bounds');
|
---|
398 | end;
|
---|
399 |
|
---|
400 | function TMultiFileContainer.Delete(AName: utf8string; AExtension: utf8string;
|
---|
401 | ACaseSensitive: boolean): boolean;
|
---|
402 | var
|
---|
403 | index: Integer;
|
---|
404 | begin
|
---|
405 | index := IndexOf(AName, AExtension, ACaseSensitive);
|
---|
406 | if index = -1 then
|
---|
407 | result := false
|
---|
408 | else
|
---|
409 | begin
|
---|
410 | Delete(index);
|
---|
411 | result := true;
|
---|
412 | end;
|
---|
413 | end;
|
---|
414 |
|
---|
415 | function TMultiFileContainer.Delete(AFilename: TEntryFilename;
|
---|
416 | ACaseSensitive: boolean): boolean;
|
---|
417 | begin
|
---|
418 | result := Delete(AFilename.Name,AFilename.Extension,ACaseSensitive);
|
---|
419 | end;
|
---|
420 |
|
---|
421 | function TMultiFileContainer.IndexOf(AEntry: TMultiFileEntry): integer;
|
---|
422 | begin
|
---|
423 | result := FEntries.IndexOf(AEntry);
|
---|
424 | end;
|
---|
425 |
|
---|
426 | function TMultiFileContainer.IndexOf(AName: utf8string; AExtenstion: utf8string; ACaseSensitive: boolean): integer;
|
---|
427 | var
|
---|
428 | i: Integer;
|
---|
429 | begin
|
---|
430 | if ACaseSensitive then
|
---|
431 | begin
|
---|
432 | for i := 0 to Count-1 do
|
---|
433 | if (Entry[i].Name = AName) and (UTF8CompareText(Entry[i].Extension,AExtenstion) = 0) then
|
---|
434 | begin
|
---|
435 | result := i;
|
---|
436 | exit;
|
---|
437 | end;
|
---|
438 | end else
|
---|
439 | for i := 0 to Count-1 do
|
---|
440 | if (UTF8CompareText(Entry[i].Name,AName) = 0) and (UTF8CompareText(Entry[i].Extension,AExtenstion) = 0) then
|
---|
441 | begin
|
---|
442 | result := i;
|
---|
443 | exit;
|
---|
444 | end;
|
---|
445 | result := -1;
|
---|
446 | end;
|
---|
447 |
|
---|
448 | function TMultiFileContainer.IndexOf(AFilename: TEntryFilename;
|
---|
449 | ACaseSensitive: boolean): integer;
|
---|
450 | begin
|
---|
451 | result := IndexOf(AFilename.Name,AFilename.Extension,ACaseSensitive);
|
---|
452 | end;
|
---|
453 |
|
---|
454 | procedure TMultiFileContainer.Clear;
|
---|
455 | var
|
---|
456 | i: Integer;
|
---|
457 | begin
|
---|
458 | for i := 0 to FEntries.Count-1 do
|
---|
459 | FEntries.Items[i].Free;
|
---|
460 | FEntries.Clear;
|
---|
461 | end;
|
---|
462 |
|
---|
463 | end.
|
---|
464 |
|
---|