Skip to content

Commit

Permalink
refactor(build_transformer): move api release download and extraction…
Browse files Browse the repository at this point in the history
… to separate files (#23)

* refactor(build_transformer): move artefact download ops

* fix(build_transformer): reduce log verbosity

limit stacktraces to SEVERE and above
  • Loading branch information
takenagain authored Feb 6, 2025
1 parent afcdfd9 commit 0d2f45a
Show file tree
Hide file tree
Showing 7 changed files with 523 additions and 302 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import 'dart:io';

import 'package:http/http.dart' as http;
import 'package:komodo_wallet_build_transformer/src/steps/models/api/api_file_matching_config.dart';

abstract class ArtefactDownloader {
ArtefactDownloader({
required this.apiCommitHash,
required this.sourceUrl,
required this.apiBranch,
});

final String apiCommitHash;
final String sourceUrl;
final String apiBranch;

Future<String> fetchDownloadUrl(
ApiFileMatchingConfig matchingConfig,
String platform,
);

Future<String> downloadArtefact({
required String url,
required String destinationPath,
});

Future<void> extractArtefact({
required String filePath,
required String destinationFolder,
});
}

extension ResponseCode on http.Response {
void throwIfNotSuccessResponse() {
if (statusCode != 200) {
throw HttpException(
'Failed to fetch data: $statusCode $reasonPhrase',
);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import 'package:komodo_wallet_build_transformer/src/steps/defi_api_build_step/artefact_downloader.dart';
import 'package:komodo_wallet_build_transformer/src/steps/defi_api_build_step/dev_builds_artefact_downloader.dart';
import 'package:komodo_wallet_build_transformer/src/steps/defi_api_build_step/github_artefact_downloader.dart';
import 'package:komodo_wallet_build_transformer/src/steps/github/github_api_provider.dart';
import 'package:komodo_wallet_build_transformer/src/steps/models/api/api_build_config.dart';

class ArtefactDownloaderFactory {
static Map<String, ArtefactDownloader> fromBuildConfig(
ApiBuildConfig buildConfig, {
String? githubToken,
}) {
final sourceUrls = buildConfig.sourceUrls;
final downloaders = <String, ArtefactDownloader>{};
for (final sourceUrl in sourceUrls) {
if (sourceUrl.startsWith('https://api.github.com/repos/')) {
downloaders[sourceUrl] = createGithubArtefactDownloader(
buildConfig,
sourceUrl,
githubToken: githubToken,
);
} else {
downloaders[sourceUrl] = DevBuildsArtefactDownloader(
apiBranch: buildConfig.branch,
apiCommitHash: buildConfig.apiCommitHash,
sourceUrl: sourceUrl,
);
}
}
return downloaders;
}

static ArtefactDownloader createGithubArtefactDownloader(
ApiBuildConfig buildConfig,
String sourceUrl, {
String? githubToken,
}) {
final apiProvider = GithubApiProvider.withBaseUrl(
baseUrl: buildConfig.sourceUrls.first,
branch: buildConfig.branch,
token: githubToken,
);
return GithubArtefactDownloader(
apiCommitHash: buildConfig.apiCommitHash,
apiBranch: buildConfig.branch,
sourceUrl: buildConfig.sourceUrls.first,
githubApiProvider: apiProvider,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import 'dart:io';

import 'package:html/parser.dart' as parser;
import 'package:http/http.dart' as http;
import 'package:komodo_wallet_build_transformer/src/steps/defi_api_build_step/artefact_downloader.dart';
import 'package:komodo_wallet_build_transformer/src/steps/models/api/api_file_matching_config.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as path;

class DevBuildsArtefactDownloader implements ArtefactDownloader {
DevBuildsArtefactDownloader({
required this.apiBranch,
required this.apiCommitHash,
required this.sourceUrl,
});

final _log = Logger('DevBuildsArtefactDownloader');

@override
final String apiBranch;

@override
final String apiCommitHash;

@override
final String sourceUrl;

@override
Future<String> fetchDownloadUrl(
ApiFileMatchingConfig matchingConfig,
String platform,
) async {
final url = '$sourceUrl/$apiBranch/';
final response = await http.get(Uri.parse(url));
response.throwIfNotSuccessResponse();

final document = parser.parse(response.body);
final extensions = ['.zip'];

// Support both full and short hash variants
final fullHash = apiCommitHash;
final shortHash = apiCommitHash.substring(0, 7);
_log.info('Looking for files with hash $fullHash or $shortHash');

// Look for files with either hash length
final attemptedFiles = <String>[];
for (final element in document.querySelectorAll('a')) {
final href = element.attributes['href'];
if (href != null) attemptedFiles.add(href);
if (href != null &&
matchingConfig.matches(href) &&
extensions.any(href.endsWith)) {
if (href.contains(fullHash) || href.contains(shortHash)) {
_log.info('Found matching file: $href');
return '$sourceUrl/$apiBranch/$href';
}
}
}

final availableAssets = attemptedFiles.join('\n . - ');
_log.fine('No matching files found in $sourceUrl. '
'\nPattern: ${matchingConfig.matchingPattern}, '
'\nHashes tried: [$fullHash, $shortHash]'
'\nAvailable assets: $availableAssets');

throw Exception('Zip file not found for platform $platform');
}

@override
Future<String> downloadArtefact({
required String url,
required String destinationPath,
}) async {
_log.info('Downloading $url...');
final response = await http.get(Uri.parse(url));
response.throwIfNotSuccessResponse();

final zipFileName = path.basename(url);
final zipFilePath = path.join(destinationPath, zipFileName);

final directory = Directory(destinationPath);
if (!directory.existsSync()) {
await directory.create(recursive: true);
}

final zipFile = File(zipFilePath);
try {
await zipFile.writeAsBytes(response.bodyBytes);
} catch (e) {
_log.info('Error writing file', e);
rethrow;
}

_log.info('Downloaded $zipFileName');
return zipFilePath;
}

@override
Future<void> extractArtefact({
required String filePath,
required String destinationFolder,
}) async {
try {
// Determine the platform to use the appropriate extraction command
if (Platform.isMacOS || Platform.isLinux) {
// For macOS and Linux, use the `unzip` command with overwrite option
final result = await Process.run(
'unzip',
['-o', filePath, '-d', destinationFolder],
);
if (result.exitCode != 0) {
throw Exception('Error extracting zip file: ${result.stderr}');
}
} else if (Platform.isWindows) {
// For Windows, use PowerShell's Expand-Archive command
final result = await Process.run('powershell', [
'Expand-Archive',
'-Path',
filePath,
'-DestinationPath',
destinationFolder,
]);
if (result.exitCode != 0) {
throw Exception('Error extracting zip file: ${result.stderr}');
}
} else {
_log.severe('Unsupported platform: ${Platform.operatingSystem}');
throw UnsupportedError('Unsupported platform');
}
_log.info('Extraction completed.');
} catch (e) {
_log.shout('Failed to extract zip file: $e');
rethrow;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import 'dart:io';

import 'package:http/http.dart' as http;
import 'package:komodo_wallet_build_transformer/src/steps/defi_api_build_step/artefact_downloader.dart';
import 'package:komodo_wallet_build_transformer/src/steps/github/github_api_provider.dart';
import 'package:komodo_wallet_build_transformer/src/steps/models/api/api_file_matching_config.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as path;

class GithubArtefactDownloader implements ArtefactDownloader {
GithubArtefactDownloader({
required this.apiBranch,
required this.apiCommitHash,
required this.sourceUrl,
required this.githubApiProvider,
});

final _log = Logger('GithubArtefactDownloader');

final GithubApiProvider githubApiProvider;

@override
final String apiBranch;

@override
final String apiCommitHash;

@override
final String sourceUrl;

@override
Future<String> fetchDownloadUrl(
ApiFileMatchingConfig matchingConfig,
String platform,
) async {
final releases = await githubApiProvider.getReleases();
final fullHash = apiCommitHash;
final shortHash = apiCommitHash.substring(0, 7);

_log.info('Looking for release files with hash $fullHash or $shortHash');

// TODO! Try to find exact version release first
// if (version != null && version!.isNotEmpty) {
// _log.info('Searching for exact version match: $version');
// for (final release in releases) {
// if (release.tagName == version) {
// _log.info('Found matching release: ${release.tagName}');
// for (final asset in release.assets) {
// final fileName = path.basename(asset.browserDownloadUrl);
// _log.fine('Checking file $fileName for $platform');

// if (matchingConfig.matches(fileName)) {
// _log.info('Found matching file $fileName in version $version');
// return asset.browserDownloadUrl;
// }
// }
// _log.warning('No matching assets found in version $version. '
// 'Available assets:\n${release.assets.map((a) => ' - ${a.name}').join('\n')}');
// }
// }
// _log.warning('No exact version match found for $version');
// }

// If no exact version match found, try matching by commit hash
_log.info('Searching for commit hash match');
for (final release in releases) {
for (final asset in release.assets) {
final fileName = path.basename(asset.browserDownloadUrl);

if (matchingConfig.matches(fileName)) {
if (fileName.contains(fullHash) || fileName.contains(shortHash)) {
final commitHash = await githubApiProvider.getLatestCommitHash(
branch: release.tagName,
);
if (commitHash == apiCommitHash) {
_log.info('Found matching file by commit hash: $fileName');
return asset.browserDownloadUrl;
}
}
}
}
}

// Log available assets to help diagnose issues
final releaseAssets =
releases.expand((r) => r.assets).map((a) => ' - ${a.name}').join('\n');
_log.fine('No files found matching criteria:\n'
'Platform: $platform\n'
'Version: \$version\n'
'Hash: $fullHash or $shortHash\n'
'Pattern: ${matchingConfig.matchingPattern}\n'
'Available assets:\n$releaseAssets');

throw Exception(
'Zip file not found for platform $platform in GitHub releases. '
'Searched for version: \$version, commit: $apiCommitHash');
}

@override
Future<String> downloadArtefact({
required String url,
required String destinationPath,
}) async {
_log.info('Downloading $url...');
final response = await http.get(Uri.parse(url));
response.throwIfNotSuccessResponse();

final zipFileName = path.basename(url);
final zipFilePath = path.join(destinationPath, zipFileName);

final directory = Directory(destinationPath);
if (!directory.existsSync()) {
await directory.create(recursive: true);
}

final zipFile = File(zipFilePath);
try {
await zipFile.writeAsBytes(response.bodyBytes);
} catch (e) {
_log.info('Error writing file', e);
rethrow;
}

_log.info('Downloaded $zipFileName');
return zipFilePath;
}

@override
Future<void> extractArtefact({
required String filePath,
required String destinationFolder,
}) async {
try {
// Determine the platform to use the appropriate extraction command
if (Platform.isMacOS || Platform.isLinux) {
// For macOS and Linux, use the `unzip` command with overwrite option
final result = await Process.run(
'unzip',
['-o', filePath, '-d', destinationFolder],
);
if (result.exitCode != 0) {
throw Exception('Error extracting zip file: ${result.stderr}');
}
} else if (Platform.isWindows) {
// For Windows, use PowerShell's Expand-Archive command
final result = await Process.run('powershell', [
'Expand-Archive',
'-Path',
filePath,
'-DestinationPath',
destinationFolder,
]);
if (result.exitCode != 0) {
throw Exception('Error extracting zip file: ${result.stderr}');
}
} else {
_log.severe('Unsupported platform: ${Platform.operatingSystem}');
throw UnsupportedError('Unsupported platform');
}
_log.info('Extraction completed.');
} catch (e) {
_log.shout('Failed to extract zip file: $e');
rethrow;
}
}
}
Loading

0 comments on commit 0d2f45a

Please sign in to comment.