
{******************************************}
{                                          }
{             FastReport v4.0              }
{    HTTP Report Server Session Manager    }
{                                          }
{         Copyright (c) 1998-2011          }
{          by Alexander Fediachov,         }
{             Fast Reports Inc.            }
{                                          }
{******************************************}

unit frxServerSessionManager;

{$I frx.inc}

interface

uses
  Windows, SysUtils, Classes, Forms, ScktComp, frxServerReports;

type
  TfrxSessionItem = class(TObject)
  private
    FActive: Boolean;
    FCompleted: Boolean;
    FName: String;
    FReportThread: TfrxReportSession;
    FSessionId: String;
    FSocket: TCustomWinSocket;
    FTimeComplete: TDateTime;
    FTimeCreated: TDateTime;
  public
    constructor Create;
    destructor Destroy; override;

    property Active: Boolean read FActive write FActive;
    property SessionId: String read FSessionId write FSessionId;
    property Socket: TCustomWinSocket read FSocket write FSocket;
    property Completed: Boolean read FCompleted write FCompleted;
    property TimeCreated: TDateTime read FTimeCreated write FTimeCreated;
    property TimeComplete: TDateTime read FTimeComplete write FTimeComplete;
    property FileName: String read FName write FName;
    property ReportThread: TfrxReportSession read FReportThread
      write FReportThread;
  end;

  TfrxSessionManager = class(TThread)
  private
    FCleanUpTimeOut: Integer;
    FSession: TfrxSessionItem;
    FSessionList: TList;
    FSessionPath: String;
    FShutDown: Boolean;
    FThreadActive: Boolean;
    function CleanUpSession(SessionId: String): Boolean;
    procedure Clear;
    procedure DeleteSession;
    procedure DeleteSessionFolder(const DirName: String);
    procedure SetSessionPath(const Value: String);
    function GetCount: Integer;
  protected
    procedure Execute; override;
  public
    constructor Create;
    destructor Destroy; override;
    function AddSession(SessionId: String;
      Socket: TCustomWinSocket): TfrxSessionItem;
    procedure CompleteSession(Socket: TCustomWinSocket);
    procedure CompleteSessionId(SessionId: String);
    function FindSessionBySocket(Socket: TCustomWinSocket): TfrxSessionItem;
    function FindSessionById(SessionId: String): TfrxSessionItem;
    procedure CleanUp;

    property CleanUpTimeOut: Integer read FCleanUpTimeOut write FCleanUpTimeOut;
    property SessionPath: String read FSessionPath write SetSessionPath;
    property Count: Integer read GetCount;
  end;

  TfrxOldSessionsCleanupThread = class(TThread)
  private
    FPath: String;
  protected
    procedure Execute; override;
  public
    constructor Create(const Dir: String);
  public
    property Path: String read FPath write FPath;
  end;

var
  SessionManager: TfrxSessionManager;

implementation

uses frxServer, frxFileUtils, frxServerUtils, frxNetUtils, frxServerConfig, SyncObjs;

var
  SessionCS: TCriticalSection;

{ TfrxSessionItem }

constructor TfrxSessionItem.Create;
begin
  FSessionId := '';
  FName := '';
  FSocket := nil;
  FCompleted := False;
  FTimeCreated := Now;
  FTimeComplete := 0;
end;

destructor TfrxSessionItem.Destroy;
begin
  if FReportThread <> nil then
    ReportThread.Terminate;
  PMessages;
  inherited;
end;

{ TfrxSessionManager }

constructor TfrxSessionManager.Create;
begin
  inherited Create(True);
  FSessionList := TList.Create;
  FCleanUpTimeOut := StrToInt(ServerConfig.GetValue('server.http.sessiontimeout'));
  SessionPath := frxGetAbsPathDir(ServerConfig.GetValue('server.http.rootpath'), ServerConfig.ConfigFolder);
  Priority := tpLowest;
  FShutDown := False;
  Resume;
end;

destructor TfrxSessionManager.Destroy;
begin
  Terminate;
  while FThreadActive do
    PMessages;
  Clear;
  FSessionList.Free;
  inherited;
end;

function TfrxSessionManager.AddSession(SessionId: String;
  Socket: TCustomWinSocket): TfrxSessionItem;
var
  Session: TfrxSessionItem;
begin
  Session := TfrxSessionItem.Create;
  Session.SessionId := SessionId;
  Session.Socket := Socket;
  Session.FReportThread := nil;
  SessionCS.Enter;
  try
    FSessionList.Add(Session);
  finally
    SessionCS.Leave;
  end;
  Result := Session;
end;

function TfrxSessionManager.CleanUpSession(SessionId: String): Boolean;
var
  DirName: String;
  Approved: Boolean;
  i: Integer;
  Session: TfrxSessionItem;
  t, t1: TDateTime;
begin
  Result := False;
  Approved := True;
  if not FShutDown then
  begin
    t1 := FCleanUpTimeOut / 100000;
    i := 0;
    while i < FSessionList.Count do
    begin
      Session := TfrxSessionItem(FSessionList[i]);
      t := Now;
      if (t < (Session.TimeComplete + t1)) and
         (Pos(SessionId, Session.FName) > 0) then
      begin
        Approved := False;
        break;
      end;
      Inc(i);
    end;
  end;
  DirName := FSessionPath + SessionId;
  if Approved then
  begin
    DeleteSessionFolder(DirName);
    Result := True;
  end;
end;

procedure TfrxSessionManager.CompleteSession(Socket: TCustomWinSocket);
var
  Session: TfrxSessionItem;
begin
  Session := FindSessionBySocket(Socket);
  if Session <> nil then
  begin
    Session.Completed := True;
    Session.TimeComplete := Now;
  end;
end;

procedure TfrxSessionManager.CompleteSessionId(SessionId: String);
var
  Session: TfrxSessionItem;
begin
  Session := FindSessionById(SessionId);
  if Session <> nil then
  begin
    Session.Completed := True;
    Session.TimeComplete := Now;
  end;
end;

procedure TfrxSessionManager.DeleteSession;
var
  i: Integer;
begin
  SessionCS.Enter;
  try
    i := FSessionList.IndexOf(FSession);
    if (i <> -1) then
    begin
      if CleanUpSession(TfrxSessionItem(FSessionList[i]).SessionId) then
      begin
        TfrxSessionItem(FSessionList[i]).Free;
        FSessionList.Delete(i);
      end;
    end;
  finally
    SessionCS.Leave;
  end;
end;

procedure TfrxSessionManager.Execute;
var
  i: Integer;
begin
  FThreadActive := True;
  while not Terminated do
  begin
    i := 0;
    CleanUp;
    while (not Terminated) and (i < 1000) do
    begin
      Inc(i);
      Sleep(10);
      PMessages;
    end;
  end;
  FThreadActive := False;
end;

function TfrxSessionManager.FindSessionById(SessionId: String): TfrxSessionItem;
var
  i: Integer;
  Session: TfrxSessionItem;
begin
  Result := nil;
  for i := 0 to FSessionList.Count - 1 do
  begin
    Session := TfrxSessionItem(FSessionList[i]);
    if Session.FSessionId = SessionId then
    begin
      Result := Session;
      break;
    end
  end;
end;

function TfrxSessionManager.FindSessionBySocket(Socket: TCustomWinSocket): TfrxSessionItem;
var
  i: Integer;
  Session: TfrxSessionItem;
begin
  Result := nil;
  for i := 0 to FSessionList.Count - 1 do
  begin
    Session := TfrxSessionItem(FSessionList[i]);
    if Session.Socket = Socket then
    begin
      Result := Session;
      break;
    end
  end;
end;

procedure TfrxSessionManager.Clear;
var
  i: Integer;
begin
  FShutDown := True;
  for i := 0 to FSessionList.Count - 1 do
  begin
    if i < FSessionList.Count then
      CleanUpSession(TfrxSessionItem(FSessionList[i]).SessionId);
    if i < FSessionList.Count
      then TfrxSessionItem(FSessionList[i]).Free;
    Application.ProcessMessages;
  end;
  FSessionList.Clear;
  FShutDown := False;
end;

procedure TfrxSessionManager.DeleteSessionFolder(const DirName: String);
var
  SearchRec: TSearchRec;
  i: Integer;
begin
  if DirectoryExists(DirName) and (Pos(SID_SIGN, DirName) > 0) then
  begin
    i := FindFirst(DirName + '\*.*', 0, SearchRec);
    try
      while i = 0 do
      begin
        try
          DeleteFile(PChar(DirName + '\' + SearchRec.Name));
        except
        end;
        i := FindNext(SearchRec);
        PMessages;
      end;
    finally
      FindClose(SearchRec);
    end;
    try
      RemoveDirectory(PChar(DirName));
    except
    end;
  end;
end;

procedure TfrxSessionManager.SetSessionPath(const Value: String);
begin
  FSessionPath := Value;
  TfrxOldSessionsCleanupThread.Create(FSessionPath);
end;

procedure TfrxSessionManager.CleanUp;
var
  i, j: Integer;
  t, t1: TDateTime;
begin
    i := 0;
    t1 := FCleanUpTimeOut / 100000;
    j := 30;
    while (i < FSessionList.Count) and (j > 0) do
    begin
      FSession := TfrxSessionItem(FSessionList[i]);
      t := Now;
      if Assigned(FSession) and FSession.Completed then
        if t > (FSession.FTimeComplete + t1) then
        begin
          DeleteSession;
          Dec(j);
        end
        else Inc(i)
      else Inc(i);
    end;
end;

function TfrxSessionManager.GetCount: Integer;
begin
  Result := FSessionList.Count;
end;

{ TfrxOldSessionsCleanupThread }

constructor TfrxOldSessionsCleanupThread.Create(const Dir: String);
begin
  inherited Create(True);
  FPath := Dir;
  FreeOnTerminate := True;
  Resume;
end;

procedure TfrxOldSessionsCleanupThread.Execute;
var
  SearchRec: TSearchRec;
  i: Integer;
begin
  if DirectoryExists(FPath) and (not Terminated) then
  begin
    i := FindFirst(FPath + SID_SIGN + '*', faDirectory  , SearchRec);
    try
      while (i = 0) and not Terminated do
      begin
        try
          DeleteFolder(FPath + SearchRec.Name);
        except
        end;
        i := FindNext(SearchRec);
        PMessages;
      end;
    finally
      FindClose(SearchRec);
    end;
  end;
end;

initialization
  SessionCS := TCriticalSection.Create;

finalization
  SessionCS.Free;


end.

