mirror of
https://github.com/Auties00/Reboot-Launcher.git
synced 2026-01-13 19:22:22 +01:00
Final version
This commit is contained in:
@@ -1,22 +1,29 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:reboot_common/common.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
final Uri _manifestSourceUrl = Uri.parse(
|
||||
"https://raw.githubusercontent.com/simplyblk/Fortnitebuilds/main/README.md");
|
||||
final Dio _dio = Dio();
|
||||
final String _manifestSourceUrl = "https://raw.githubusercontent.com/simplyblk/Fortnitebuilds/main/README.md";
|
||||
final RegExp _rarProgressRegex = RegExp("^((100)|(\\d{1,2}(.\\d*)?))%\$");
|
||||
|
||||
Future<List<FortniteBuild>> fetchBuilds(ignored) async {
|
||||
var response = await http.get(_manifestSourceUrl);
|
||||
var response = await _dio.get<String>(
|
||||
_manifestSourceUrl,
|
||||
options: Options(
|
||||
responseType: ResponseType.plain
|
||||
)
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception("Erroneous status code: ${response.statusCode}");
|
||||
}
|
||||
|
||||
var results = <FortniteBuild>[];
|
||||
for (var line in response.body.split("\n")) {
|
||||
for (var line in response.data?.split("\n") ?? []) {
|
||||
if(!line.startsWith("|")) {
|
||||
continue;
|
||||
}
|
||||
@@ -44,77 +51,118 @@ Future<void> downloadArchiveBuild(ArchiveDownloadOptions options) async {
|
||||
var stopped = _setupLifecycle(options);
|
||||
var outputDir = Directory("${options.destination.path}\\.build");
|
||||
outputDir.createSync(recursive: true);
|
||||
try {
|
||||
options.destination.createSync(recursive: true);
|
||||
var fileName = options.archiveUrl.substring(options.archiveUrl.lastIndexOf("/") + 1);
|
||||
var extension = path.extension(fileName);
|
||||
var tempFile = File("${outputDir.path}\\$fileName");
|
||||
if(tempFile.existsSync()) {
|
||||
tempFile.deleteSync(recursive: true);
|
||||
}
|
||||
|
||||
await _download(options, tempFile, stopped);
|
||||
await _extract(stopped, extension, tempFile, options);
|
||||
delete(outputDir);
|
||||
} catch(message) {
|
||||
throw Exception("Cannot download build: $message");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _download(ArchiveDownloadOptions options, File tempFile, Completer<dynamic> stopped) async {
|
||||
var client = http.Client();
|
||||
var request = http.Request("GET", Uri.parse(options.archiveUrl));
|
||||
request.headers['Connection'] = 'Keep-Alive';
|
||||
var response = await client.send(request);
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception("Erroneous status code: ${response.statusCode}");
|
||||
options.destination.createSync(recursive: true);
|
||||
var fileName = options.archiveUrl.substring(options.archiveUrl.lastIndexOf("/") + 1);
|
||||
var extension = path.extension(fileName);
|
||||
var tempFile = File("${outputDir.path}\\$fileName");
|
||||
if(tempFile.existsSync()) {
|
||||
tempFile.deleteSync(recursive: true);
|
||||
}
|
||||
|
||||
var startTime = DateTime.now().millisecondsSinceEpoch;
|
||||
var length = response.contentLength!;
|
||||
var received = 0;
|
||||
var sink = tempFile.openWrite();
|
||||
var subscription = response.stream.listen((data) async {
|
||||
received += data.length;
|
||||
var now = DateTime.now();
|
||||
var progress = (received / length) * 100;
|
||||
var msLeft = startTime + (now.millisecondsSinceEpoch - startTime) * length / received - now.millisecondsSinceEpoch;
|
||||
var minutesLeft = (msLeft / 1000 / 60).round();
|
||||
options.port.send(ArchiveDownloadProgress(progress, minutesLeft, false));
|
||||
sink.add(data);
|
||||
});
|
||||
var response = _downloadFile(options, tempFile, startTime);
|
||||
await Future.any([stopped.future, response]);
|
||||
if(!stopped.isCompleted) {
|
||||
var awaitedResponse = await response;
|
||||
if (!awaitedResponse.statusCode.toString().startsWith("20")) {
|
||||
throw Exception("Erroneous status code: ${awaitedResponse.statusCode}");
|
||||
}
|
||||
|
||||
await Future.any([stopped.future, subscription.asFuture()]);
|
||||
if(stopped.isCompleted) {
|
||||
await subscription.cancel();
|
||||
}else {
|
||||
await sink.flush();
|
||||
await sink.close();
|
||||
await sink.done;
|
||||
await _extract(stopped, extension, tempFile, options);
|
||||
}
|
||||
|
||||
delete(outputDir);
|
||||
}
|
||||
|
||||
Future<Response> _downloadFile(ArchiveDownloadOptions options, File tempFile, int startTime, [int? byteStart = null]) {
|
||||
var received = byteStart ?? 0;
|
||||
return _dio.download(
|
||||
options.archiveUrl,
|
||||
tempFile.path,
|
||||
onReceiveProgress: (data, length) {
|
||||
received = data;
|
||||
var now = DateTime.now();
|
||||
var progress = (received / length) * 100;
|
||||
var msLeft = startTime + (now.millisecondsSinceEpoch - startTime) * length / received - now.millisecondsSinceEpoch;
|
||||
var minutesLeft = (msLeft / 1000 / 60).round();
|
||||
options.port.send(ArchiveDownloadProgress(progress, minutesLeft, false));
|
||||
},
|
||||
deleteOnError: false,
|
||||
options: Options(
|
||||
headers: byteStart == null ? null : {
|
||||
"Range": "bytes=${byteStart}-"
|
||||
}
|
||||
)
|
||||
).catchError((error) => _downloadFile(options, tempFile, startTime, received));
|
||||
}
|
||||
|
||||
Future<void> _extract(Completer<dynamic> stopped, String extension, File tempFile, ArchiveDownloadOptions options) async {
|
||||
if(stopped.isCompleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
options.port.send(ArchiveDownloadProgress(0, -1, true));
|
||||
var startTime = DateTime.now().millisecondsSinceEpoch;
|
||||
Process? process;
|
||||
switch (extension.toLowerCase()) {
|
||||
case '.zip':
|
||||
case ".zip":
|
||||
process = await Process.start(
|
||||
'tar',
|
||||
['-xf', tempFile.path, '-C', options.destination.path],
|
||||
mode: ProcessStartMode.inheritStdio
|
||||
"${assetsDirectory.path}\\build\\7zip.exe",
|
||||
["a", "-bsp1", '-o"${options.destination.path}"', tempFile.path]
|
||||
);
|
||||
process.stdout.listen((bytes) {
|
||||
var now = DateTime.now().millisecondsSinceEpoch;
|
||||
var data = utf8.decode(bytes);
|
||||
if(data == "Everything is Ok") {
|
||||
options.port.send(ArchiveDownloadProgress(100, 0, true));
|
||||
return;
|
||||
}
|
||||
|
||||
var element = data.trim().split(" ")[0];
|
||||
if(!element.endsWith("%")) {
|
||||
return;
|
||||
}
|
||||
|
||||
var percentage = int.parse(element.substring(0, element.length - 1));
|
||||
if(percentage == 0) {
|
||||
options.port.send(ArchiveDownloadProgress(percentage.toDouble(), null, true));
|
||||
return;
|
||||
}
|
||||
|
||||
_onProgress(startTime, now, percentage, options);
|
||||
});
|
||||
break;
|
||||
case '.rar':
|
||||
case ".rar":
|
||||
process = await Process.start(
|
||||
'${assetsDirectory.path}\\build\\winrar.exe',
|
||||
['x', tempFile.path, '*.*', options.destination.path],
|
||||
mode: ProcessStartMode.inheritStdio
|
||||
"${assetsDirectory.path}\\build\\winrar.exe",
|
||||
["x", "-o+", tempFile.path, "*.*", options.destination.path]
|
||||
);
|
||||
process.stdout.listen((event) {
|
||||
var now = DateTime.now().millisecondsSinceEpoch;
|
||||
var data = utf8.decode(event);
|
||||
data.replaceAll("\r", "")
|
||||
.replaceAll("\b", "")
|
||||
.trim()
|
||||
.split("\n")
|
||||
.forEach((entry) {
|
||||
if(entry == "All OK") {
|
||||
options.port.send(ArchiveDownloadProgress(100, 0, true));
|
||||
return;
|
||||
}
|
||||
|
||||
var element = _rarProgressRegex.firstMatch(entry)?.group(1);
|
||||
if(element == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var percentage = int.parse(element);
|
||||
if(percentage == 0) {
|
||||
options.port.send(ArchiveDownloadProgress(percentage.toDouble(), null, true));
|
||||
return;
|
||||
}
|
||||
|
||||
_onProgress(startTime, now, percentage, options);
|
||||
});
|
||||
});
|
||||
process.stderr.listen((event) {
|
||||
var data = utf8.decode(event);
|
||||
options.port.send(data);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw ArgumentError("Unexpected file extension: $extension}");
|
||||
@@ -123,6 +171,12 @@ Future<void> _extract(Completer<dynamic> stopped, String extension, File tempFil
|
||||
await Future.any([stopped.future, process.exitCode]);
|
||||
}
|
||||
|
||||
void _onProgress(int startTime, int now, int percentage, ArchiveDownloadOptions options) {
|
||||
var msLeft = startTime + (now - startTime) * 100 / percentage - now;
|
||||
var minutesLeft = (msLeft / 1000 / 60).round();
|
||||
options.port.send(ArchiveDownloadProgress(percentage.toDouble(), minutesLeft, true));
|
||||
}
|
||||
|
||||
Completer<dynamic> _setupLifecycle(ArchiveDownloadOptions options) {
|
||||
var stopped = Completer();
|
||||
var lifecyclePort = ReceivePort();
|
||||
@@ -133,20 +187,4 @@ Completer<dynamic> _setupLifecycle(ArchiveDownloadOptions options) {
|
||||
});
|
||||
options.port.send(lifecyclePort.sendPort);
|
||||
return stopped;
|
||||
}
|
||||
|
||||
class ArchiveDownloadOptions {
|
||||
String archiveUrl;
|
||||
Directory destination;
|
||||
SendPort port;
|
||||
|
||||
ArchiveDownloadOptions(this.archiveUrl, this.destination, this.port);
|
||||
}
|
||||
|
||||
class ArchiveDownloadProgress {
|
||||
final double progress;
|
||||
final int minutesLeft;
|
||||
final bool extracting;
|
||||
|
||||
ArchiveDownloadProgress(this.progress, this.minutesLeft, this.extracting);
|
||||
}
|
||||
Reference in New Issue
Block a user