LLM for Unity  v2.3.0
Create characters in Unity with LLMs!
Loading...
Searching...
No Matches
ResumingWebClient.cs
Go to the documentation of this file.
1
3using System;
4using System.Collections.Generic;
5using System.IO;
6using System.Net;
7using System.Threading;
8using System.Threading.Tasks;
9
10namespace LLMUnity
11{
16 public class ResumingWebClient : WebClient
17 {
18 private const int timeoutMs = 30 * 1000;
19 private SynchronizationContext _context;
20 private const int DefaultDownloadBufferLength = 65536;
21 List<WebRequest> requests = new List<WebRequest>();
22
23 public ResumingWebClient()
24 {
25 _context = SynchronizationContext.Current ?? new SynchronizationContext();
26 }
27
28 public long GetURLFileSize(string address)
29 {
30 return GetURLFileSize(new Uri(address));
31 }
32
33 public long GetURLFileSize(Uri address)
34 {
35 WebRequest request = GetWebRequest(address);
36 request.Method = "HEAD";
37 WebResponse response = request.GetResponse();
38 return response.ContentLength;
39 }
40
41 public Task DownloadFileTaskAsyncResume(Uri address, string fileName, bool resume = false, Callback<float> progressCallback = null)
42 {
43 var tcs = new TaskCompletionSource<object>(address);
44 FileStream fs = null;
45 long bytesToSkip = 0;
46
47 try
48 {
49 FileMode filemode = FileMode.Create;
50 if (resume)
51 {
52 var fileInfo = new FileInfo(fileName);
53 if (fileInfo.Exists) bytesToSkip = fileInfo.Length;
54 }
55
56 WebRequest request = GetWebRequest(address);
57 if (request is HttpWebRequest webRequest && bytesToSkip > 0)
58 {
59 long remoteFileSize = GetURLFileSize(address);
60 if (bytesToSkip >= remoteFileSize)
61 {
62 LLMUnitySetup.Log($"File is already fully downloaded: {fileName}");
63 tcs.TrySetResult(true);
64 return tcs.Task;
65 }
66
67 filemode = FileMode.Append;
68 LLMUnitySetup.Log($"File exists at {fileName}, skipping {bytesToSkip} bytes");
69 webRequest.AddRange(bytesToSkip);
70 webRequest.ReadWriteTimeout = timeoutMs;
71 }
72
73 fs = new FileStream(fileName, filemode, FileAccess.Write);
74 DownloadBitsAsync(request, fs, bytesToSkip, progressCallback, tcs);
75 }
76 catch (Exception e)
77 {
78 fs?.Close();
79 tcs.TrySetException(e);
80 }
81
82 return tcs.Task;
83 }
84
85 public void CancelDownloadAsync()
86 {
87 LLMUnitySetup.Log("Cancellation requested, aborting download.");
88 foreach (WebRequest request in requests) AbortRequest(request);
89 requests.Clear();
90 }
91
92 public void AbortRequest(WebRequest request)
93 {
94 try
95 {
96 request?.Abort();
97 }
98 catch (Exception e)
99 {
100 LLMUnitySetup.LogError($"Error aborting request: {e.Message}");
101 }
102 }
103
104 private async void DownloadBitsAsync(WebRequest request, Stream writeStream, long bytesToSkip = 0, Callback<float> progressCallback = null, TaskCompletionSource<object> tcs = null)
105 {
106 try
107 {
108 requests.Add(request);
109 WebResponse response = await request.GetResponseAsync().ConfigureAwait(false);
110
111 long contentLength = response.ContentLength;
112 byte[] copyBuffer = new byte[contentLength == -1 || contentLength > DefaultDownloadBufferLength ? DefaultDownloadBufferLength : contentLength];
113
114 long TotalBytesToReceive = Math.Max(contentLength, 0) + bytesToSkip;
115 long BytesReceived = bytesToSkip;
116
117 using (writeStream)
118 using (Stream readStream = response.GetResponseStream())
119 {
120 if (readStream != null)
121 {
122 while (true)
123 {
124 int bytesRead = await readStream.ReadAsync(new Memory<byte>(copyBuffer)).ConfigureAwait(false);
125 if (bytesRead == 0)
126 {
127 break;
128 }
129
130 BytesReceived += bytesRead;
131 if (BytesReceived != TotalBytesToReceive)
132 {
133 PostProgressChanged(progressCallback, BytesReceived, TotalBytesToReceive);
134 }
135
136 await writeStream.WriteAsync(new ReadOnlyMemory<byte>(copyBuffer, 0, bytesRead)).ConfigureAwait(false);
137 }
138 }
139
140 if (TotalBytesToReceive < 0)
141 {
142 TotalBytesToReceive = BytesReceived;
143 }
144 PostProgressChanged(progressCallback, BytesReceived, TotalBytesToReceive);
145 }
146 tcs.TrySetResult(true);
147 }
148 catch (Exception e)
149 {
150 tcs.TrySetException(e);
151 LLMUnitySetup.LogError(e.Message);
152 AbortRequest(request);
153 tcs.TrySetResult(false);
154 }
155 finally
156 {
157 writeStream?.Close();
158 requests.Remove(request);
159 }
160 }
161
162 private void PostProgressChanged(Callback<float> progressCallback, long BytesReceived, long TotalBytesToReceive)
163 {
164 if (progressCallback != null && BytesReceived > 0)
165 {
166 float progressPercentage = TotalBytesToReceive < 0 ? 0 : TotalBytesToReceive == 0 ? 1 : (float)BytesReceived / TotalBytesToReceive;
167 _context.Post(_ => progressCallback?.Invoke(progressPercentage), null);
168 }
169 }
170 }
171}
Class implementing helper functions for setup and process management.
Class implementing a resumable Web client.