Skip to content

Commit

Permalink
Add dependency download progress, fix qavs crashing if no app is sele…
Browse files Browse the repository at this point in the history
…cted, backup qavs mods folder and restore it too (not working apparently)
  • Loading branch information
ComputerElite committed Jan 30, 2024
1 parent f7ad7b7 commit 9c0d890
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 111 deletions.
206 changes: 113 additions & 93 deletions QuestAppVersionSwitcher/FastFileDownloader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class FileDownloader
public Action OnDownloadProgress;
public Action OnDownloadError;
public Exception exception;
public bool isDone = false;

public void DownloadFile(string url, string savePath, int numConnections)
{
Expand All @@ -48,65 +49,78 @@ public void DownloadFile(string url, string savePath, int numConnections)

public void DownloadFileInternal(string url, string savePath, int numConnections)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "GET";
request.AllowAutoRedirect = true;
long fileSize = 0;
try
{
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
fileSize = response.ContentLength;
response.Close();
} catch (Exception e)
{
Logger.Log("Error while GET request: " + e);
error = true;
exception = e;
OnDownloadError?.Invoke();
return;
}
// Create an array to store the downloaded bytes for each connection
long[] bytesDownloadedArray = new long[numConnections];
long chunkSize;

if (fileSize <= 0)
long[] startPosArray;
long[] endPosArray;
long fileSize = -1;
if (numConnections > 1)
{

Logger.Log("File size is " + fileSize + ". Thus we cannot download the file");
error = true;
exception = new Exception("File size is " + fileSize + ". Thus we cannot download the file");
OnDownloadError?.Invoke();
return;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "GET";
request.AllowAutoRedirect = true;
try
{
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
fileSize = response.ContentLength;
response.Close();
} catch (Exception e)
{
Logger.Log("Error while GET request: " + e);
error = true;
exception = e;
if(OnDownloadError != null) OnDownloadError?.Invoke();
return;
}

if (fileSize <= 0)
{

Logger.Log("File size is " + fileSize + ". Thus we cannot download the file");
error = true;
exception = new Exception("File size is 0");
if(OnDownloadError != null) OnDownloadError?.Invoke();
return;
}
totalBytes = fileSize;

Logger.Log("File sizee: " + fileSize);

chunkSize = fileSize / numConnections;

startPosArray = new long[numConnections];
endPosArray = new long[numConnections];

for (int i = 0; i < numConnections; i++)
{
startPosArray[i] = i * chunkSize;
endPosArray[i] = (i + 1) * chunkSize - 1;

if (i == numConnections - 1) endPosArray[i] = fileSize - 1;

// Console.WriteLine("Connection " + i + " range: " + startPosArray[i] + "-" + endPosArray[i]);
}
}
totalBytes = fileSize;

Logger.Log("File size: " + fileSize);

long chunkSize = fileSize / numConnections;

long[] startPosArray = new long[numConnections];
long[] endPosArray = new long[numConnections];

for (int i = 0; i < numConnections; i++)
else
{
startPosArray[i] = i * chunkSize;
endPosArray[i] = (i + 1) * chunkSize - 1;

if (i == numConnections - 1) endPosArray[i] = fileSize - 1;

Console.WriteLine("Connection " + i + " range: " + startPosArray[i] + "-" + endPosArray[i]);
startPosArray = new long[] { -1 };
endPosArray = new long[] { -1 };
}

// Create an array to store the downloaded bytes for each connection
long[] bytesDownloadedArray = new long[numConnections];




// Download each chunk in a separate thread
for (int i = 0; i < numConnections; i++)
{
int chunkIndex = i;
new Thread(() =>
{
DownloadChunk(url, savePath, startPosArray[chunkIndex], endPosArray[chunkIndex], chunkIndex, ref bytesDownloadedArray);
long fs = DownloadChunk(url, savePath, startPosArray[chunkIndex], endPosArray[chunkIndex], chunkIndex, ref bytesDownloadedArray);
if (startPosArray.Length == 1) fileSize = fs; // Only set the fileSize if it's one connection only
}).Start();
}

// Wait for all threads to complete
while (true)
{
Expand All @@ -116,69 +130,81 @@ public void DownloadFileInternal(string url, string savePath, int numConnections
downloadedBytes += bytesDownloadedArray[i];
}

if (canceled || error) return;
if (canceled || error)
{
Logger.Log("Download cancelled or has error");
return;
}

//double progress = (double)downloadedBytes / fileSize * 100;
//Logger.Log("Download progress: " + progress.ToString("0.00") + "%");
OnDownloadProgress.Invoke();

//Logger.Log(downloadedBytes + " " + fileSize);
if(OnDownloadProgress != null && downloadedBytes > 0) OnDownloadProgress.Invoke();
if (downloadedBytes == fileSize) break;
Thread.Sleep(200);
Thread.Sleep(100);
}




Logger.Log("Download complete!");

// Merge downloaded chunks into a single file
using (FileStream fileStream = new FileStream(savePath, FileMode.Create, FileAccess.Write))
{
byte[] buffer = new byte[1024 * 1024 * 20]; // 20MB buffer

for (int i = 0; i < numConnections; i++)
{
using (FileStream chunkStream = new FileStream(savePath + "." + i, FileMode.Open, FileAccess.Read))
{
int bytesRead;
while ((bytesRead = chunkStream.Read(buffer, 0, buffer.Length)) > 0)
{
fileStream.Write(buffer, 0, bytesRead);
}
}

File.Delete(savePath + "." + i);
}
}
if (numConnections > 1)
{
// Merge downloaded chunks into a single file
using (FileStream fileStream = new FileStream(savePath, FileMode.Create, FileAccess.Write))
{
byte[] buffer = new byte[1024 * 1024 * 20]; // 20MB buffer

for (int i = 0; i < numConnections; i++)
{
using (FileStream chunkStream = new FileStream(savePath + "." + i, FileMode.Open, FileAccess.Read))
{
int bytesRead;
while ((bytesRead = chunkStream.Read(buffer, 0, buffer.Length)) > 0)
{
fileStream.Write(buffer, 0, bytesRead);
}
}

File.Delete(savePath + "." + i);
}
}
}


Logger.Log("File saved at " + savePath);
OnDownloadComplete?.Invoke();
isDone = true;
if(OnDownloadComplete != null) OnDownloadComplete?.Invoke();
}

Dictionary<int, int> chunkDownloadFailiures = new Dictionary<int, int>();

public void DownloadChunk(string url, string savePath, long startPos, long endPos, int chunkIndex, ref long[] bytesDownloadedArray)
public long DownloadChunk(string url, string savePath, long startPos, long endPos, int chunkIndex, ref long[] bytesDownloadedArray)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "GET";
request.AddRange(startPos, endPos);
if(startPos != -1 && endPos != -1) request.AddRange(startPos, endPos);
long totalBytesRead = 0;
try
{
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
if(endPos == -1) totalBytes = response.ContentLength;
Stream stream = response.GetResponseStream();

byte[] buffer = new byte[0xFFFF];
byte[] buffer = new byte[4096];
int bytesRead;
long totalBytesRead = 0;
string fileName = savePath + "." + chunkIndex;
if(File.Exists(fileName)) File.Delete(fileName);

string filePath = endPos == -1 ? savePath : savePath + "." + chunkIndex;

using (FileStream fileStream =
new FileStream(fileName, FileMode.Create, FileAccess.Write))
new FileStream(filePath, FileMode.Create, FileAccess.Write))
{
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
{
if (error || canceled)
{
stream.Close();
response.Close();
return;
return totalBytesRead;
}
fileStream.Write(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
Expand All @@ -193,20 +219,14 @@ public void DownloadChunk(string url, string savePath, long startPos, long endPo
}
catch (Exception e)
{
if(!chunkDownloadFailiures.ContainsKey(chunkIndex)) chunkDownloadFailiures.Add(chunkIndex, 0);
chunkDownloadFailiures[chunkIndex]++;
if (chunkDownloadFailiures[chunkIndex] >= 3)
{
Logger.Log("Error while downloading file chunk " + chunkIndex + ": " + e);
Cancel();
error = true;
exception = e;
OnDownloadError.Invoke();
return;
}
Logger.Log("Error while downloading file chunk " + chunkIndex + ": " + e + ". Retrying... (" + chunkDownloadFailiures[chunkIndex] + "/3)");
DownloadChunk(url, savePath, startPos, endPos, chunkIndex, ref bytesDownloadedArray);
Logger.Log("Error while downloading file chunk " + chunkIndex + ": " + e);
Cancel();
error = true;
exception = e;
if(OnDownloadError != null) OnDownloadError.Invoke();
}

return totalBytesRead;
}

public void Cancel()
Expand Down
24 changes: 11 additions & 13 deletions QuestAppVersionSwitcher/Mods/ExternalFilesDownloader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,24 @@
using System.Net;
using System.Threading.Tasks;
using ComputerUtils.Android.Logging;
using ComputerUtils.Android.VarUtils;
using Java.Lang;
using Exception = System.Exception;

namespace QuestAppVersionSwitcher.Mods
{
public class ExternalFilesDownloader
{
public static void DownloadUrl(string downloadUrlString, string path)
public static void DownloadUrl(string downloadUrlString, string path, int operationId, string operationPrefix)
{
WebClient client = new WebClient();
Uri uri = new Uri(downloadUrlString);
TempFile temp = new TempFile();
try
FileDownloader downloader = new FileDownloader();
downloader.OnDownloadProgress += () =>
{
client.DownloadFile(uri, temp.Path);
if (File.Exists(path)) File.Delete(path);
File.Move(temp.Path, path);
}
catch (Exception e)
{
Logger.Log("Error downloading file from " +downloadUrlString + ": " + e, LoggingType.Warning);
}
QAVSModManager.UpdateRunningOperation(operationId,
operationPrefix + " (" + SizeConverter.ByteSizeToString(downloader.downloadedBytes) + " / " +
SizeConverter.ByteSizeToString(downloader.totalBytes) + ")");
};
downloader.DownloadFileInternal(downloadUrlString, path, 1); // Internal method blocks the thread until download is complete
}

public static string DownloadStringWithTimeout(string url, int timeout)
Expand Down
7 changes: 5 additions & 2 deletions QuestAppVersionSwitcher/Mods/ModManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,11 @@ public List<IMod> AllMods
public string Scotland2LibsPath => $"/sdcard/ModData/{CoreService.coreVars.currentApp}/Modloader/libs/";

public string ConfigPath => CoreService.coreVars.QAVSModsDir + $"{CoreService.coreVars.currentApp}/modsStatus.json";
public string ModsExtractPath => CoreService.coreVars.QAVSModsDir + $"{CoreService.coreVars.currentApp}/installedMods/";

public string ModsExtractPath => GetModsExtractPath(CoreService.coreVars.currentApp);
public string GetModsExtractPath(string package)
{
return CoreService.coreVars.QAVSModsDir + package + "/installedMods/";
}
private readonly Dictionary<string, IModProvider> _modProviders = new Dictionary<string, IModProvider>();
private readonly ModConverter _modConverter = new ModConverter();
private readonly OtherFilesManager _otherFilesManager;
Expand Down
7 changes: 7 additions & 0 deletions QuestAppVersionSwitcher/Mods/QAVSModManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Java.Lang;
using Org.BouncyCastle.Asn1.Pkcs;
using AndroidX.Work;
using Java.Util.Jar;
using QuestAppVersionSwitcher.ClientModels;
using QuestPatcher.QMod;
using Exception = System.Exception;
Expand Down Expand Up @@ -64,6 +65,12 @@ public static void AddRunningOperation(QAVSOperation operation)
BroadcastOperation(operation.operationId);
//BroadcastModsAndStatus();
}

public static void UpdateRunningOperation(int operationId, string name)
{
runningOperations[operationId].name = name;
BroadcastOperation(operationId);
}

public static void MarkOperationAsError(int operationId)
{
Expand Down
5 changes: 3 additions & 2 deletions QuestAppVersionSwitcher/Mods/QPMod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -348,14 +348,15 @@ private async Task PrepareDependency(Dependency dependency, List<string> install
}
int operationId = QAVSModManager.operations;
QAVSModManager.operations++;
QAVSModManager.AddRunningOperation(new QAVSOperation { type = QAVSOperationType.DependencyDownload, name = "Downloading Dependency " + dependency.Id, taskId = taskId, operationId = operationId, modId = dependency.Id});
string operationPrefix = "Downloading Dependency " + dependency.Id;
QAVSModManager.AddRunningOperation(new QAVSOperation { type = QAVSOperationType.DependencyDownload, name = operationPrefix, taskId = taskId, operationId = operationId, modId = dependency.Id});

QPMod installedDependency;
TempFile downloadFile = new TempFile();
Logger.Log($"Downloading dependency {dependency.Id} . . .");
try
{
ExternalFilesDownloader.DownloadUrl(dependency.DownloadUrlString, downloadFile.Path);
ExternalFilesDownloader.DownloadUrl(dependency.DownloadUrlString, downloadFile.Path, operationId, operationPrefix);
}
catch (WebException ex)
{
Expand Down
4 changes: 3 additions & 1 deletion QuestAppVersionSwitcher/PatchingManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,9 @@ public static bool IsAPKModded(ZipArchive apkArchive)
[CanBeNull]
public static ModdedJson GetModdedJson()
{
using ZipArchive apk = ZipFile.OpenRead(AndroidService.FindAPKLocation(CoreService.coreVars.currentApp));
string? apkLocation = AndroidService.FindAPKLocation(CoreService.coreVars.currentApp);
if (apkLocation == null) return null;
using ZipArchive apk = ZipFile.OpenRead(apkLocation);
ModdedJson? json = GetModdedJson(apk);
return json;
}
Expand Down
Loading

0 comments on commit 9c0d890

Please sign in to comment.