source: trunk/Packages/bgrabitmap/bgramemdirectory.pas

Last change on this file was 2, checked in by chronos, 5 years ago
File size: 18.7 KB
Line 
1unit BGRAMemDirectory;
2
3{$mode objfpc}{$H+}
4
5interface
6
7uses
8 Classes, SysUtils, BGRAMultiFileType, fgl;
9
10const
11 MemDirectoryFileHeader = 'TMemDirectory'#26#0#0;
12 MemDirectoryEntry_FlagDirectory = 1; //entry is a directory
13 MemDirectoryEntry_FlagCompressed = 2; //the stream is compressed
14 MemDirectoryEntry_FlagSmallEntryPacked = $8000; //name and size <= 255
15
16type
17 TMemDirectory = class;
18 TEntryFilename = BGRAMultiFileType.TEntryFilename;
19
20type
21 TMemDirectoryPath = specialize TFPGList<TEntryFilename>;
22
23 { TMemDirectoryEntry }
24
25 TMemDirectoryEntry = class(TMultiFileEntry)
26 private
27 FStream: TStream;
28 function GetIsCompressed: boolean;
29 function GetCompressedSize: int64;
30 function GetIsDirectory: boolean;
31 procedure SetIsCompressed(AValue: boolean);
32 procedure LoadExtraFromEmbeddedStream(ADataStream: TStream; AStartPos: int64);
33 procedure SaveToEmbeddedStream(AEntryStream, ADataStream: TStream; AStartPos: int64; out uncompressedSize: int64);
34 protected
35 FFlags: Word;
36 FName,FExtension: utf8String;
37 FUncompressedSize: int64;
38 FEmbeddedStreamPos: int64;
39 FMemDirectory: TMemDirectory;
40 function GetName: utf8string; override;
41 procedure SetName(AValue: utf8string); override;
42 function GetFileSize: int64; override;
43 function GetExtension: utf8string; override;
44 function InternalCopyTo({%H-}ADestination: TStream): int64;
45 public
46 function CopyTo({%H-}ADestination: TStream): int64; override;
47 constructor Create(AContainer: TMultiFileContainer; AFilename: TEntryFilename; AUncompressedStream: TStream; AOwnStream: boolean); overload;
48 constructor CreateDirectory(AContainer: TMultiFileContainer; AFilename: TEntryFilename);
49 constructor CreateFromData(AContainer: TMultiFileContainer; AFilename: TEntryFilename; AStream: TStream; AOwnStream: boolean; AUncompressedSize: int64; AFlags: Word);
50 destructor Destroy; override;
51 property EmbeddedStreamPos: int64 read FEmbeddedStreamPos write FEmbeddedStreamPos;
52 property IsCompressed: boolean read GetIsCompressed write SetIsCompressed;
53 property IsDirectory: boolean read GetIsDirectory;
54 property CompressedSize: int64 read GetCompressedSize;
55 property Flags: Word read FFlags;
56 property MemDirectory: TMemDirectory read FMemDirectory;
57 end;
58
59 TMemDirectory = class(TMultiFileContainer)
60 private
61 FParentDirectory: TMemDirectory;
62 function GetEntryCompressed(AIndex: integer): boolean;
63 function GetIsDirectory(AIndex: integer): boolean;
64 function GetDirectory(AIndex: integer): TMemDirectory;
65 procedure SetEntryCompressed(AIndex: integer; AValue: boolean);
66 protected
67 function CreateEntry(AName: utf8string; AExtension: utf8string; AContent: TStream): TMultiFileEntry; override;
68 function SplitPath(APath: utf8string): TMemDirectoryPath;
69 public
70 constructor Create(AParentDirectory: TMemDirectory = nil);
71 procedure LoadFromStream(AStream: TStream); override;
72 class function CheckHeader(AStream: TStream): boolean;
73 procedure LoadFromEmbeddedStream(ARootStream, ADataStream: TStream; AStartPos: int64);
74 procedure SaveToStream(ADestination: TStream); override;
75 procedure SaveToEmbeddedStream(ARootDest, ADataDest: TStream; AStartPos: int64);
76 function AddDirectory(AName: utf8string; AExtension: utf8string= ''; ACaseSensitive: boolean= true): integer;
77 function FindPath(APath: utf8String; ACaseSensitive: boolean = true): TMemDirectory;
78 function FindEntry(APath: utf8String; ACaseSensitive: boolean = true): TMemDirectoryEntry;
79 procedure CopyTo(ADest: TMemDirectory; ARecursive: boolean);
80 property IsEntryCompressed[AIndex: integer]: boolean read GetEntryCompressed write SetEntryCompressed;
81 property Directory[AIndex: integer]: TMemDirectory read GetDirectory;
82 property IsDirectory[AIndex: integer]: boolean read GetIsDirectory;
83 property ParentDirectory: TMemDirectory read FParentDirectory;
84 end;
85
86implementation
87
88uses zstream, BGRAUTF8, strutils;
89
90type
91 TDirEntryRecord = packed record
92 Flags: Word;
93 FilenameSize: Word;
94 Offset: int64;
95 end;
96
97{ TMemDirectory }
98
99function TMemDirectory.GetEntryCompressed(AIndex: integer): boolean;
100begin
101 result := (Entry[AIndex] as TMemDirectoryEntry).IsCompressed;
102end;
103
104function TMemDirectory.GetIsDirectory(AIndex: integer): boolean;
105begin
106 result := (Entry[AIndex] as TMemDirectoryEntry).IsDirectory;
107end;
108
109function TMemDirectory.GetDirectory(AIndex: integer): TMemDirectory;
110begin
111 result := (Entry[AIndex] as TMemDirectoryEntry).MemDirectory;
112end;
113
114procedure TMemDirectory.SetEntryCompressed(AIndex: integer; AValue: boolean);
115begin
116 (Entry[AIndex] as TMemDirectoryEntry).IsCompressed := AValue;
117end;
118
119function TMemDirectory.CreateEntry(AName: utf8string; AExtension: utf8string;
120 AContent: TStream): TMultiFileEntry;
121begin
122 result := TMemDirectoryEntry.Create(self, EntryFilename(AName, AExtension), AContent, true);
123end;
124
125procedure TMemDirectory.LoadFromStream(AStream: TStream);
126var rootPos, rootSize: integer;
127 header: string;
128 rootStream: TStream;
129 startPos: Int64;
130begin
131 startPos := AStream.Position;
132 setlength(header, length(MemDirectoryFileHeader));
133 AStream.ReadBuffer(header[1], length(header));
134 if header<>MemDirectoryFileHeader then
135 raise exception.Create('Invalid header');
136 rootPos := LEReadInt64(AStream);
137 if rootPos = 0 then
138 raise exception.Create('Invalid root offset');
139 rootSize := LEReadInt64(AStream);
140 if rootSize <= 4 then
141 raise exception.Create('Invalid root size');
142 AStream.Position:= rootPos + startPos;
143 rootStream:= TMemoryStream.Create;
144 try
145 rootStream.CopyFrom(AStream, rootSize);
146 LoadFromEmbeddedStream(rootStream, AStream, startPos);
147 finally
148 rootStream.Free;
149 end;
150end;
151
152class function TMemDirectory.CheckHeader(AStream: TStream): boolean;
153var
154 startPos: Int64;
155 header: string;
156begin
157 startPos := AStream.Position;
158 setlength(header, length(MemDirectoryFileHeader));
159 AStream.Read(header[1], length(header));
160 result := (header=MemDirectoryFileHeader);
161 AStream.Position:= startPos;
162end;
163
164procedure TMemDirectory.LoadFromEmbeddedStream(ARootStream, ADataStream: TStream;
165 AStartPos: int64);
166var
167 nbEntries,i: LongInt;
168 entryRec: TDirEntryRecord;
169 filename: string;
170 entryData: TStream;
171 newEntry: TMemDirectoryEntry;
172 compressedSize, uncompressedSize: Int64;
173
174begin
175 Clear;
176 ARootStream.Position := 0;
177 nbEntries := LEReadLongint(ARootStream);
178 for i := 1 to nbEntries do
179 begin
180 ARootStream.ReadBuffer({%H-}entryRec, sizeof(entryRec));
181 entryRec.Offset:= LEtoN(entryRec.Offset);
182 entryRec.Flags:= LEtoN(entryRec.Flags);
183 entryRec.FilenameSize:= LEtoN(entryRec.FilenameSize);
184
185 if (entryRec.Flags and MemDirectoryEntry_FlagSmallEntryPacked) <> 0 then
186 begin
187 entryRec.Flags := entryRec.Flags xor MemDirectoryEntry_FlagSmallEntryPacked;
188 compressedSize := entryRec.FilenameSize shr 8;
189 uncompressedSize := compressedSize;
190 entryRec.FilenameSize := entryRec.FilenameSize and 255;
191 end else
192 begin
193 compressedSize := LEReadInt64(ARootStream);
194 uncompressedSize := LEReadInt64(ARootStream);
195 end;
196
197 setlength(filename, entryRec.FilenameSize);
198 if length(filename)> 0 then
199 ARootStream.ReadBuffer(filename[1], entryRec.FilenameSize);
200
201 ADataStream.Position:= entryRec.Offset + AStartPos;
202 entryData := TMemoryStream.Create;
203 try
204 if compressedSize <> 0 then
205 entryData.CopyFrom(ADataStream, compressedSize);
206 newEntry := TMemDirectoryEntry.CreateFromData(self, EntryFilename(filename), entryData, true,
207 uncompressedSize, entryRec.Flags);
208 newEntry.LoadExtraFromEmbeddedStream(ADataStream, AStartPos);
209 AddEntry(newEntry);
210 entryData := nil;
211 finally
212 entryData.Free;
213 end;
214 end;
215end;
216
217procedure TMemDirectory.SaveToStream(ADestination: TStream);
218var rootPos,rootSize: integer;
219 header: string;
220 rootRecPos, startPos, endPos: int64;
221 rootStream: TStream;
222begin
223 startPos := ADestination.Position;
224 header := MemDirectoryFileHeader;
225 ADestination.WriteBuffer(header[1], length(header));
226
227 rootRecPos := ADestination.Position;
228 LEWriteInt64(ADestination,0); //root pos
229 LEWriteInt64(ADestination,0); //root size
230
231 rootStream := TMemoryStream.Create;
232 try
233 SaveToEmbeddedStream(rootStream, ADestination, startPos);
234 rootStream.Position := 0;
235 rootPos := ADestination.Position - startPos;
236 rootSize := rootStream.Size;
237 ADestination.CopyFrom(rootStream, rootStream.Size);
238 FreeAndNil(rootStream);
239 endPos := ADestination.Position;
240 ADestination.Position := rootRecPos;
241 LEWriteInt64(ADestination, rootPos);
242 LEWriteInt64(ADestination, rootSize);
243 ADestination.Position := endPos;
244 finally
245 rootStream.Free;
246 end;
247end;
248
249procedure TMemDirectory.SaveToEmbeddedStream(ARootDest, ADataDest: TStream;
250 AStartPos: int64);
251var
252 entryRec: TDirEntryRecord;
253 entryStream: TMemoryStream;
254 curEntry: TMemDirectoryEntry;
255 filename: string;
256 i: Integer;
257 uncompressedSize: int64;
258begin
259 LEWriteLongint(ARootDest, Count);
260 entryStream := TMemoryStream.Create;
261 try
262 for i := 0 to Count-1 do
263 begin
264 curEntry := Entry[i] as TMemDirectoryEntry;
265 entryStream.Clear;
266 curEntry.SaveToEmbeddedStream(entryStream, ADataDest, AStartPos, uncompressedSize);
267
268 entryRec.Offset:= ADataDest.Position - AStartPos;
269 entryRec.Offset:= NtoLE(entryRec.Offset);
270 if curEntry.Extension <> '' then
271 filename := curEntry.Name+'.'+curEntry.Extension
272 else
273 filename := curEntry.Name;
274
275 if ((curEntry.Flags and MemDirectoryEntry_FlagCompressed)=0) and
276 (Length(filename)<=255) and (entryStream.Size<=255) then
277 begin
278 entryRec.Flags:= curEntry.Flags or MemDirectoryEntry_FlagSmallEntryPacked;
279 entryRec.Flags:= NtoLE(entryRec.Flags);
280 entryRec.FilenameSize:= length(filename) + (entryStream.Size shl 8);
281 entryRec.FilenameSize := NtoLE(entryRec.FilenameSize);
282 ARootDest.WriteBuffer(entryRec, sizeof(entryRec));
283 end else
284 begin
285 entryRec.Flags:= curEntry.Flags;
286 entryRec.Flags:= NtoLE(entryRec.Flags);
287 entryRec.FilenameSize:= length(filename);
288 entryRec.FilenameSize := NtoLE(entryRec.FilenameSize);
289 ARootDest.WriteBuffer(entryRec, sizeof(entryRec));
290 LEWriteInt64(ARootDest, entryStream.Size);
291 LEWriteInt64(ARootDest, uncompressedSize);
292 end;
293
294 if filename <> '' then
295 ARootDest.WriteBuffer(filename[1], length(filename));
296
297 entryStream.Position:= 0;
298 ADataDest.CopyFrom(entryStream, entryStream.Size);
299 end;
300 finally
301 entryStream.Free;
302 end;
303end;
304
305function TMemDirectory.AddDirectory(AName: utf8string; AExtension: utf8string;
306 ACaseSensitive: boolean): integer;
307var
308 newEntry: TMemDirectoryEntry;
309begin
310 result := IndexOf(AName,AExtension,ACaseSensitive);
311 if result <> -1 then
312 begin
313 if not IsDirectory[result] then
314 raise exception.Create('There is already a file with this name and extension');
315 exit;
316 end;
317 newEntry := TMemDirectoryEntry.CreateDirectory(self, EntryFilename(AName, AExtension));
318 result := AddEntry(newEntry);
319end;
320
321function TMemDirectory.FindPath(APath: utf8String; ACaseSensitive: boolean): TMemDirectory;
322var
323 path: TMemDirectoryPath;
324 idxPath: integer;
325 idxSub: LongInt;
326begin
327 path := SplitPath(APath);
328 result := self;
329 if path.Items[0].IsEmpty then
330 begin
331 idxPath := 1;
332 while Assigned(result.ParentDirectory) do result := result.ParentDirectory;
333 end
334 else
335 idxPath := 0;
336
337 while idxPath < path.Count do
338 begin
339 idxSub := result.IndexOf(path[idxPath], ACaseSensitive);
340 if idxSub= -1 then
341 begin
342 result := nil;
343 break;
344 end;
345 result := result.Directory[idxSub];
346 inc(idxPath);
347 end;
348
349 path.Free;
350end;
351
352function TMemDirectory.FindEntry(APath: utf8String; ACaseSensitive: boolean): TMemDirectoryEntry;
353var
354 path: TMemDirectoryPath;
355 idxPath: integer;
356 idxSub, idxEntry: LongInt;
357 curDir: TMemDirectory;
358begin
359 path := SplitPath(APath);
360 curDir := self;
361 if path.Items[0].IsEmpty then
362 begin
363 idxPath := 1;
364 while Assigned(curDir.ParentDirectory) do curDir := curDir.ParentDirectory;
365 end
366 else
367 idxPath := 0;
368
369 while idxPath < path.Count-1 do
370 begin
371 idxSub := curDir.IndexOf(path[idxPath], ACaseSensitive);
372 if idxSub= -1 then
373 begin
374 curDir := nil;
375 break;
376 end;
377 curDir := curDir.Directory[idxSub];
378 inc(idxPath);
379 end;
380
381 if Assigned(curDir) and (idxPath < path.Count) then
382 begin
383 idxEntry := curDir.IndexOf(path[idxPath], ACaseSensitive);
384 if idxEntry = -1 then
385 result := nil
386 else
387 result := curDir.Entry[idxEntry] as TMemDirectoryEntry;
388 end
389 else
390 result := nil;
391
392 path.Free;
393end;
394
395procedure TMemDirectory.CopyTo(ADest: TMemDirectory; ARecursive: boolean);
396var
397 i, idxDir: Integer;
398 entryContent: TMemoryStream;
399begin
400 for i := 0 to Count-1 do
401 if IsDirectory[i] and ARecursive then
402 begin
403 idxDir := ADest.AddDirectory(Entry[i].Name,Entry[i].Extension);
404 Directory[i].CopyTo(ADest.Directory[idxDir], true);
405 end else
406 begin
407 entryContent := TMemoryStream.Create;
408 Entry[i].CopyTo(entryContent);
409 ADest.Add(Entry[i].Name,Entry[i].Extension,entryContent,false,true);
410 end;
411end;
412
413function TMemDirectory.SplitPath(APath: utf8string): TMemDirectoryPath;
414var idx,idxSlash: integer;
415begin
416 result := TMemDirectoryPath.Create;
417 idx := 1;
418 repeat
419 idxSlash := PosEx('/',APath,idx);
420 if idxSlash = 0 then
421 begin
422 result.Add(EntryFilename(copy(APath, idx, length(APath)-idx+1)));
423 break;
424 end else
425 begin
426 result.Add(EntryFilename(copy(APath, idx, idxSlash-idx)));
427 idx := idxSlash+1;
428 end;
429 until false;
430end;
431
432constructor TMemDirectory.Create(AParentDirectory: TMemDirectory);
433begin
434 inherited Create;
435 FParentDirectory := AParentDirectory;
436end;
437
438{ TMemDirectoryEntry }
439
440function TMemDirectoryEntry.GetIsCompressed: boolean;
441begin
442 result := (FFlags and MemDirectoryEntry_FlagCompressed) <> 0;
443end;
444
445function TMemDirectoryEntry.GetCompressedSize: int64;
446begin
447 if not IsDirectory and Assigned(FStream) then
448 result := FStream.Size
449 else
450 result := 0;
451end;
452
453function TMemDirectoryEntry.GetIsDirectory: boolean;
454begin
455 result := (FFlags and MemDirectoryEntry_FlagDirectory) <> 0;
456end;
457
458procedure TMemDirectoryEntry.SetIsCompressed(AValue: boolean);
459var compressedStream,decompressed: TMemoryStream;
460 compression: Tcompressionstream;
461begin
462 if AValue = IsCompressed then exit;
463
464 if Assigned(FStream) then
465 begin
466 if AValue then //compress
467 begin
468 compressedStream := TMemoryStream.Create;
469 compression := nil;
470 try
471 compression := Tcompressionstream.create(cldefault, compressedStream, true);
472 FStream.Position := 0;
473 compression.CopyFrom(FStream,FStream.Size);
474 FStream.Free;
475 FStream := compressedStream;
476 compressedStream := nil;
477 FFlags := FFlags xor MemDirectoryEntry_FlagCompressed;
478 finally
479 compression.Free;
480 compressedStream.Free;
481 end;
482 end else
483 begin //decompress
484 decompressed := TMemoryStream.Create;
485 try
486 InternalCopyTo(decompressed);
487 FStream.Free;
488 FStream := decompressed;
489 decompressed := nil;
490 FFlags := FFlags xor MemDirectoryEntry_FlagCompressed;
491 finally
492 decompressed.Free;
493 end;
494 end;
495 end else
496 FFlags := FFlags xor MemDirectoryEntry_FlagCompressed;
497end;
498
499function TMemDirectoryEntry.GetName: utf8string;
500begin
501 result := FName;
502end;
503
504procedure TMemDirectoryEntry.SetName(AValue: utf8string);
505begin
506 while AValue[length(AValue)] = '.' do delete(AValue, length(AValue), 1);
507 FName := AValue;
508end;
509
510function TMemDirectoryEntry.GetFileSize: int64;
511begin
512 if IsDirectory then
513 result := 0
514 else
515 Result:= FUncompressedSize;
516end;
517
518function TMemDirectoryEntry.GetExtension: utf8string;
519begin
520 Result:= FExtension;
521end;
522
523function TMemDirectoryEntry.InternalCopyTo(ADestination: TStream): int64;
524var
525 decomp: Tdecompressionstream;
526begin
527 if not Assigned(FStream) then exit(0);
528 if IsCompressed then
529 begin
530 FStream.Position := 0;
531 decomp := Tdecompressionstream.Create(FStream,true);
532 try
533 result := ADestination.CopyFrom(decomp,FUncompressedSize);
534 finally
535 decomp.Free;
536 end;
537 end else
538 begin
539 FStream.Position := 0;
540 result := ADestination.CopyFrom(FStream, FStream.Size);
541 end;
542end;
543
544function TMemDirectoryEntry.CopyTo(ADestination: TStream): int64;
545begin
546 if IsDirectory then exit(0);
547 result := InternalCopyTo(ADestination);
548end;
549
550constructor TMemDirectoryEntry.Create(AContainer: TMultiFileContainer; AFilename: TEntryFilename;
551 AUncompressedStream: TStream; AOwnStream: boolean);
552begin
553 CreateFromData(AContainer, AFilename, AUncompressedStream, AOwnStream, AUncompressedStream.Size, 0);
554end;
555
556constructor TMemDirectoryEntry.CreateFromData(AContainer: TMultiFileContainer; AFilename: TEntryFilename;
557 AStream: TStream; AOwnStream: boolean;
558 AUncompressedSize: int64; AFlags: Word);
559begin
560 inherited Create(AContainer);
561 Name := AFilename.Name;
562 FExtension:= AFilename.Extension;
563 if AOwnStream then
564 FStream := AStream
565 else
566 begin
567 FStream := TMemoryStream.Create;
568 AStream.Position:= 0;
569 FStream.CopyFrom(AStream, AStream.Size);
570 end;
571 FUncompressedSize:= AUncompressedSize;
572 FFlags:= AFlags;
573 FMemDirectory := nil;
574end;
575
576procedure TMemDirectoryEntry.SaveToEmbeddedStream(AEntryStream, ADataStream: TStream;
577 AStartPos: int64; out uncompressedSize: int64);
578var
579 entryStartPos: Int64;
580begin
581 if IsDirectory then
582 begin
583 if not Assigned(FMemDirectory) then
584 raise exception.Create('Directory not allocated');
585 FreeAndNil(FStream);
586 IsCompressed:= false;
587 entryStartPos := AEntryStream.Position;
588 FMemDirectory.SaveToEmbeddedStream(AEntryStream, ADataStream, AStartPos);
589 uncompressedSize:= AEntryStream.Position - entryStartPos;
590 end else
591 if Assigned(FStream) then
592 begin
593 FStream.Position:= 0;
594 AEntryStream.CopyFrom(FStream, FStream.Size);
595 uncompressedSize:= FUncompressedSize;
596 end;
597end;
598
599procedure TMemDirectoryEntry.LoadExtraFromEmbeddedStream(ADataStream: TStream;
600 AStartPos: int64);
601begin
602 if IsDirectory and Assigned(FStream) then
603 begin
604 IsCompressed:= false;
605 if not Assigned(FMemDirectory) then
606 FMemDirectory := TMemDirectory.Create(Container as TMemDirectory);
607 FMemDirectory.LoadFromEmbeddedStream(FStream, ADataStream, AStartPos);
608 FreeAndNil(FStream);
609 end;
610end;
611
612constructor TMemDirectoryEntry.CreateDirectory(AContainer: TMultiFileContainer;
613 AFilename: TEntryFilename);
614begin
615 Name := AFilename.Name;
616 FExtension:= AFilename.Extension;
617 FStream := nil;
618 FUncompressedSize:= 0;
619 FFlags := MemDirectoryEntry_FlagDirectory;
620 FContainer := AContainer;
621 FMemDirectory := TMemDirectory.Create(Container as TMemDirectory);
622end;
623
624destructor TMemDirectoryEntry.Destroy;
625begin
626 FStream.Free;
627 FMemDirectory.Free;
628 inherited Destroy;
629end;
630
631end.
632
Note: See TracBrowser for help on using the repository browser.