DEADSOFTWARE

net: slightly better detection of "map change" event in resource downloader
[d2df-sdl.git] / src / game / g_res_downloader.pas
index ed1ea3bdf3feb7c77de12529d99b0b146c67d94b..0b8d352eaab736e29962ea5e09d368ca8f913484 100644 (file)
@@ -20,7 +20,6 @@ interface
 uses sysutils, Classes, md5, g_net, g_netmsg, g_console, g_main, e_log;
 
 function g_Res_SearchSameWAD(const path, filename: AnsiString; const resMd5: TMD5Digest): AnsiString;
-function g_Res_SearchResWad (asMap: Boolean; const resMd5: TMD5Digest): AnsiString;
 
 // download map wad from server (if necessary)
 // download all required map resource wads too
@@ -38,120 +37,12 @@ implementation
 
 uses g_language, sfs, utils, wadreader, g_game, hashtable;
 
-const DOWNLOAD_DIR = 'downloads';
-
-type
-  TFileInfo = record
-    diskName: AnsiString; // lowercased
-    baseName: AnsiString; // lowercased
-    md5: TMD5Digest;
-    md5valid: Boolean;
-    nextBaseNameIndex: Integer;
-  end;
+//const DOWNLOAD_DIR = 'downloads';
 
 var
-  knownFiles: array of TFileInfo;
-  knownHash: THashStrInt = nil; // key: base name; value: index
-  scannedDirs: THashStrInt = nil; // key: lowercased dir name
   replacements: THashStrStr = nil;
 
 
-function findKnownFile (diskName: AnsiString): Integer;
-var
-  idx: Integer;
-  baseName: AnsiString;
-begin
-  result := -1;
-  if not assigned(knownHash) then exit;
-  if (length(diskName) = 0) then exit;
-  baseName := toLowerCase1251(ExtractFileName(diskName));
-  if (not knownHash.get(baseName, idx)) then exit;
-  if (idx < 0) or (idx >= length(knownFiles)) then raise Exception.Create('wutafuck?');
-  while (idx >= 0) do
-  begin
-    if (strEquCI1251(knownFiles[idx].diskName, diskName)) then begin result := idx; exit; end; // i found her!
-    idx := knownFiles[idx].nextBaseNameIndex;
-  end;
-end;
-
-
-function addKnownFile (diskName: AnsiString): Integer;
-var
-  idx: Integer;
-  lastIdx: Integer = -1;
-  baseName: AnsiString;
-  fi: ^TFileInfo;
-begin
-  result := -1;
-  if not assigned(knownHash) then knownHash := THashStrInt.Create();
-  if (length(diskName) = 0) then exit;
-  baseName := toLowerCase1251(ExtractFileName(diskName));
-  if (length(baseName) = 0) then exit;
-  // check if we already have this file
-  if (knownHash.get(baseName, idx)) then
-  begin
-    if (idx < 0) or (idx >= length(knownFiles)) then raise Exception.Create('wutafuck?');
-    while (idx >= 0) do
-    begin
-      if (strEquCI1251(knownFiles[idx].diskName, diskName)) then
-      begin
-        // already here
-        result := idx;
-        exit;
-      end;
-      lastIdx := idx;
-      idx := knownFiles[idx].nextBaseNameIndex;
-    end;
-  end;
-  // this file is not there, append it
-  idx := length(knownFiles);
-  result := idx;
-  SetLength(knownFiles, idx+1); // sorry
-  fi := @knownFiles[idx];
-  fi.diskName := diskName;
-  fi.baseName := baseName;
-  fi.md5valid := false;
-  fi.nextBaseNameIndex := -1;
-  if (lastIdx < 0) then
-  begin
-    // totally new one
-    knownHash.put(baseName, idx);
-  end
-  else
-  begin
-    knownFiles[lastIdx].nextBaseNameIndex := idx;
-  end;
-end;
-
-
-function getKnownFileWithMD5 (diskDir: AnsiString; baseName: AnsiString; const md5: TMD5Digest): AnsiString;
-var
-  idx: Integer;
-begin
-  result := '';
-  if not assigned(knownHash) then exit;
-  if (not knownHash.get(toLowerCase1251(baseName), idx)) then exit;
-  if (idx < 0) or (idx >= length(knownFiles)) then raise Exception.Create('wutafuck?');
-  while (idx >= 0) do
-  begin
-    if (strEquCI1251(knownFiles[idx].diskName, IncludeTrailingPathDelimiter(diskDir)+baseName)) then
-    begin
-      if (not knownFiles[idx].md5valid) then
-      begin
-        knownFiles[idx].md5 := MD5File(knownFiles[idx].diskName);
-        knownFiles[idx].md5valid := true;
-      end;
-      if (MD5Match(knownFiles[idx].md5, md5)) then
-      begin
-        result := knownFiles[idx].diskName;
-        exit;
-      end;
-    end;
-    idx := knownFiles[idx].nextBaseNameIndex;
-  end;
-end;
-
-
 // call this before downloading a new map from a server
 procedure g_Res_ClearReplacementWads ();
 begin
@@ -174,100 +65,91 @@ end;
 procedure g_Res_PutReplacementWad (oldname: AnsiString; newDiskName: AnsiString);
 begin
   e_LogWritefln('adding replacement wad: oldname=%s; newname=%s', [oldname, newDiskName]);
+  if not assigned(replacements) then replacements := THashStrStr.Create();
   replacements.put(toLowerCase1251(oldname), newDiskName);
 end;
 
 
-procedure scanDir (const dirName: AnsiString; calcMD5: Boolean);
+function scanDir (dirName: AnsiString; baseName: AnsiString; const resMd5: TMD5Digest): AnsiString;
 var
   searchResult: TSearchRec;
   dfn: AnsiString;
-  idx: Integer;
+  md5: TMD5Digest;
+  dirs: array of AnsiString;
+  f: Integer;
 begin
-  if not assigned(scannedDirs) then scannedDirs := THashStrInt.Create();
-  dfn := toLowerCase1251(IncludeTrailingPathDelimiter(dirName));
-  if scannedDirs.has(dfn) then exit;
-  scannedDirs.put(dfn, 42);
+  result := '';
+  SetLength(dirs, 0);
+  if (length(baseName) = 0) then exit;
+  dirName := IncludeTrailingPathDelimiter(dirName);
+  e_LogWritefln('scanning dir `%s` for file `%s`...', [dirName, baseName]);
 
-  if (FindFirst(dirName+'/*', faAnyFile, searchResult) <> 0) then exit;
+  // scan files
+  if (FindFirst(dirName+'*', faAnyFile, searchResult) <> 0) then exit;
   try
     repeat
-      if (searchResult.Attr and faDirectory) = 0 then
+      if ((searchResult.Attr and faDirectory) = 0) then
       begin
-        dfn := dirName+'/'+searchResult.Name;
-        idx := addKnownFile(dfn);
-        if (calcMD5) and (idx >= 0) then
+        if (isWadNamesEqu(searchResult.Name, baseName)) then
         begin
-          if (not knownFiles[idx].md5valid) then
+          dfn := dirName+searchResult.Name;
+          if FileExists(dfn) then
           begin
-            knownFiles[idx].md5 := MD5File(knownFiles[idx].diskName);
-            knownFiles[idx].md5valid := true;
+            e_LogWritefln('  found `%s`...', [dfn]);
+            md5 := MD5File(dfn);
+            if MD5Match(md5, resMd5) then
+            begin
+              e_LogWritefln('    MATCH `%s`...', [dfn]);
+              SetLength(dirs, 0);
+              result := dfn;
+              exit;
+            end;
           end;
         end;
       end
-      else if (searchResult.Name <> '.') and (searchResult.Name <> '..') then
+      else
       begin
-        scanDir(IncludeTrailingPathDelimiter(dirName)+searchResult.Name, calcMD5);
+        if (searchResult.Name <> '.') and (searchResult.Name <> '..') then
+        begin
+          dfn := dirName+searchResult.Name;
+          SetLength(dirs, Length(dirs)+1);
+          dirs[length(dirs)-1] := dfn;
+        end;
       end;
     until (FindNext(searchResult) <> 0);
   finally
     FindClose(searchResult);
   end;
-end;
-
-
-function CompareFileHash(const filename: AnsiString; const resMd5: TMD5Digest): Boolean;
-var
-  gResHash: TMD5Digest;
-  fname: AnsiString;
-begin
-  fname := findDiskWad(filename);
-  if length(fname) = 0 then begin result := false; exit; end;
-  gResHash := MD5File(fname);
-  Result := MD5Match(gResHash, resMd5);
-end;
 
-function CheckFileHash(const path, filename: AnsiString; const resMd5: TMD5Digest): Boolean;
-var
-  fname: AnsiString;
-begin
-  fname := findDiskWad(path+filename);
-  if length(fname) = 0 then begin result := false; exit; end;
-  Result := FileExists(fname) and CompareFileHash(fname, resMd5);
+  // scan subdirs
+  for f := 0 to High(dirs) do
+  begin
+    dfn := dirs[f];
+    result := scanDir(dfn, baseName, resMd5);
+    if (length(result) <> 0) then begin SetLength(dirs, 0); exit; end;
+  end;
+  SetLength(dirs, 0);
 end;
 
 
-function g_Res_SearchResWad (asMap: Boolean; const resMd5: TMD5Digest): AnsiString;
-var
-  f: Integer;
+function g_Res_SearchResWad (asMap: Boolean; fname: AnsiString; const resMd5: TMD5Digest): AnsiString;
 begin
   result := '';
   //if not assigned(scannedDirs) then scannedDirs := THashStrInt.Create();
   if (asMap) then
   begin
-    scanDir(GameDir+'/maps/downloads', true);
+    result := scanDir(GameDir+'/maps', ExtractFileName(fname), resMd5);
   end
   else
   begin
-    scanDir(GameDir+'/wads/downloads', true);
-  end;
-  for f := Low(knownFiles) to High(knownFiles) do
-  begin
-    if (not knownFiles[f].md5valid) then continue;
-    if (MD5Match(knownFiles[f].md5, resMd5)) then
-    begin
-      result := knownFiles[f].diskName;
-      exit;
-    end;
+    result := scanDir(GameDir+'/wads', ExtractFileName(fname), resMd5);
   end;
-  //resStream := createDiskFile(GameDir+'/wads/'+mapData.ExternalResources[i].Name);
 end;
 
 
 function g_Res_SearchSameWAD (const path, filename: AnsiString; const resMd5: TMD5Digest): AnsiString;
 begin
-  scanDir(path, false);
-  result := getKnownFileWithMD5(path, filename, resMd5);
+  result := scanDir(path, filename, resMd5);
 end;
 
 
@@ -277,30 +159,18 @@ var
   resList: TStringList;
   f, res: Integer;
   strm: TStream;
-  mmd5: TMD5Digest;
   fname: AnsiString;
-  idx: Integer;
   wadname: AnsiString;
+  md5: TMD5Digest;
 begin
   //SetLength(mapData.ExternalResources, 0);
-  //result := g_Res_SearchResWad(true{asMap}, mapHash);
   result := '';
   g_Res_ClearReplacementWads();
-  g_Res_received_map_start := false;
-
-  try
-    CreateDir(GameDir+'/maps/downloads');
-  except
-  end;
-
-  try
-    CreateDir(GameDir+'/wads/downloads');
-  except
-  end;
 
   resList := TStringList.Create();
 
   try
+    g_Res_received_map_start := 1;
     g_Console_Add(Format(_lc[I_NET_MAP_DL], [FileName]));
     e_WriteLog('Downloading map `' + FileName + '` from server', TMsgType.Notify);
     g_Game_SetLoadingText(FileName + '...', 0, False);
@@ -313,7 +183,7 @@ begin
     if (res <> 0) then exit;
 
     // find or download a map
-    result := g_Res_SearchResWad(true{asMap}, mapHash);
+    result := g_Res_SearchResWad(true{asMap}, tf.diskName, mapHash);
     if (length(result) = 0) then
     begin
       // download map
@@ -324,14 +194,19 @@ begin
         result := '';
         exit;
       end;
+      try
+        CreateDir(GameDir+'/maps/downloads');
+      except
+      end;
       fname := GameDir+'/maps/downloads/'+FileName;
       try
-        strm := createDiskFile(fname);
+        strm := openDiskFileRW(fname);
       except
         e_WriteLog('cannot create map file `'+FileName+'`', TMsgType.Fatal);
         result := '';
         exit;
       end;
+      tf.diskName := fname;
       try
         res := g_Net_ReceiveResourceFile(-1{map}, tf, strm);
       except
@@ -347,22 +222,39 @@ begin
         result := '';
         exit;
       end;
-      mmd5 := MD5File(fname);
-      if (not MD5Match(mmd5, mapHash)) then
-      begin
-        e_WriteLog('error downloading map file `'+FileName+'` (bad hash)', TMsgType.Fatal);
-        result := '';
-        exit;
-      end;
-      idx := addKnownFile(fname);
-      if (idx < 0) then
+      // if it was resumed, check md5 and initiate full download if necessary
+      if tf.resumed then
       begin
-        e_WriteLog('error downloading map file `'+FileName+'`', TMsgType.Fatal);
-        result := '';
-        exit;
+        md5 := MD5File(fname);
+        // sorry for pasta, i am asshole
+        if not MD5Match(md5, tf.hash) then
+        begin
+          e_LogWritefln('resuming failed; downloading map `%s` from scratch...', [fname]);
+          try
+            DeleteFile(fname);
+            strm := createDiskFile(fname);
+          except
+            e_WriteLog('cannot create map file `'+fname+'`', TMsgType.Fatal);
+            result := '';
+            exit;
+          end;
+          try
+            res := g_Net_ReceiveResourceFile(-1{map}, tf, strm);
+          except
+            e_WriteLog('error downloading map file `'+FileName+'`', TMsgType.Fatal);
+            strm.Free;
+            result := '';
+            exit;
+          end;
+          strm.Free;
+          if (res <> 0) then
+          begin
+            e_WriteLog('error downloading map file `'+FileName+'`', TMsgType.Fatal);
+            result := '';
+            exit;
+          end;
+        end;
       end;
-      knownFiles[idx].md5 := mmd5;
-      knownFiles[idx].md5valid := true;
       result := fname;
     end;
 
@@ -371,7 +263,7 @@ begin
     begin
       res := g_Net_RequestResFileInfo(f, tf);
       if (res <> 0) then begin result := ''; exit; end;
-      wadname := g_Res_SearchResWad(false{asMap}, tf.hash);
+      wadname := g_Res_SearchResWad(false{asMap}, tf.diskName, tf.hash);
       if (length(wadname) <> 0) then
       begin
         // already here
@@ -380,9 +272,14 @@ begin
       end
       else
       begin
+        try
+          CreateDir(GameDir+'/wads/downloads');
+        except
+        end;
         fname := GameDir+'/wads/downloads/'+tf.diskName;
+        e_LogWritefln('downloading resource `%s` to `%s`...', [tf.diskName, fname]);
         try
-          strm := createDiskFile(fname);
+          strm := openDiskFileRW(fname);
         except
           e_WriteLog('cannot create resource file `'+fname+'`', TMsgType.Fatal);
           result := '';
@@ -403,20 +300,45 @@ begin
           result := '';
           exit;
         end;
-        idx := addKnownFile(fname);
-        if (idx < 0) then
+        // if it was resumed, check md5 and initiate full download if necessary
+        if tf.resumed then
         begin
-          e_WriteLog('error downloading map file `'+FileName+'`', TMsgType.Fatal);
-          result := '';
-          exit;
+          md5 := MD5File(fname);
+          // sorry for pasta, i am asshole
+          if not MD5Match(md5, tf.hash) then
+          begin
+            e_LogWritefln('resuming failed; downloading resource `%s` to `%s` from scratch...', [tf.diskName, fname]);
+            try
+              DeleteFile(fname);
+              strm := createDiskFile(fname);
+            except
+              e_WriteLog('cannot create resource file `'+fname+'`', TMsgType.Fatal);
+              result := '';
+              exit;
+            end;
+            try
+              res := g_Net_ReceiveResourceFile(f, tf, strm);
+            except
+              e_WriteLog('error downloading map file `'+FileName+'`', TMsgType.Fatal);
+              strm.Free;
+              result := '';
+              exit;
+            end;
+            strm.Free;
+            if (res <> 0) then
+            begin
+              e_WriteLog('error downloading map file `'+FileName+'`', TMsgType.Fatal);
+              result := '';
+              exit;
+            end;
+          end;
         end;
-        knownFiles[idx].md5 := tf.hash;
-        knownFiles[idx].md5valid := true;
         g_Res_PutReplacementWad(tf.diskName, fname);
       end;
     end;
   finally
     resList.Free;
+    g_Res_received_map_start := 0;
   end;
 end;