Statistics
| Branch: | Tag: | Revision:

root / UStreamHelper.pas

History | View | Annotate | Download (17.1 kB)

1
(*
2
 * CDDL HEADER START
3
 *
4
 * The contents of this file are subject to the terms of the
5
 * Common Development and Distribution License, Version 1.0 only
6
 * (the "License").  You may not use this file except in compliance
7
 * with the License.
8
 *
9
 * You can obtain a copy of the license at
10
 * http://www.opensource.org/licenses/cddl1.php.
11
 * See the License for the specific language governing permissions
12
 * and limitations under the License.
13
 *
14
 * When distributing Covered Code, include this CDDL HEADER in each
15
 * file and include the License file at
16
 * http://www.opensource.org/licenses/cddl1.php.  If applicable,
17
 * add the following below this CDDL HEADER, with the fields enclosed
18
 * by brackets "[]" replaced with your own identifying * information:
19
 *      Portions Copyright [yyyy] [name of copyright owner]
20
 *
21
 * CDDL HEADER END
22
 *
23
 *
24
 *      Portions Copyright 2007 Andreas Schneider
25
 *)
26
27
{@abstract(This unit contains procedures and classes to help with stream handling.
28
It can be used to ease copying of streams and to assist in writing and reading
29
specialized types to/from streams.)
30
31
@bold(Warning!!!)@br
32
Due to a problem with generics in FPC 2.2.0 I introduced @link(TStreamType) as
33
workaround to reference the actual type of stream used inside the
34
@link(TStreamWrapper).
35
36
@author(Andreas Schneider <aksdb@gmx.de>)
37
@created(2007-07-08)
38
@lastmod(2007-11-14)}
39
unit UStreamHelper;
40
41
{$mode objfpc}{$H+}
42
43
interface
44
45
uses
46
  Classes, RtlConsts, SysUtils;
47
48
type
49
  {@name is the stub for the method which will handle the OnProgress callbacks.
50
  @param(ATotal Specifies the complete size of the operation.)
51
  @param(ACurrent Specifies the current position during the operation.)}
52
  TOnProgressEvent = procedure(ATotal, ACurrent: Cardinal) of object;
53
  
54
  { TFifoStream }
55
  
56
  {@abstract(The @name contains special handling for queuing and dequeing. It is
57
  meant to be used as a network queue.)}
58
  TFifoStream = class(TStream)
59
    destructor Destroy; override;
60
  protected
61
    FMemory: Pointer;
62
    FSize, FRealSize, FPosition, FLockOffset: Longint;
63
    FCapacity: Longint;
64
    procedure SetCapacity(ANewCapacity: Longint);
65
    procedure SetPointer(APtr: Pointer; ASize: Longint);
66
    function GetOptimalCapacity(ANewCapacity: Longint): Longint;
67
    function Realloc(var NewCapacity: Longint): Pointer; virtual;
68
    property Capacity: Longint read FCapacity write SetCapacity;
69
  public
70
    function GetSize: Int64; override;
71
    function Read(var Buffer; ACount: Longint): Longint; override;
72
    function Write(const Buffer; ACount: Longint): Longint; override;
73
    function Seek(AOffset: Longint; AOrigin: Word): Longint; override;
74
    procedure LoadFromStream(AStream: TStream);
75
    procedure SaveToStream(AStream: TStream);
76
    procedure LoadFromFile(const FileName: string);
77
    procedure SaveToFile(const FileName: string);
78
    procedure SetSize(ANewSize: Longint); override;
79
    procedure Clear;
80
    procedure Dequeue(ACount: Longint);                                         //<Removes a specified number of bytes from the queue. @param(ACount The number of bytes to remove from the queue.)
81
    procedure Enqueue(const Buffer; ACount: Longint);                           //<Adds a specified number of bytes from a given buffer to the end of the queue. @param(Buffer The buffer containing the data to enqueue.) @param(ACount The number of bytes to enqueue from the buffer.)
82
    procedure Lock(AOffset, ASize: Longint);                                    //<Restricts the visible area of the stream. @param(AOffset is the starting position of the area.) @param(ASize The size of the area.)
83
    procedure Unlock;                                                           //<Removes the restrictions from the stream and resets the visible area to the original size.
84
    property Memory: Pointer read FMemory;
85
  end;
86
  
87
88
  { TStreamWrapper }
89
90
  {The @name is just a placeholder for the type used in the
91
  @link(TStreamWrapper). It is currently in place to work around a problem with
92
  generics in fpc 2.2.0}
93
  TStreamType = TFifoStream;
94
  //generic TStreamWrapper<TStreamType> = class(TObject{, IStream})
95
  {@abstract(@name implements @link(IStream) and offers a bunch of functions to
96
  ease reading and writing special types (like @link(Integer)s or @link(String)s.))}
97
  TStreamWrapper = class(TObject)
98
    constructor Create(AStream: TStreamType; AOwnsStream: Boolean = True);      //<Creates a new instance of @classname. @param(AStream The underlying stream to perform the actual operations on.) @param(AOwnsStream Defines wheather to free the stream on destruction of @classname or not. Defaults to @false.)
99
    destructor Destroy; override;                                               //<Is called when the current instance of @classname is destroyed. If it owns the underlying stream it is destroyed aswell.
100
  protected
101
    FStream: TStream;
102
    FOwnsStream: Boolean;
103
    function GetStream: TStreamType;
104
    procedure SetStream(AStream: TStreamType);
105
  public
106
    property Raw: TStreamType read GetStream write SetStream;                   //<Provides raw access to the underlying stream. Useful for manipulation of the stream position and other class specific calls.
107
    
108
    function ReadBoolean: Boolean;                                              //<Implementation of @link(IStream.ReadBoolean).
109
    function ReadByte: Byte;                                                    //<Implementation of @link(IStream.ReadByte).
110
    function ReadCardinal: Cardinal;                                            //<Implementation of @link(IStream.ReadCardinal).
111
    function ReadInteger: Integer;                                              //<Implementation of @link(IStream.ReadInteger).
112
    function ReadInt64: Int64;                                                  //<Implementation of @link(IStream.ReadInt64).
113
    function ReadSmallInt: SmallInt;                                            //<Implementation of @link(IStream.ReadSmallInt).
114
    function ReadWord: Word;                                                    //<Implementation of @link(IStream.ReadWord).
115
    function ReadString: string;                                                //<Implementation of @link(IStream.ReadString).
116
    function ReadStringFixed(ALength: Integer): string;                         //<Implementation of @link(IStream.ReadStringFixed).
117
    procedure WriteBoolean(AValue: Boolean);                                    //<Implementation of @link(IStream.WriteBoolean).
118
    procedure WriteByte(AValue: Byte);                                          //<Implementation of @link(IStream.WriteByte).
119
    procedure WriteCardinal(AValue: Cardinal);                                  //<Implementation of @link(IStream.WriteCardinal).
120
    procedure WriteInteger(AValue: Integer);                                    //<Implementation of @link(IStream.WriteInteger).
121
    procedure WriteInt64(AValue: Int64);                                        //<Implementation of @link(IStream.WriteInt64).
122
    procedure WriteSmallInt(AValue: SmallInt);                                  //<Implementation of @link(IStream.WriteSmallInt).
123
    procedure WriteWord(AValue: Word);                                          //<Implementation of @link(IStream.WriteWord).
124
    procedure WriteString(AValue: string);                                      //<Implementation of @link(IStream.WriteString).
125
    procedure WriteStringFixed(AValue: string; ALength: Integer);               //<Implementation of @link(IStream.WriteStringFixed).
126
127
    function Read(ABuffer: PByte; ACount: Cardinal): Cardinal;                  //<Implementation of @link(IStream.Read).
128
    function Write(ABuffer: PByte; ACount: Cardinal): Cardinal;                 //<Implementation of @link(IStream.Write).
129
    
130
    procedure Skip(ACount: Cardinal);                                           //<Implementation of @link(IStream.Skip).
131
  end;
132
133
{@name is used to have a progress (see @link(TOnProgressEvent)) for a copy
134
action of the content of one stream into another. This is especially useful
135
for writing and reading to @link(TFileStream).
136
@param(ASource The stream from which the content is copied.)
137
@param(ATarget The stream to which the content is copied.)
138
@param(ACount Specifies the amount to copy. 0 means, that the whole stream is processed.)
139
@param(AOnProgress The callback for the @link(TOnProgressEvent). Defaults to @nil.)
140
@returns(The amount of bytes copied.)}
141
function StreamCopy(ASource, ATarget: TStream; ACount: Int64; AOnProgress: TOnProgressEvent = nil): Int64;
142
143
implementation
144
145
function StreamCopy(ASource, ATarget: TStream; ACount: Int64; AOnProgress: TOnProgressEvent = nil): Int64;
146
var
147
  i, targetSize: Int64;
148
  buffer: array[0..4095] of byte;
149
begin
150
  Result := 0;
151
  if (ACount = 0) then
152
  begin
153
    //This WILL fail for non-seekable streams...
154
    ASource.Position := 0;
155
    ACount := ASource.Size;
156
  end;
157
  targetSize := ACount;
158
  while ACount > 0 do
159
  begin
160
    if (ACount > SizeOf(buffer)) then
161
      i := SizeOf(Buffer)
162
    else
163
      i := ACount;
164
    i := ASource.Read(buffer, i);
165
    i := ATarget.Write(buffer, i);
166
    if i = 0 then break;
167
    Dec(ACount, i);
168
    Inc(Result, i);
169
    if Assigned(AOnProgress) then
170
      AOnProgress(targetSize, Result);
171
  end;
172
end;
173
174
{ TFifoStream }
175
176
const TMSGrow = 4096; { Use 4k blocks. }
177
178
destructor TFifoStream.Destroy;
179
begin
180
  Clear;
181
  inherited Destroy;
182
end;
183
184
procedure TFifoStream.SetCapacity(ANewCapacity: Longint);
185
begin
186
  SetPointer(Realloc(ANewCapacity), FSize);
187
  FCapacity := ANewCapacity;
188
end;
189
190
procedure TFifoStream.SetPointer(APtr: Pointer; ASize: Longint);
191
begin
192
  FMemory := APtr;
193
  FSize := ASize;
194
end;
195
196
function TFifoStream.GetOptimalCapacity(ANewCapacity: Longint): Longint;
197
begin
198
  Result := ANewCapacity;
199
  if Result <= 0 then
200
    Result := 0
201
  else
202
  begin
203
    // if growing, grow at least a quarter
204
    if (Result > FCapacity) and (Result < (5 * FCapacity) div 4) then
205
      Result := (5 * FCapacity) div 4;
206
    // round off to block size.
207
    Result := (Result + (TMSGrow-1)) and not (TMSGROW-1);
208
  end;
209
end;
210
211
function TFifoStream.Realloc(var NewCapacity: Longint): Pointer;
212
begin
213
  NewCapacity := GetOptimalCapacity(NewCapacity);
214
  // Only now check !
215
  if NewCapacity = FCapacity then
216
    Result := FMemory
217
  else
218
  begin
219
    Result := ReAllocMem(FMemory, NewCapacity);
220
    if (Result = nil) and (NewCapacity > 0) then
221
      raise EStreamError.Create(SMemoryStreamError);
222
  end;
223
end;
224
225
function TFifoStream.GetSize: Int64;
226
begin
227
  Result := FSize;
228
end;
229
230
function TFifoStream.Read(var Buffer; ACount: Longint): Longint;
231
begin
232
  Result := 0;
233
  If (FSize > 0) and (FPosition - FLockOffset < FSize) then
234
  begin
235
    Result := FSize - (FPosition - FLockOffset);
236
    if Result > ACount then Result := ACount;
237
    Move((FMemory + FPosition)^, Buffer, Result);
238
    FPosition := FPosition + Result;
239
  end;
240
end;
241
242
function TFifoStream.Write(const Buffer; ACount: Longint): Longint;
243
var
244
  NewPos: Longint;
245
begin
246
  Unlock;
247
  if ACount = 0 then
248
    Exit(0);
249
  NewPos := FPosition + ACount;
250
  if NewPos > FSize then
251
  begin
252
    if NewPos > FCapacity then
253
      SetCapacity(NewPos);
254
    FSize := NewPos;
255
  end;
256
  System.Move(Buffer, (FMemory + FPosition)^, ACount);
257
  FPosition := NewPos;
258
  Result := ACount;
259
end;
260
261
function TFifoStream.Seek(AOffset: Longint; AOrigin: Word): Longint;
262
begin
263
  case AOrigin of
264
    soFromBeginning : FPosition := AOffset + FLockOffset;
265
    soFromEnd       : FPosition := FSize + AOffset + FLockOffset;
266
    soFromCurrent   : FPosition := FPosition + AOffset;
267
  end;
268
  Result := FPosition - FLockOffset;
269
end;
270
271
procedure TFifoStream.LoadFromStream(AStream: TStream);
272
begin
273
  Unlock;
274
  AStream.Position := 0;
275
  SetSize(AStream.Size);
276
  If FSize > 0 then AStream.ReadBuffer(FMemory^,FSize);
277
end;
278
279
procedure TFifoStream.SaveToStream(AStream: TStream);
280
begin
281
  if FSize > 0 then AStream.WriteBuffer((FMemory + FLockOffset)^, FSize);
282
end;
283
284
procedure TFifoStream.LoadFromFile(const FileName: string);
285
var
286
  S: TFileStream;
287
begin
288
  S := TFileStream.Create(FileName, fmOpenRead);
289
  try
290
    LoadFromStream(S);
291
  finally
292
    S.free;
293
  end;
294
end;
295
296
procedure TFifoStream.SaveToFile(const FileName: string);
297
var
298
  S: TFileStream;
299
begin
300
  S := TFileStream.Create(FileName, fmCreate);
301
  try
302
    SaveToStream(S);
303
  finally
304
    S.free;
305
  end;
306
end;
307
308
procedure TFifoStream.SetSize(ANewSize: Longint);
309
begin
310
  Unlock;
311
  SetCapacity(ANewSize);
312
  FSize := ANewSize;
313
  if FPosition > FSize then
314
    FPosition := FSize;
315
end;
316
317
procedure TFifoStream.Clear;
318
begin
319
  FSize := 0;
320
  FRealSize := 0;
321
  FPosition := 0;
322
  FLockOffset := 0;
323
  SetCapacity(0);
324
end;
325
326
procedure TFifoStream.Dequeue(ACount: Longint);
327
var
328
  newCapacity, newSize: Longint;
329
  queue, newMemory: Pointer;
330
begin
331
  Unlock;
332
  if ACount >= FSize then
333
  begin
334
    Size := 0;
335
    Exit;
336
  end;
337
  
338
  queue := FMemory + ACount;
339
  newSize := FSize - ACount;
340
  
341
  newCapacity := GetOptimalCapacity(newSize);
342
  if newCapacity <> FCapacity then
343
  begin
344
    newMemory := GetMem(newCapacity);
345
    System.Move(queue^, newMemory^, newSize);
346
    if (newMemory = nil) and (newCapacity > 0) then
347
      raise EStreamError.Create(SMemoryStreamError);
348
    FreeMem(FMemory);
349
    FMemory := newMemory;
350
    FCapacity := newCapacity;
351
  end else
352
    System.Move(queue^, FMemory^, newSize);
353
354
  FSize := newSize;
355
  if FPosition > ACount then
356
    Dec(FPosition, ACount)
357
  else
358
    FPosition := 0;
359
end;
360
361
procedure TFifoStream.Enqueue(const Buffer; ACount: Longint);
362
var
363
  oldPos: Int64;
364
begin
365
  Unlock;
366
  oldPos := FPosition;
367
  FPosition := FSize;
368
  Write(Buffer, ACount);
369
  FPosition := oldPos;
370
end;
371
372
procedure TFifoStream.Lock(AOffset, ASize: Longint);
373
begin
374
  if (FLockOffset <> 0) or (FRealSize <> 0) then Exit;
375
  FLockOffset := AOffset;
376
  FRealSize := FSize;
377
  FSize := ASize;
378
end;
379
380
procedure TFifoStream.Unlock;
381
begin
382
  if (FLockOffset = 0) and (FRealSize = 0) then Exit;
383
  FLockOffset := 0;
384
  FSize := FRealSize;
385
  FRealSize := 0;
386
end;
387
388
{ TStreamWrapper }
389
390
constructor TStreamWrapper.Create(AStream: TStreamType; AOwnsStream: Boolean);
391
begin
392
  inherited Create;
393
  FStream := TStream(AStream);
394
  FOwnsStream := AOwnsStream;
395
end;
396
397
destructor TStreamWrapper.Destroy;
398
begin
399
  if FOwnsStream and Assigned(FStream) then FreeAndNil(FStream);
400
  inherited Destroy;
401
end;
402
403
function TStreamWrapper.GetStream: TStreamType;
404
begin
405
  Result := TStreamType(FStream);
406
end;
407
408
procedure TStreamWrapper.SetStream(AStream: TStreamType);
409
begin
410
  FStream := TStream(AStream);
411
end;
412
413
function TStreamWrapper.ReadBoolean: Boolean;
414
begin
415
  if not Assigned(FStream) then Exit(False);
416
  FStream.Read(Result, SizeOf(Boolean));
417
end;
418
419
function TStreamWrapper.ReadByte: Byte;
420
begin
421
  if not Assigned(FStream) then Exit(0);
422
  FStream.Read(Result, SizeOf(Byte));
423
end;
424
425
function TStreamWrapper.ReadCardinal: Cardinal;
426
begin
427
  if not Assigned(FStream) then Exit(0);
428
  FStream.Read(Result, SizeOf(Cardinal));
429
end;
430
431
function TStreamWrapper.ReadInteger: Integer;
432
begin
433
  if not Assigned(FStream) then Exit(0);
434
  FStream.Read(Result, SizeOf(Integer));
435
end;
436
437
function TStreamWrapper.ReadInt64: Int64;
438
begin
439
  if not Assigned(FStream) then Exit(0);
440
  FStream.Read(Result, SizeOf(Int64));
441
end;
442
443
function TStreamWrapper.ReadSmallInt: SmallInt;
444
begin
445
  if not Assigned(FStream) then Exit(0);
446
  FStream.Read(Result, SizeOf(SmallInt));
447
end;
448
449
function TStreamWrapper.ReadWord: Word;
450
begin
451
  if not Assigned(FStream) then Exit(0);
452
  FStream.Read(Result, SizeOf(Word));
453
end;
454
455
function TStreamWrapper.ReadString: string;
456
begin
457
  if not Assigned(FStream) then Exit('');
458
  Result := ReadStringFixed(ReadInteger);
459
end;
460
461
function TStreamWrapper.ReadStringFixed(ALength: Integer): string;
462
begin
463
  if not Assigned(FStream) then Exit('');
464
  SetLength(Result, ALength);
465
  FStream.Read(PChar(Result)^, ALength);
466
end;
467
468
procedure TStreamWrapper.WriteBoolean(AValue: Boolean);
469
begin
470
  if not Assigned(FStream) then Exit;
471
  FStream.Write(AValue, SizeOf(Boolean));
472
end;
473
474
procedure TStreamWrapper.WriteByte(AValue: Byte);
475
begin
476
  if not Assigned(FStream) then Exit;
477
  FStream.Write(AValue, SizeOf(Byte));
478
end;
479
480
procedure TStreamWrapper.WriteCardinal(AValue: Cardinal);
481
begin
482
  if not Assigned(FStream) then Exit;
483
  FStream.Write(AValue, SizeOf(Cardinal));
484
end;
485
486
procedure TStreamWrapper.WriteInteger(AValue: Integer);
487
begin
488
  if not Assigned(FStream) then Exit;
489
  FStream.Write(AValue, SizeOf(Integer));
490
end;
491
492
procedure TStreamWrapper.WriteInt64(AValue: Int64);
493
begin
494
  if not Assigned(FStream) then Exit;
495
  FStream.Write(AValue, SizeOf(Int64));
496
end;
497
498
procedure TStreamWrapper.WriteSmallInt(AValue: SmallInt);
499
begin
500
  if not Assigned(FStream) then Exit;
501
  FStream.Write(AValue, SizeOf(SmallInt));
502
end;
503
504
procedure TStreamWrapper.WriteWord(AValue: Word);
505
begin
506
  if not Assigned(FStream) then Exit;
507
  FStream.Write(AValue, SizeOf(Word));
508
end;
509
510
procedure TStreamWrapper.WriteString(AValue: string);
511
var
512
  stringLength: Integer;
513
begin
514
  if not Assigned(FStream) then Exit;
515
  stringLength := Length(AValue);
516
  WriteInteger(stringLength);
517
  WriteStringFixed(AValue, stringLength);
518
end;
519
520
procedure TStreamWrapper.WriteStringFixed(AValue: string; ALength: Integer);
521
begin
522
  if not Assigned(FStream) then Exit;
523
  FStream.Write(PChar(AValue)^, ALength);
524
end;
525
526
function TStreamWrapper.Read(ABuffer: PByte; ACount: Cardinal): Cardinal;
527
begin
528
  Result := FStream.Read(ABuffer^, ACount);
529
end;
530
531
function TStreamWrapper.Write(ABuffer: PByte; ACount: Cardinal): Cardinal;
532
begin
533
  Result := FStream.Write(ABuffer^, ACount);
534
end;
535
536
procedure TStreamWrapper.Skip(ACount: Cardinal);
537
begin
538
  FStream.Seek(ACount, soFromCurrent);
539
end;
540
541
end.
542