// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:async'; import 'dart:io'; import 'package:archive/archive.dart'; import 'package:path/path.dart' as path; import 'context.dart'; import 'process.dart'; /// Returns [OperatingSystemUtils] active in the current app context (i.e. zone). OperatingSystemUtils get os { return context[OperatingSystemUtils] ?? (context[OperatingSystemUtils] = new OperatingSystemUtils._()); } abstract class OperatingSystemUtils { factory OperatingSystemUtils._() { if (Platform.isWindows) { return new _WindowsUtils(); } else { return new _PosixUtils(); } } OperatingSystemUtils._private(); String get operatingSystem => Platform.operatingSystem; bool get isMacOS => operatingSystem == 'macos'; bool get isWindows => operatingSystem == 'windows'; bool get isLinux => operatingSystem == 'linux'; /// Make the given file executable. This may be a no-op on some platforms. ProcessResult makeExecutable(File file); /// Return the path (with symlinks resolved) to the given executable, or `null` /// if `which` was not able to locate the binary. File which(String execName); void unzip(File file, Directory targetDirectory); } class _PosixUtils extends OperatingSystemUtils { _PosixUtils() : super._private(); @override ProcessResult makeExecutable(File file) { return Process.runSync('chmod', <String>['a+x', file.path]); } /// Return the path (with symlinks resolved) to the given executable, or `null` /// if `which` was not able to locate the binary. @override File which(String execName) { ProcessResult result = Process.runSync('which', <String>[execName]); if (result.exitCode != 0) return null; String path = result.stdout.trim().split('\n').first.trim(); return new File(new File(path).resolveSymbolicLinksSync()); } // unzip -o -q zipfile -d dest @override void unzip(File file, Directory targetDirectory) { runSync(<String>['unzip', '-o', '-q', file.path, '-d', targetDirectory.path]); } } class _WindowsUtils extends OperatingSystemUtils { _WindowsUtils() : super._private(); // This is a no-op. @override ProcessResult makeExecutable(File file) { return new ProcessResult(0, 0, null, null); } @override File which(String execName) { ProcessResult result = Process.runSync('where', <String>[execName]); if (result.exitCode != 0) return null; return new File(result.stdout.trim().split('\n').first.trim()); } @override void unzip(File file, Directory targetDirectory) { Archive archive = new ZipDecoder().decodeBytes(file.readAsBytesSync()); for (ArchiveFile archiveFile in archive.files) { // The archive package doesn't correctly set isFile. if (!archiveFile.isFile || archiveFile.name.endsWith('/')) continue; File destFile = new File(path.join(targetDirectory.path, archiveFile.name)); if (!destFile.parent.existsSync()) destFile.parent.createSync(recursive: true); destFile.writeAsBytesSync(archiveFile.content); } } } Future<int> findAvailablePort() async { ServerSocket socket = await ServerSocket.bind(InternetAddress.LOOPBACK_IP_V4, 0); int port = socket.port; await socket.close(); return port; } const int _kMaxSearchIterations = 5; /// This method will attempt to return a port close to or the same as /// [defaultPort]. Failing that, it will return any available port. Future<int> findPreferredPort(int defaultPort, { int searchStep: 2 }) async { int iterationCount = 0; while (iterationCount < _kMaxSearchIterations) { int port = defaultPort + iterationCount * searchStep; if (await _isPortAvailable(port)) return port; iterationCount++; } return findAvailablePort(); } Future<bool> _isPortAvailable(int port) async { try { ServerSocket socket = await ServerSocket.bind(InternetAddress.LOOPBACK_IP_V4, port); await socket.close(); return true; } catch (error) { return false; } }