unit LineStream; {$mode ObjFPC}{$H+} interface uses Classes, SysUtils, g_node_splay; type PLineNode=^TLineNode; TLineNode=object pLeft :PLineNode; pRight:PLineNode; FIndex:Integer; FData :record end; function c(n1,n2:PLineNode):Integer; static; end; TLineCache=specialize TNodeSplay; TLineStream = Class FStream :TStream; FLineCache:TLineCache; FLineStart:Int64; FLine__End:Int64; FLineIndex:Integer; FLineCount:Integer; Constructor Create(Stream:TStream); Destructor Destroy; override; procedure Reset(full:Boolean); function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; Function ReadForward (var ABuffer; ACount : LongInt) : Integer; Function ReadBackward(var ABuffer; ACount : LongInt) : Integer; Function GetLine:RawByteString; Function NextLine:Boolean; Function PrevLine:Boolean; function GetCount: integer; function GetCache(Index: integer;var R:RawByteString): Boolean; procedure AddCache(Index: integer;const R:RawByteString); procedure DelCache(Index: integer); procedure ClearCache; function Get(Index: integer): RawByteString; function Update:Integer; end; implementation function TLineNode.c(n1,n2:PLineNode):Integer; begin Result:=Integer(n1^.FIndex>n2^.FIndex)-Integer(n1^.FIndex0) then begin FStream.Seek(-Result,soCurrent); end; end; Function TLineStream.GetLine:RawByteString; var s:Int64; i:Integer; begin Result:=''; if (FLineStart<0) then Exit; s:=FLine__End; if (s<0) then s:=FStream.Size; i:=s-FLineStart; if (i=0) then Exit; SetLength(Result,i); Seek(FLineStart,soBeginning); i:=ReadForward(Pchar(Result)^,i); if (i<=0) then i:=0; while (i>0) do begin case Result[i] of #13:; #10:; else Break; end; Dec(i); end; SetLength(Result,i); end; Function TLineStream.NextLine:Boolean; var f:Int64; i:Integer; fv,pv,ch:AnsiChar; begin Result:=True; fv:=#0; pv:=#0; ch:=#0; //end line if (FLine__End>=0) then begin Seek(FLine__End,soBeginning); end else begin Exit(False); end; //read prev if (FLineIndex>=0) then begin Seek(-1,soCurrent); i:=ReadForward(fv,1); if (i<=0) then Exit(False); end; f:=FLineStart; if (FLineIndex<0) or (fv=#13) or (fv=#10) then begin FLineStart:=FStream.Position; end; repeat i:=ReadForward(ch,1); if (i<=0) then begin if (f=-1) or (FLineStart=f) then Exit(False); Break; end; //CRLF if (pv=#13) and (ch=#10) then begin Break; end else if (pv=#13) or (pv=#10) then begin Seek(-1,soCurrent); Break; end else if (fv=#13) and (ch=#10) then begin // end else begin pv:=ch; end; fv:=#0; until false; FLine__End:=FStream.Position; Inc(FLineIndex); end; Function TLineStream.PrevLine:Boolean; var i:Integer; pv,ch:AnsiChar; begin Result:=True; pv:=#0; ch:=#0; //start line if (FLineStart>=0) then begin Seek(FLineStart,soBeginning); FLine__End:=FLineStart; end else begin Exit(False); end; //prev caret repeat i:=ReadBackward(ch,1); if (i<=0) then begin Reset(False); Exit(False); end; //LFCR if (pv=#10) and (ch=#13) then begin Break; end else if (pv=#13) or (pv=#10) then begin Seek(1,soCurrent); Break; end; pv:=ch; until false; //read line repeat i:=ReadBackward(ch,1); if (i<=0) then Break; if (ch=#13) or (ch=#10) then begin Seek(1,soCurrent); Break; end; until false; FLineStart:=FStream.Position; Dec(FLineIndex); end; function TLineStream.GetCount: integer; begin if (FLineCount<=0) then begin while NextLine do; FLineCount:=FLineIndex+1; end; Result:=FLineCount; end; function TLineStream.GetCache(Index: integer;var R:RawByteString): Boolean; var _node:TLineNode; node :PLineNode; begin Result:=False; if (Index<0) then Exit; _node:=Default(TLineNode); _node.FIndex:=Index; node:=FLineCache.Find(@_node); if (node=nil) then Exit(); R:=PChar(@node^.FData); Result:=True; end; procedure TLineStream.AddCache(Index: integer;const R:RawByteString); var node:PLineNode; begin if (Index>=FLineCount) then Exit; node:=AllocMem(sizeof(TLineNode)+Length(R)+1); node^.FIndex:=Index; Move(PChar(R)^,PChar(@node^.FData)^,Length(R)+1); FLineCache.Insert(node); end; procedure TLineStream.DelCache(Index: integer); var _node:TLineNode; node :PLineNode; begin if (Index<0) then Exit; _node:=Default(TLineNode); _node.FIndex:=Index; node:=FLineCache.Find(@_node); if (node=nil) then Exit(); FLineCache.Delete(node); FreeMem(node); end; procedure TLineStream.ClearCache; var node:PLineNode; begin node:=FLineCache.Min; while (node<>nil) do begin FLineCache.Delete(node); FreeMem(node); node:=FLineCache.Min; end; end; function TLineStream.Get(Index: integer): RawByteString; label _next; begin Result:=''; if (Index<0) then Exit; if GetCache(Index,Result) then Exit; if (FLineIndex=Index) then begin Result:=GetLine; AddCache(Index,Result); Exit; end; if (FLineIndex>Index) then begin if (Index<=(FLineIndex div 2)) then begin Reset(False); goto _next; end; while PrevLine do begin if (FLineIndex=Index) then begin Result:=GetLine; AddCache(Index,Result); Exit; end; end; end else begin _next: while NextLine do begin if (FLineIndex=Index) then begin Result:=GetLine; AddCache(Index,Result); Exit; end; end; end; end; function TLineStream.Update:Integer; var i:Integer; e:Int64; begin e:=FLine__End; i:=GetCount; FLineCount:=0; PrevLine; Result:=GetCount-i; if (e<>FLine__End) then begin DelCache(i-1); end; end; end.