Skip to content

Commit 7b44b1b

Browse files
committed
clients: fix grpcurl tar.gz extraction
Add support for extracting .tar.gz and .tgz files
1 parent 6138345 commit 7b44b1b

File tree

2 files changed

+89
-1
lines changed

2 files changed

+89
-1
lines changed

sail_ui/lib/providers/binaries/download_manager.dart

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,9 @@ class DownloadManager extends ChangeNotifier {
388388
if (fileName.endsWith('.zip')) {
389389
// extract the zip file
390390
await _extractZipFile(extractDir, filePath, downloadsDir, binary);
391+
} else if (fileName.endsWith('.tar.gz') || fileName.endsWith('.tgz')) {
392+
// extract the tar.gz file
393+
await _extractTarGzFile(extractDir, filePath, downloadsDir, binary);
391394
} else {
392395
// move the raw binary
393396
await _processRawBinary(extractDir, filePath, binary);
@@ -452,6 +455,90 @@ class DownloadManager extends ChangeNotifier {
452455
}
453456
}
454457

458+
/// Extract tar.gz file
459+
Future<void> _extractTarGzFile(Directory extractDir, String tarGzPath, Directory downloadsDir, Binary binary) async {
460+
// Create a temporary directory for extraction
461+
final tempDir = Directory(path.join(extractDir.path, 'temp', path.basenameWithoutExtension(binary.binary)));
462+
try {
463+
await tempDir.delete(recursive: true);
464+
} catch (e) {
465+
// directory probably doesn't exist, swallow!
466+
}
467+
await tempDir.create(recursive: true);
468+
469+
// Read the file bytes, decode gzip, then decode tar
470+
final bytes = await File(tarGzPath).readAsBytes();
471+
final gzipBytes = GZipDecoder().decodeBytes(bytes);
472+
final archive = TarDecoder().decodeBytes(gzipBytes);
473+
474+
try {
475+
await extractArchiveToDisk(archive, tempDir.path);
476+
477+
log.d('Moving files from temp directory to final location');
478+
await for (final entity in tempDir.list()) {
479+
final baseName = path.basename(entity.path);
480+
final targetPath = path.join(extractDir.path, baseName);
481+
482+
// Handle nested directory structure
483+
if (entity is Directory &&
484+
baseName == path.basenameWithoutExtension(path.basenameWithoutExtension(tarGzPath))) {
485+
await for (final innerEntity in entity.list()) {
486+
final innerBaseName = path.basename(innerEntity.path);
487+
488+
// Only extract the binary itself, skip LICENSE and other files
489+
if (innerBaseName == binary.binary ||
490+
innerBaseName == '${binary.binary}.exe' ||
491+
innerBaseName == path.basenameWithoutExtension(binary.binary)) {
492+
final targetFile = path.join(extractDir.path, innerBaseName);
493+
await safeMove(innerEntity, targetFile);
494+
495+
// Make binary executable on Unix-like systems
496+
if (!Platform.isWindows && innerEntity is File) {
497+
await Process.run('chmod', ['+x', targetFile]);
498+
}
499+
}
500+
}
501+
await entity.delete(recursive: true);
502+
continue;
503+
}
504+
505+
await safeMove(entity, targetPath);
506+
}
507+
508+
// Clean up temp directory
509+
await tempDir.delete(recursive: true);
510+
511+
// Get the tar.gz name without extension
512+
final tarGzBaseName = path.basenameWithoutExtension(path.basenameWithoutExtension(tarGzPath));
513+
final expectedDirPath = path.join(extractDir.path, tarGzBaseName);
514+
515+
if (await Directory(expectedDirPath).exists()) {
516+
final innerDir = Directory(expectedDirPath);
517+
518+
for (final entity in innerDir.listSync()) {
519+
final entityBaseName = path.basename(entity.path);
520+
521+
// Only extract the binary itself, skip LICENSE and other files
522+
if (entityBaseName == binary.binary ||
523+
entityBaseName == '${binary.binary}.exe' ||
524+
entityBaseName == path.basenameWithoutExtension(binary.binary)) {
525+
final newPath = path.join(extractDir.path, entityBaseName);
526+
await safeMove(entity, newPath);
527+
528+
// Make binary executable on Unix-like systems
529+
if (!Platform.isWindows && entity is File) {
530+
await Process.run('chmod', ['+x', newPath]);
531+
}
532+
}
533+
}
534+
await innerDir.delete(recursive: true);
535+
}
536+
} catch (e) {
537+
log.e('Extraction error: $e\nStack trace: ${StackTrace.current}');
538+
rethrow;
539+
}
540+
}
541+
455542
/// Process raw binary file
456543
Future<void> _processRawBinary(Directory extractDir, String binaryPath, Binary binary) async {
457544
final downloadedFile = File(binaryPath);

sail_ui/lib/widgets/console/integrated_console_view.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,8 @@ class _IntegratedConsoleViewState extends State<IntegratedConsoleView> {
178178
}
179179
}
180180

181-
final binaryPaths = paths.where((p) => Directory(p).existsSync()).toList();
181+
// toSet removes duplicates
182+
final binaryPaths = paths.where((p) => Directory(p).existsSync()).toSet().toList();
182183

183184
// enforcer-cli is just a grpcurl-wrapper, so it's available if grpcurl is available
184185
availableCLIs['enforcer-cli'] = availableCLIs['grpcurl'] ?? false;

0 commit comments

Comments
 (0)