{
    This file is part of Chentrah,
    Copyright (C) 2004-2008 Anton Rzheshevski (chebmaster@mail.ru).

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see http://www.gnu.org/licenses/

 **********************************************************************

    This file contains the routines for organizing the session files.

 **********************************************************************}

{$include mo_globaldefs.h}

unit mo_sessionstack;
interface
uses SysUtils, mo_hub, un_filelist;

  function HaveASessionToLoad: boolean;
  function SessionFileNameWoExt: AnsiString;
  function GetSessionFileName: AnsiString;
  procedure PutSavedSessionOntoStack(tempfname: ansistring);
  procedure FinalizeSessionRollback;
  procedure EraseSession(sid: AnsiString);
  function ValidateSessionId(s: AnsiString): AnsiString;
  procedure SetSessionIdInConfig(s: AnsiString);
  procedure GetSessionIDFromConfig;

  type
    TSessionList = class(TFileList)
    private
      function _getSessionId(i: integer): AnsiString;
    public
      constructor Create(CurrentOne: AnsiString); // Beware. This one is a validity nazi.
      property SID[index: integer]: AnsiString read _getSessionId;
    end;

  var
      SessionId: AnsiString;

implementation
  uses Classes;
  const megabyte: int64 = 1024 * 1024;

  function SessionFileNameWoExt: AnsiString;
  begin
    Result:= string(MotherState^.SessionDir) + string(MotherState^.ModulePureName) + DS + SessionID + DS;
  end;

  function SessionsRoot: AnsiString;
  begin
    Result:= string(MotherState^.SessionDir) + string(MotherState^.ModulePureName) + DS;
  end;

  procedure PutSavedSessionOntoStack(tempfname: ansistring);
  var
    f: file;
    s, sl: int64;
    i, j: integer;
    fl: TFileList;
    bname, sname: ansistring;
    fs: array of integer;
  begin
    if MotherState^.VerboseLog then AddLog('Putting the saved session into the file stack...');
    bname:= SessionFileNameWoExt;
    sname:= bname + FormatDateTime('_yyyy_mm_dd_hh_nn_ss_zzz', Now()) + '.che';
    RenameFile(tempfname, sname);
    if MotherState^.VerboseLog then AddLog('  Renamed to "%0".', [sname]);
    //now for the crowd control, to limit the disk  space used:
    FL:= TFileList.Create(bname + '*.che');
    FL.SortByName;
    if MotherState^.VerboseLog then AddLog('  Found %0 files', [FL.Count]);
    i:= FL.Count - 1;
    s:=0;
    while (i >= 0) and (s < int64(SessionSpaceLimit) * megabyte) do begin
      s+= FL.Size[i];
      dec(i);
    end;
    if MotherState^.VerboseLog then begin
      sl:=0;
      for j:=0 to FL.Count - 1 do sl+= FL.Size[j];
      AddLog ('  Total size is %0 megabytes', [format('%.3f', [sl / (1024 * 1024)])]);
    end;
    if i >= 0 then begin
      AddLog(RuEn(
        'Объём резервных файлов сессии превысил лимит в %0Мб.',
        'The session backup files have reached the %0Mb limit.'), [SessionSpaceLimit]);
      while i >=0 do begin
        sname:= FL.Names[i];
        AddLog(RuEn('  удаляю %0 (%1 Мб)','  deleting %0 (%1 Mb)'),
           [ExtractFileName(sname), format('%g.3',[FL.Size[i] / megabyte])]);
        AssignFile(f, sname);
        Erase(f);
        dec(i);
      end;
    end;
    FL.Free;
  end;

  procedure GetSessionIDFromConfig;
  begin
    SessionId:= ValidateSessionId(string(_GetConfigStr(MotherState^.ModulePureName, 'SessionId')));
    if SessionId = '' then SessionId:= 'default';
  end;

var FL: TFileList = nil;

  function HaveASessionToLoad: boolean;
  begin
    SessionId:= ValidateSessionId(string(_GetConfigStr(MotherState^.ModulePureName, 'SessionId')));
    if SessionId = '' then SessionId:= 'default';
    FL:= TFileList.Create(SessionFileNameWoExt + '*.che');
    if MotherState^.RollBackTheSessionOnNextLoad
    then begin
      if FL.Count > 1
        then Result:=Yes
        else begin
          Result:= No;
          if FL.Count = 0 then begin
            AddLog(RuEn(
              'Откат сессии невозможен: нет ни одной точки возврата!',
              'Unable to rollback the session: there are no restore points!'));
            MotherState^.RollBackTheSessionOnNextLoad:= false;
          end
          else
            FinalizeSessionRollback;
       end;
    end
    else
      Result:= FL.Count > 0;
  end;


  procedure SetSessionIdInConfig(s: AnsiString);
  begin
    _SetConfigStr(MotherState^.ModulePureName, 'SessionId', PAnsiChar(s));
  end;

  function GetSessionFileName: AnsiString;
  begin
    if MotherState^.RollBackTheSessionOnNextLoad then begin
      if FL.Count = 2 then begin
        AddLog(RuEn(
          'Внимание! Осталась одна точка возврата, при следующем откате сессия будет полностью стёрта!',
          'warning! Only one restore point left, at the next rollback the session will be erased completely!'));
      end;
    end;
    if MotherState^.RollBackTheSessionOnNextLoad
      then Result:= FL.Names[FL.Count - 2]
      else Result:= FL.Names[FL.Count - 1];
  end;


  procedure FinalizeSessionRollback;
  var f: file;
  begin
    if MotherState^.RollBackTheSessionOnNextLoad then begin
      MotherState^.RollBackTheSessionOnNextLoad:= false;
      if MotherState^.DebugMode then AddLog(RuEn(
        'Откат сессии: удаляю %0','Session rollback: deleting %0'),
         [ExtractFileName(FL.Names[FL.Count - 1])]);
      AssignFile(f, FL.Names[FL.Count - 1]);
      Erase(f);
      if FL.Count = 1 then
        AddLog(RuEn(
          'Сессия полностью стёрта, не осталось ни одной точки возврата.',
          'Session is erased completely, there are no restore points.'))
      else
        AddLog(RuEn('Откат сессии успешен.','Session rolled back successfully.'),[]);
    end;
    FL.Free;
  end;

  function ValidateSessionId(s: AnsiString): AnsiString;
  var i: integer;
  begin
//    s:= LowerCase(s);
    result:= '';
    for i:=1 to Length(s) do if s[i] in ['a'..'z','_','0'..'9'] then result+= s[i];
  end;


  function TSessionList._getSessionId(i: integer): AnsiString;
  begin
    Result:= Strings[i];
  end;

  constructor TSessionList.Create(CurrentOne: AnsiString);
  var
    i: integer;
    ssl: TStringList;
  begin
    inherited Create(SessionsRoot + '*', faDirectory);
//    Self.SortByName;
    for i:=0 to Count - 1 do Self.Strings[i]:= Self.OnlyName[i];
    i:= (Self as TStringList).IndexOf('.'); if i >=0 then Self.Delete(i);
    i:= (Self as TStringList).IndexOf('..'); if i >=0 then Self.Delete(i);
    i:= (Self as TStringList).IndexOf('default'); if i >=0 then Self.Delete(i);
    Self.Insert(0, 'default');
    if (currentone <> '') and (currentone <> 'default') then begin
      i:= (Self as TStringList).IndexOf(CurrentOne);
      if i < 0 then Self.Insert(0, CurrentOne); //A case where there is no folder as the current session haven't bee saved yet
    end;
//    Self.SortByName;
    (Self as TStringList).Sorted:= No;
    (Self as TStringList).Sort;

    for i:=0 to Count - 1 do begin
      if ValidateSessionId(Self.Strings[i]) <> Self.Strings[i] then Die (
        RuEn(
          'Обнаружена сессия с невалидным ID! ("%0")'#10#13
         +'  (полное имя папки'#10#13'  "%1")'#10#13
         +'  Имена папок (они же идентификаторы сессий) должны содержать'#10#13
         +'  только строчные латинские буквы, цифры, и символ подчёркивания!'#10#13
         +'  Любые другие символы не допускаются! (включая заглавные буквы)',
          'Invalid session ID was found ("%0")!'#10#13
         +'  (full folder name'#10#13'  "%1")'#10#13
         +'  Folder names (used as session IDs) must contain only the'#10#13
         +'  lowercase basic Latin letters, numbers or the underline character!'#10#13
         +'  Any other characters are prohibited! (including capital letters)'
        ),
        [Self.Strings[i], Self.Names[i]]
      );
    end;
  end;


  procedure EraseSession(sid: AnsiString);
  var i: integer;
    fn: AnsiString;
  begin
    if (sid = '') or (sid <> ValidateSessionId(sid)) then Die(RuEn('Невалидный ID сессии, "%0"','Invalid session ID, "%0"'), [sid]);
    FL:= TFileList.Create(SessionsRoot + sid + '/*.*');
    for i:=0 to FL.Count - 1 do begin
      if (FL.OnlyName[i] = '.') or (FL.OnlyName[i] = '..') then continue;
      fn:= FL.Names[i];
      if MotherState^.VerboseLog then AddLog(RuEn('Удаляю файл %0 ...','Erasing file %0 ...'),[fn]);
      if not DeleteFile(fn) then Die(RuEn('Не удалось удалить файл %0','Unable to delete file %0'),[fn]);
      if MotherState^.VerboseLog then AddLogOk;
    end;
    fn:= SessionsRoot + sid;
    if MotherState^.VerboseLog then AddLog(RuEn('Удаляю папку %0 ...','Erasing folder %0 ...'),[fn]);
    if not RemoveDir(fn) then Die(RuEn('Не удалось удалить папку %0','Unable to delete folder %0'),[fn]);
    if MotherState^.VerboseLog then AddLogOk;
    AddLog(RuEn('Сессия %0 успешно стёрта.','Session %0 has been erased succesfully.'),[sid]);
  end;

end.






