From 651f063582ce4ebc8eb1b36025d7553675005995 Mon Sep 17 00:00:00 2001
From: Ketmar Dark <ketmar@ketmar.no-ip.org>
Date: Sat, 12 Oct 2019 04:47:26 +0300
Subject: [PATCH] net: implemented (half-assed) download resuming

---
 src/game/g_net.pas            | 14 ++++++-
 src/game/g_res_downloader.pas | 71 ++++++++++++++++++++++++++++++++++-
 src/shared/utils.pas          | 26 +++++++++++++
 3 files changed, 108 insertions(+), 3 deletions(-)

diff --git a/src/game/g_net.pas b/src/game/g_net.pas
index 251e06b..db8db65 100644
--- a/src/game/g_net.pas
+++ b/src/game/g_net.pas
@@ -90,6 +90,7 @@ type
     lastAckTime: Int64; // msecs; if not "in progress", we're waiting for the first ack
     inProgress: Boolean;
     diskBuffer: PChar; // of `chunkSize` bytes
+    resumed: Boolean;
   end;
 
   TNetClient = record
@@ -1885,17 +1886,28 @@ var
   chunk: Integer;
   csize: Integer;
   buf: PChar = nil;
+  resumed: Boolean;
   //stx: Int64;
 begin
+  tf.resumed := false;
+  e_LogWritefln('file `%s`, size=%d (%d)', [tf.diskName, Integer(strm.size), tf.size], TMsgType.Notify);
+  // check if we should resume downloading
+  resumed := (strm.size > tf.chunkSize) and (strm.size < tf.size);
   // send request
   trans_omsg.Clear();
   trans_omsg.Write(Byte(NTF_CLIENT_START));
-  trans_omsg.Write(LongInt(0));
+  if resumed then chunk := strm.size div tf.chunkSize else chunk := 0;
+  trans_omsg.Write(LongInt(chunk));
   if not ftransSendClientMsg(trans_omsg) then begin result := -1; exit; end;
 
+  strm.Seek(chunk*tf.chunkSize, soFromBeginning);
   chunkTotal := (tf.size+tf.chunkSize-1) div tf.chunkSize;
   e_LogWritefln('receiving file `%s` (%d chunks)', [tf.diskName, chunkTotal], TMsgType.Notify);
   g_Game_SetLoadingText('downloading "'+ExtractFileName(tf.diskName)+'"', chunkTotal, False);
+  tf.resumed := resumed;
+
+  if (chunk > 0) then g_Game_StepLoading(chunk);
+  nextChunk := chunk;
 
   // wait for reply data
   FillChar(ev, SizeOf(ev), 0);
diff --git a/src/game/g_res_downloader.pas b/src/game/g_res_downloader.pas
index ae645a1..b2844c6 100644
--- a/src/game/g_res_downloader.pas
+++ b/src/game/g_res_downloader.pas
@@ -161,6 +161,7 @@ var
   strm: TStream;
   fname: AnsiString;
   wadname: AnsiString;
+  md5: TMD5Digest;
 begin
   //SetLength(mapData.ExternalResources, 0);
   result := '';
@@ -199,7 +200,7 @@ begin
       end;
       fname := GameDir+'/maps/downloads/'+FileName;
       try
-        strm := createDiskFile(fname);
+        strm := openDiskFileRW(fname);
       except
         e_WriteLog('cannot create map file `'+FileName+'`', TMsgType.Fatal);
         result := '';
@@ -221,6 +222,39 @@ begin
         result := '';
         exit;
       end;
+      // if it was resumed, check md5 and initiate full download if necessary
+      if tf.resumed then
+      begin
+        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;
       result := fname;
     end;
 
@@ -245,7 +279,7 @@ begin
         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 := '';
@@ -266,6 +300,39 @@ begin
           result := '';
           exit;
         end;
+        // if it was resumed, check md5 and initiate full download if necessary
+        if tf.resumed then
+        begin
+          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;
         g_Res_PutReplacementWad(tf.diskName, fname);
       end;
     end;
diff --git a/src/shared/utils.pas b/src/shared/utils.pas
index a854ca4..215e60a 100644
--- a/src/shared/utils.pas
+++ b/src/shared/utils.pas
@@ -110,6 +110,8 @@ function isWadNamesEqu (wna, wnb: AnsiString): Boolean;
 // they throws
 function openDiskFileRO (pathname: AnsiString): TStream;
 function createDiskFile (pathname: AnsiString): TStream;
+// creates file if necessary
+function openDiskFileRW (pathname: AnsiString): TStream;
 
 // little endian
 procedure writeSign (st: TStream; const sign: AnsiString);
@@ -1217,6 +1219,30 @@ begin
 end;
 
 
+function openDiskFileRW (pathname: AnsiString): TStream;
+var
+  path: AnsiString;
+  oldname: AnsiString;
+begin
+  //writeln('*** TRYING R/W FILE "', pathname, '"');
+  path := ExtractFilePath(pathname);
+  if length(path) > 0 then
+  begin
+    if not findFileCI(path, true) then raise Exception.Create('can''t create file "'+pathname+'"');
+  end;
+  oldname := pathname;
+  if findFileCI(oldname) then
+  begin
+    //writeln('*** found old file "', oldname, '"');
+    result := TFileStream.Create(oldname, fmOpenReadWrite or fmShareDenyWrite);
+  end
+  else
+  begin
+    result := TFileStream.Create(path+ExtractFileName(pathname), fmCreate);
+  end;
+end;
+
+
 procedure writeIntegerLE (st: TStream; vp: Pointer; size: Integer);
 {$IFDEF ENDIAN_LITTLE}
 begin
-- 
2.29.2