(* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * http://www.opensource.org/licenses/cddl1.php. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * http://www.opensource.org/licenses/cddl1.php. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Portions Copyright 2009 Andreas Schneider *) unit UMulProvider; {$mode objfpc}{$H+} interface uses SysUtils, Classes, UBufferedStreams, UMulBlock, UGenericIndex; type TOnProgressEvent = procedure(Total, Current: Integer) of object; { TMulEventHandler } TMulEventHandler = class constructor Create; destructor Destroy; override; protected FEvents: TList; public procedure RegisterEvent(AEvent: TMulBlockChanged); procedure UnregisterEvent(AEvent: TMulBlockChanged); procedure FireEvents(ABlock: TMulBlock); end; { TMulProvider } TMulProvider = class constructor Create; overload; virtual; constructor Create(AData: TStream; AReadOnly: Boolean = False); overload; virtual; constructor Create(AData: string; AReadOnly: Boolean = False); overload; virtual; destructor Destroy; override; protected FData: TStream; FOwnsData: Boolean; FReadOnly: Boolean; FChangeEvents: TMulEventHandler; FFinishedEvents: TMulEventHandler; function CalculateOffset(AID: Integer): Integer; virtual; abstract; function GetData(AID, AOffset: Integer): TMulBlock; virtual; abstract; procedure SetData(AID, AOffset: Integer; ABlock: TMulBlock); virtual; procedure OnChanged(ABlock: TMulBlock); procedure OnFinished(ABlock: TMulBlock); public function GetBlock(AID: Integer): TMulBlock; virtual; procedure SetBlock(AID: Integer; ABlock: TMulBlock); virtual; procedure RegisterOnChangeEvent(AEvent: TMulBlockChanged); procedure UnregisterOnChangeEvent(AEvent: TMulBlockChanged); procedure RegisterOnFinishedEvent(AEvent: TMulBlockChanged); procedure UnregisterOnFinishedEvent(AEvent: TMulBlockChanged); property Block[ID: Integer]: TMulBlock read GetBlock write SetBlock; property Data: TStream read FData; end; { TIndexedMulProvider } TIndexedMulProvider = class(TMulProvider) constructor Create(AData, AIndex: TStream; AReadOnly: Boolean = False); overload; virtual; constructor Create(AData, AIndex: string; AReadOnly: Boolean = False); overload; virtual; destructor Destroy; override; protected FIndex: TBufferedReader; FEntryCount: Cardinal; function CalculateIndexOffset(AID: Integer): Integer; virtual; function GetData(AID: Integer; AIndex: TGenericIndex): TMulBlock; reintroduce; virtual; abstract; procedure SetData(AID: Integer; AIndex: TGenericIndex; ABlock: TMulBlock); reintroduce; virtual; function GetVarious(AID: Integer; ABlock: TMulBlock; ADefault: Integer): Integer; virtual; public function GetBlock(AID: Integer): TMulBlock; override; procedure GetBlockEx(AID: Integer; var ABlock: TMulBlock; var AIndex: TGenericIndex); virtual; procedure SetBlock(AID: Integer; ABlock: TMulBlock); override; function Exists(AID: Integer): Boolean; virtual; procedure Defragment(ATempStream: TStream; AOnProgress: TOnProgressEvent = nil); virtual; property Index: TBufferedReader read FIndex; property EntryCount: Cardinal read FEntryCount; end; implementation type PMethod = ^TMethod; { TMulEventHandler } constructor TMulEventHandler.Create; begin inherited; FEvents := TList.Create; end; destructor TMulEventHandler.Destroy; var i: Integer; begin if Assigned(FEvents) then begin for i := 0 to FEvents.Count - 1 do Dispose(PMethod(FEvents.Items[i])); FreeAndNil(FEvents); end; inherited Destroy; end; procedure TMulEventHandler.FireEvents(ABlock: TMulBlock); var i: Integer; begin for i := 0 to FEvents.Count - 1 do TMulBlockChanged(FEvents.Items[i]^)(ABlock); end; procedure TMulEventHandler.RegisterEvent(AEvent: TMulBlockChanged); var eventInfo: PMethod; begin UnregisterEvent(AEvent); New(eventInfo); eventInfo^.Code := TMethod(AEvent).Code; eventInfo^.Data := TMethod(AEvent).Data; FEvents.Add(eventInfo); end; procedure TMulEventHandler.UnregisterEvent(AEvent: TMulBlockChanged); var i: Integer; function RemoveEntry: Boolean; begin Dispose(PMethod(FEvents.Items[i])); FEvents.Delete(i); Result := True; end; begin i := 0; while (i < FEvents.Count) and ((TMethod(AEvent).Code <> TMethod(FEvents.Items[i]^).Code) or (TMethod(AEvent).Data <> TMethod(FEvents.Items[i]^).Data) or not RemoveEntry) do Inc(i); end; { TMulProvider } constructor TMulProvider.Create(AData: TStream; AReadOnly: Boolean = False); begin Create; FData := AData; FOwnsData := False; FReadOnly := AReadOnly; end; constructor TMulProvider.Create(AData: string; AReadOnly: Boolean = False); var mode: Word; begin Create; if AReadOnly then mode := fmOpenRead or fmShareDenyWrite else mode := fmOpenReadWrite or fmShareDenyWrite; FData := TFileStream.Create(AData, mode); FOwnsData := True; FReadOnly := AReadOnly; end; constructor TMulProvider.Create; begin inherited; FChangeEvents := TMulEventHandler.Create; FFinishedEvents := TMulEventHandler.Create; end; destructor TMulProvider.Destroy; begin if FOwnsData and Assigned(FData) then FreeAndNil(FData); if Assigned(FChangeEvents) then FreeAndNil(FChangeEvents); if Assigned(FFinishedEvents) then FreeAndNil(FFinishedEvents); inherited; end; function TMulProvider.GetBlock(AID: Integer): TMulBlock; begin Result := GetData(AID, CalculateOffset(AID)); Result.OnChanged := @OnChanged; Result.OnFinished := @OnFinished; end; procedure TMulProvider.OnChanged(ABlock: TMulBlock); begin SetBlock(ABlock.ID, ABlock); FChangeEvents.FireEvents(ABlock); end; procedure TMulProvider.OnFinished(ABlock: TMulBlock); begin FFinishedEvents.FireEvents(ABlock); ABlock.Free; end; procedure TMulProvider.RegisterOnChangeEvent(AEvent: TMulBlockChanged); begin FChangeEvents.RegisterEvent(AEvent); end; procedure TMulProvider.RegisterOnFinishedEvent(AEvent: TMulBlockChanged); begin FFinishedEvents.RegisterEvent(AEvent); end; procedure TMulProvider.SetBlock(AID: Integer; ABlock: TMulBlock); begin if FReadOnly then Exit; SetData(AID, CalculateOffset(AID), ABlock); end; procedure TMulProvider.SetData(AID, AOffset: Integer; ABlock: TMulBlock); begin if FReadOnly then Exit; FData.Position := AOffset; ABlock.Write(FData); end; procedure TMulProvider.UnregisterOnChangeEvent(AEvent: TMulBlockChanged); begin FChangeEvents.UnregisterEvent(AEvent); end; procedure TMulProvider.UnregisterOnFinishedEvent(AEvent: TMulBlockChanged); begin FFinishedEvents.UnregisterEvent(AEvent); end; { TIndexedMulProvider } function TIndexedMulProvider.CalculateIndexOffset(AID: Integer): Integer; begin Result := 12 * AID; end; constructor TIndexedMulProvider.Create(AData, AIndex: TStream; AReadOnly: Boolean = False); begin inherited Create(AData, AReadOnly); FIndex := TBufferedReader.Create(AIndex); FEntryCount := AIndex.Size div 12; end; constructor TIndexedMulProvider.Create(AData, AIndex: string; AReadOnly: Boolean = False); var mode: Word; begin inherited Create(AData, AReadOnly); if AReadOnly then mode := fmOpenRead or fmShareDenyWrite else mode := fmOpenReadWrite or fmShareDenyWrite; FIndex := TBufferedReader.Create(TFileStream.Create(AIndex, mode), True); FEntryCount := FIndex.Size div 12; end; procedure TIndexedMulProvider.Defragment(ATempStream: TStream; AOnProgress: TOnProgressEvent = nil); var genericIndex: TGenericIndex; begin if FReadOnly then Exit; ATempStream.Size := FData.Size; ATempStream.Position := 0; FIndex.Position := 0; while FIndex.Position < FIndex.Size do begin genericIndex := TGenericIndex.Create(FIndex); if genericIndex.Lookup > -1 then begin FData.Position := genericIndex.Lookup; genericIndex.Lookup := ATempStream.Position; ATempStream.CopyFrom(FData, genericIndex.Size); FIndex.Seek(-12, soFromCurrent); genericIndex.Write(FIndex); end; genericIndex.Free; if Assigned(AOnProgress) and (FIndex.Position mod 1200 = 0) then AOnProgress(FIndex.Size, FIndex.Position); end; FData.Size := ATempStream.Position; FData.Position := 0; ATempStream.Position := 0; FData.CopyFrom(ATempStream, FData.Size); end; destructor TIndexedMulProvider.Destroy; begin FreeAndNil(FIndex); inherited Destroy; end; function TIndexedMulProvider.Exists(AID: Integer): Boolean; var genericIndex: TGenericIndex; begin FIndex.Position := CalculateIndexOffset(AID); genericIndex := TGenericIndex.Create(FIndex); Result := (genericIndex.Lookup > -1) and (genericIndex.Size > 0); genericIndex.Free; end; function TIndexedMulProvider.GetBlock(AID: Integer): TMulBlock; var genericIndex: TGenericIndex; begin GetBlockEx(AID, Result, genericIndex); genericIndex.Free; end; procedure TIndexedMulProvider.GetBlockEx(AID: Integer; var ABlock: TMulBlock; var AIndex: TGenericIndex); begin FIndex.Position := CalculateIndexOffset(AID); AIndex := TGenericIndex.Create(FIndex); ABlock := GetData(AID, AIndex); ABlock.OnChanged := @OnChanged; ABlock.OnFinished := @OnFinished; end; function TIndexedMulProvider.GetVarious(AID: Integer; ABlock: TMulBlock; ADefault: Integer): Integer; begin Result := ADefault; end; procedure TIndexedMulProvider.SetBlock(AID: Integer; ABlock: TMulBlock); var genericIndex: TGenericIndex; begin if FReadOnly then Exit; FIndex.Position := CalculateIndexOffset(AID); genericIndex := TGenericIndex.Create(FIndex); SetData(AID, genericIndex, ABlock); FIndex.Position := CalculateIndexOffset(AID); genericIndex.Various := GetVarious(AID, ABlock, genericIndex.Various); genericIndex.Write(FIndex); genericIndex.Free; end; procedure TIndexedMulProvider.SetData(AID: Integer; AIndex: TGenericIndex; ABlock: TMulBlock); var size: Integer; begin if FReadOnly then Exit; size := ABlock.GetSize; if size = 0 then begin AIndex.Lookup := -1; AIndex.Various := -1; end else if (size > AIndex.Size) or (AIndex.Lookup < 0) then begin FData.Position := FData.Size; AIndex.Lookup := FData.Position; ABlock.Write(FData); end else begin FData.Position := AIndex.Lookup; ABlock.Write(FData); end; AIndex.Size := size; end; end.