DEADSOFTWARE

sfs and wad code refactoring: part 1
[d2df-sdl.git] / src / sfs / sfs.pas
index 9615d4b4db7549457453df0d0933e3bb57725303..157fbac3c2ccb3d20d5a811fecc34e9a1b6ea142 100644 (file)
@@ -1,6 +1,7 @@
 // streaming file system (virtual)
 {$MODE DELPHI}
 {.$R-}
+{.$DEFINE SFS_VOLDEBUG}
 unit sfs;
 
 interface
@@ -73,6 +74,8 @@ type
     // íèêàêèõ ïàäåíèé íà íåïðàâèëüíûå èíäåêñû!
     function GetFiles (index: Integer): TSFSFileInfo; virtual;
 
+    procedure removeCommonPath (); virtual;
+
   public
     // pSt íå îáÿçàòåëüíî çàïîìèíàòü, åñëè îí íå íóæåí.
     constructor Create (const pFileName: TSFSString; pSt: TStream); virtual;
@@ -166,6 +169,9 @@ procedure SFSUnregisterVolumeFactory (factory: TSFSVolumeFactory);
 // ïðèíèìàåòñÿ âî âíèìàíèå òîëüêî ïîñëåäíÿÿ òðóáà.
 function SFSAddDataFile (const dataFileName: TSFSString; top: Boolean=false): Boolean;
 
+// äîáàâèòü ñáîðíèê âðåìåííî
+function SFSAddDataFileTemp (const dataFileName: TSFSString; top: Boolean=false): Boolean;
+
 // äîáàâèòü â ïîñòîÿííûé ñïèñîê ñáîðíèê èç ïîòîêà ds.
 // åñëè âîçâðàùàåò èñòèíó, òî SFS ñòàíîâèòñÿ âëÿäåëüöåì ïîòîêà ds è ñàìà
 // óãðîáèò ñåé ïîòîê ïî íåîáõîäèìîñòè.
@@ -193,20 +199,21 @@ function SFSFileOpen (const fName: TSFSString): TStream;
 // ïîñëå èñïîëüçîâàíèÿ, íàòóðàëüíî, èòåðàòîð íàäî ãðîõíóòü %-)
 function SFSFileList (const dataFileName: TSFSString): TSFSFileList;
 
+// çàïðåòèòü îñâîáîæäåíèå âðåìåííûõ òîìîâ (ìîæíî âûçûâàòü ðåêóðñèâíî)
+procedure sfsGCDisable ();
+
+// ðàçðåøèòü îñâîáîæäåíèå âðåìåííûõ òîìîâ (ìîæíî âûçûâàòü ðåêóðñèâíî)
+procedure sfsGCEnable ();
+
+// for completeness sake
+procedure sfsGCCollect ();
+
 function SFSReplacePathDelims (const s: TSFSString; newDelim: TSFSChar): TSFSString;
-// èãíîðèðóåò ðåãèñòð ñèìâîëîâ
-// <0: s0 < s1
-// =0: s0 = s1
-// >0: s0 > s1
-function SFSStrComp (const s0, s1: TSFSString): Integer;
 
 // ðàçîáðàòü òîëñòîå èìÿ ôàéëà, âåðíóòü âèðòóàëüíîå èìÿ ïîñëåäíåãî ñïèñêà
 // èëè ïóñòóþ ñòîðîêó, åñëè ñïèñêîâ íå áûëî.
 function SFSGetLastVirtualName (const fn: TSFSString): string;
 
-// ïðåîáðàçîâàòü ÷èñëî â ñòðîêó, êðàñèâî ðàçáàâëÿÿ çàïÿòûìè
-function Int64ToStrComma (i: Int64): string;
-
 // Wildcard matching
 // this code is meant to allow wildcard pattern matches. tt is VERY useful
 // for matching filename wildcard patterns. tt allows unix grep-like pattern
@@ -247,20 +254,7 @@ var
 implementation
 
 uses
-  xstreams;
-
-
-function Int64ToStrComma (i: Int64): string;
-var
-  f: Integer;
-begin
-  Str(i, result);
-  f := Length(result)+1;
-  while f > 4 do
-  begin
-    Dec(f, 3); Insert(',', result, f);
-  end;
-end;
+  xstreams, utils;
 
 
 const
@@ -437,6 +431,63 @@ type
 var
   factories: TObjectList; // TSFSVolumeFactory
   volumes: TObjectList;   // TVolumeInfo
+  gcdisabled: Integer = 0; // >0: disabled
+
+
+procedure sfsGCCollect ();
+var
+  f, c: Integer;
+  vi: TVolumeInfo;
+  used: Boolean;
+begin
+  // collect garbage
+  f := 0;
+  while f < volumes.Count do
+  begin
+    vi := TVolumeInfo(volumes[f]);
+    if vi = nil then continue;
+    if (not vi.fPermanent) and (vi.fVolume.fRC = 0) and (vi.fOpenedFilesCount = 0) then
+    begin
+      // this volume probably can be removed
+      used := false;
+      c := volumes.Count-1;
+      while not used and (c >= 0) do
+      begin
+        if (c <> f) and (volumes[c] <> nil) then
+        begin
+          used := (TVolumeInfo(volumes[c]).fStream = vi.fStream);
+          if not used then used := (TVolumeInfo(volumes[c]).fVolume.fFileStream = vi.fStream);
+          if used then break;
+        end;
+        Dec(c);
+      end;
+      if not used then
+      begin
+        {$IFDEF SFS_VOLDEBUG}writeln('000: destroying volume "', TVolumeInfo(volumes[f]).fPackName, '"');{$ENDIF}
+        volumes.extract(vi); // remove from list
+        vi.Free; // and kill
+        f := 0;
+        continue;
+      end;
+    end;
+    Inc(f); // next volume
+  end;
+end;
+
+procedure sfsGCDisable ();
+begin
+  Inc(gcdisabled);
+end;
+
+procedure sfsGCEnable ();
+begin
+  Dec(gcdisabled);
+  if gcdisabled <= 0 then
+  begin
+    gcdisabled := 0;
+    sfsGCCollect();
+  end;
+end;
 
 
 // ðàçáèòü èìÿ ôàéëà íà ÷àñòè: ïðåôèêñ ôàéëîâîé ñèñòåìû, èìÿ ôàéëà äàííûõ,
@@ -519,7 +570,7 @@ begin
       vi := TVolumeInfo(volumes[f]);
       if not onlyPerm or vi.fPermanent then
       begin
-        if SFSStrComp(vi.fPackName, dataFileName) = 0 then
+        if StrEquCI1251(vi.fPackName, dataFileName) then
         begin
           result := f;
           exit;
@@ -546,12 +597,32 @@ begin
   end;
 end;
 
-// <0: s0 < s1
-// =0: s0 = s1
-// >0: s0 > s1
-function SFSStrComp (const s0, s1: TSFSString): Integer;
+
+// adds '/' too
+function normalizePath (fn: string): string;
+var
+  i: Integer;
 begin
-  result := AnsiCompareText(s0, s1);
+  result := '';
+  i := 1;
+  while i <= length(fn) do
+  begin
+    if (fn[i] = '.') and ((length(fn)-i = 0) or (fn[i+1] = '/') or (fn[i+1] = '\')) then
+    begin
+      i := i+2;
+      continue;
+    end;
+    if (fn[i] = '/') or (fn[i] = '\') then
+    begin
+      if (length(result) > 0) and (result[length(result)] <> '/') then result := result+'/';
+    end
+    else
+    begin
+      result := result+fn[i];
+    end;
+    Inc(i);
+  end;
+  if (length(result) > 0) and (result[length(result)] <> '/') then result := result+'/';
 end;
 
 function SFSReplacePathDelims (const s: TSFSString; newDelim: TSFSChar): TSFSString;
@@ -636,10 +707,14 @@ begin
   if fOwner <> nil then
   begin
     Dec(fOwner.fOpenedFilesCount);
-    if not fOwner.fPermanent and (fOwner.fOpenedFilesCount < 1) then
+    if (gcdisabled = 0) and not fOwner.fPermanent and (fOwner.fOpenedFilesCount < 1) then
     begin
       f := volumes.IndexOf(fOwner);
-      if f <> -1 then volumes[f] := nil; // this will destroy the volume
+      if f <> -1 then
+      begin
+        {$IFDEF SFS_VOLDEBUG}writeln('001: destroying volume "', TVolumeInfo(volumes[f]).fPackName, '"');{$ENDIF}
+        volumes[f] := nil; // this will destroy the volume
+      end;
     end;
   end;
 end;
@@ -674,61 +749,42 @@ begin
   fFiles := TObjectList.Create(true);
 end;
 
+procedure TSFSVolume.removeCommonPath ();
+begin
+end;
+
 procedure TSFSVolume.DoDirectoryRead ();
 var
-  fl: TStringList; //!!!FIXME! change to list of wide TSFSStrings or so!
-  f, c, n: Integer;
+  f, c: Integer;
   sfi: TSFSFileInfo;
-  tmp, fn, ext: TSFSString;
+  tmp: TSFSString;
 begin
-  fl := nil;
   fFileName := ExpandFileName(SFSReplacePathDelims(fFileName, '/'));
-  try
-    ReadDirectory();
-    fFiles.Pack();
+  ReadDirectory();
+  fFiles.Pack();
 
-    // check for duplicate file names
-    fl := TStringList.Create(); fl.Sorted := true;
-    for f := 0 to fFiles.Count-1 do
+  f := 0;
+  while f < fFiles.Count do
+  begin
+    sfi := TSFSFileInfo(fFiles[f]);
+    // normalize name & path
+    sfi.fPath := SFSReplacePathDelims(sfi.fPath, '/');
+    if (sfi.fPath <> '') and (sfi.fPath[1] = '/') then Delete(sfi.fPath, 1, 1);
+    if (sfi.fPath <> '') and (sfi.fPath[Length(sfi.fPath)] <> '/') then sfi.fPath := sfi.fPath+'/';
+    tmp := SFSReplacePathDelims(sfi.fName, '/');
+    c := Length(tmp); while (c > 0) and (tmp[c] <> '/') do Dec(c);
+    if c > 0 then
     begin
-      sfi := TSFSFileInfo(fFiles[f]);
-
-      // normalize name & path
-      sfi.fPath := SFSReplacePathDelims(sfi.fPath, '/');
-      if (sfi.fPath <> '') and (sfi.fPath[1] = '/') then Delete(sfi.fPath, 1, 1);
-      if (sfi.fPath <> '') and (sfi.fPath[Length(sfi.fPath)] <> '/') then sfi.fPath := sfi.fPath+'/';
-      tmp := SFSReplacePathDelims(sfi.fName, '/');
-      c := Length(tmp); while (c > 0) and (tmp[c] <> '/') do Dec(c);
-      if c > 0 then
-      begin
-        // split path and name
-        Delete(sfi.fName, 1, c); // cut name
-        tmp := Copy(tmp, 1, c);  // get path
-        if tmp = '/' then tmp := ''; // just delimiter; ignore it
-        sfi.fPath := sfi.fPath+tmp;
-      end;
-
-      // check for duplicates
-      if fl.Find(sfi.fPath+sfi.fName, c) then
-      begin
-        n := 0; tmp := sfi.fName;
-        c := Length(tmp); while (c > 0) and (tmp[c] <> '.') do Dec(c);
-        if c < 1 then c := Length(tmp)+1;
-        fn := Copy(tmp, 1, c-1); ext := Copy(tmp, c, Length(tmp));
-        repeat
-          tmp := fn+'_'+IntToStr(n)+ext;
-          if not fl.Find(sfi.fPath+tmp, c) then break;
-          Inc(n);
-        until false;
-        sfi.fName := tmp;
-      end;
-      fl.Add(sfi.fName);
+      // split path and name
+      Delete(sfi.fName, 1, c); // cut name
+      tmp := Copy(tmp, 1, c);  // get path
+      if tmp = '/' then tmp := ''; // just delimiter; ignore it
+      sfi.fPath := sfi.fPath+tmp;
     end;
-    fl.Free();
-  except
-    fl.Free();
-    raise;
+    sfi.fPath := normalizePath(sfi.fPath);
+    if (length(sfi.fPath) = 0) and (length(sfi.fName) = 0) then sfi.Free else Inc(f);
   end;
+  removeCommonPath();
 end;
 
 destructor TSFSVolume.Destroy ();
@@ -755,8 +811,8 @@ begin
       Dec(result);
       if fFiles[result] <> nil then
       begin
-        if (SFSStrComp(fPath, TSFSFileInfo(fFiles[result]).fPath) = 0) and
-           (SFSStrComp(fName, TSFSFileInfo(fFiles[result]).fName) = 0) then exit;
+        if StrEquCI1251(fPath, TSFSFileInfo(fFiles[result]).fPath) and
+           StrEquCI1251(fName, TSFSFileInfo(fFiles[result]).fName) then exit;
       end;
     end;
     result := -1;
@@ -823,8 +879,11 @@ begin
   if fVolume <> nil then Dec(fVolume.fRC);
   Dec(TVolumeInfo(volumes[f]).fOpenedFilesCount);
   // óáü¸ì çàïèñü, åñëè îíà âðåìåííàÿ, è â íåé íåò áîëüøå íè÷åãî îòêðûòîãî
-  if not TVolumeInfo(volumes[f]).fPermanent and
-     (TVolumeInfo(volumes[f]).fOpenedFilesCount < 1) then volumes[f] := nil;
+  if (gcdisabled = 0) and not TVolumeInfo(volumes[f]).fPermanent and (TVolumeInfo(volumes[f]).fOpenedFilesCount < 1) then
+  begin
+    {$IFDEF SFS_VOLDEBUG}writeln('002: destroying volume "', TVolumeInfo(volumes[f]).fPackName, '"');{$ENDIF}
+    volumes[f] := nil;
+  end;
   inherited Destroy();
 end;
 
@@ -917,7 +976,7 @@ begin
     except
       FreeAndNil(st);
       // óäàëèì íåèñïîëüçóåìûé âðåìåííûé òîì.
-      if not vi.fPermanent and (vi.fOpenedFilesCount < 1) then volumes[result] := nil;
+      if (gcdisabled = 0) and not vi.fPermanent and (vi.fOpenedFilesCount < 1) then volumes[result] := nil;
       raise;
     end;
     // óðà. îòêðûëè ôàéë. êèäàåì â âîçäóõ ÷åï÷èêè, ïðîäîëæàåì ðàçâëå÷åíèå.
@@ -948,7 +1007,7 @@ begin
   end;
 
   if ds <> nil then st := ds
-  else st := TFileStream.Create(fn, fmOpenRead or fmShareDenyWrite);
+  else st := TFileStream.Create(fn, fmOpenRead or {fmShareDenyWrite}fmShareDenyNone);
   st.Position := 0;
 
   volumes.Pack();
@@ -999,8 +1058,7 @@ begin
   vi.fOpenedFilesCount := 0;
 end;
 
-function SFSAddSubDataFile (const virtualName: TSFSString; ds: TStream;
-  top: Boolean = false): Boolean;
+function SFSAddSubDataFile (const virtualName: TSFSString; ds: TStream; top: Boolean=false): Boolean;
 var
   tv: Integer;
 begin
@@ -1014,7 +1072,7 @@ begin
   end;
 end;
 
-function SFSAddDataFile (const dataFileName: TSFSString; top: Boolean = false): Boolean;
+function SFSAddDataFile (const dataFileName: TSFSString; top: Boolean=false): Boolean;
 var
   tv: Integer;
 begin
@@ -1027,6 +1085,20 @@ begin
   end;
 end;
 
+function SFSAddDataFileTemp (const dataFileName: TSFSString; top: Boolean=false): Boolean;
+var
+  tv: Integer;
+begin
+  try
+    if top then tv := -1 else tv := 1;
+    SFSAddDataFileEx(dataFileName, nil, tv, 0);
+    result := true;
+  except
+    result := false;
+  end;
+end;
+
+
 
 function SFSExpandDirName (const s: TSFSString): TSFSString;
 var
@@ -1083,7 +1155,7 @@ var
       cdir := SFSReplacePathDelims(SFSExpandDirName(cdir), '/');
       if cdir[Length(cdir)] <> '/' then cdir := cdir+'/';
       try
-        result := TFileStream.Create(cdir+dfn, fmOpenRead or fmShareDenyWrite);
+        result := TFileStream.Create(cdir+dfn, fmOpenRead or {fmShareDenyWrite}fmShareDenyNone);
         exit;
       except
       end;
@@ -1113,7 +1185,7 @@ begin
       ps := TOwnedPartialStream.Create(vi, result, 0, result.Size, true);
     except
       result.Free();
-      if not vi.fPermanent and (vi.fOpenedFilesCount < 1) then volumes[f] := nil;
+      if (gcdisabled = 0) and not vi.fPermanent and (vi.fOpenedFilesCount < 1) then volumes[f] := nil;
       result := CheckDisk(); // îáëîì ñ datafile, ïðîâåðèì äèñê
       if result = nil then raise ESFSError.Create('file not found: "'+fName+'"');
       exit;
@@ -1186,7 +1258,7 @@ begin
     result := TSFSFileList.Create(vi.fVolume);
     Inc(vi.fVolume.fRC);
   except
-    if not vi.fPermanent and (vi.fOpenedFilesCount < 1) then volumes[f] := nil;
+    if (gcdisabled = 0) and not vi.fPermanent and (vi.fOpenedFilesCount < 1) then volumes[f] := nil;
   end;
 end;
 
@@ -1194,7 +1266,7 @@ end;
 initialization
   factories := TObjectList.Create(true);
   volumes := TObjectList.Create(true);
-finalization
+//finalization
   //volumes.Free(); // it fails for some reason... Runtime 217 (^C hit). wtf?!
   //factories.Free(); // not need to be done actually...
 end.