| // Copyright 2013 The Flutter 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:convert'; |
| import 'dart:io'; |
| |
| import 'package:file/file.dart'; |
| import 'package:file/local.dart'; |
| import 'package:path/path.dart' as path; |
| import 'package:process/process.dart'; |
| import 'package:uuid/uuid.dart'; |
| |
| import '../fuchsia_ctl.dart'; |
| |
| /// A wrapper around the Fuchsia SDK `pm` tool. |
| class PackageServer { |
| /// Creates a new package server. |
| PackageServer( |
| this.pmPath, { |
| this.processManager = const LocalProcessManager(), |
| this.fileSystem = const LocalFileSystem(), |
| }) : assert(pmPath != null), |
| assert(processManager != null), |
| assert(fileSystem != null); |
| |
| /// The path on the file system to the `pm` executable. |
| final String pmPath; |
| |
| /// The process manager to use for launching `pm`. |
| final ProcessManager processManager; |
| |
| /// The file sytem for the package server. |
| final FileSystem fileSystem; |
| |
| Process _pmServerProcess; |
| |
| /// Path to the port file generated by `pm`. |
| String portPath; |
| |
| /// The port the server is listening on, if the server is running. |
| /// |
| /// Throws a [StateError] if accessed when the server is not running. |
| int get serverPort { |
| if (_pmServerProcess == null) { |
| throw StateError('Attempted to get port before starting server.'); |
| } |
| return _serverPort; |
| } |
| |
| int _serverPort; |
| |
| /// Is the server up? |
| bool get serving { |
| return _pmServerProcess != null; |
| } |
| |
| /// Creates a new local repository and associated key material. |
| /// |
| /// Corresponds to `pm newrepo`. |
| Future<OperationResult> newRepo(String repo) async { |
| return OperationResult.fromProcessResult( |
| await processManager.run( |
| <String>[ |
| pmPath, |
| 'newrepo', |
| '-repo', repo, // |
| ], |
| ), |
| ); |
| } |
| |
| /// Publishes an archive package for use on a device with the specified |
| /// .far files. |
| Future<OperationResult> publishRepo(String repo, String farFile) async { |
| return OperationResult.fromProcessResult( |
| await processManager.run( |
| <String>[ |
| pmPath, |
| 'publish', |
| '-a', |
| '-repo', repo, // |
| '-f', farFile, |
| ], |
| ), |
| ); |
| } |
| |
| /// Starts a server for the specified repo path and port. |
| /// |
| /// Use port 0 to have the server choose a port. The acutal port used |
| /// will be avialalbe in the [serverPort] property after this method |
| /// returns. The stdout and stderr of the server will be printed to [stdout] |
| /// and [stderr], respectively. |
| Future<void> serveRepo( |
| String repo, { |
| String address = '', |
| int port = 0, |
| String portFilePath, |
| }) async { |
| assert(repo != null); |
| assert(port != null); |
| |
| final String uuid = const Uuid().v4(); |
| portPath = portFilePath ?? |
| path.join(fileSystem.systemTempDirectory.path, '${uuid}_port.txt'); |
| final List<String> pmCommand = <String>[ |
| pmPath, |
| 'serve', |
| '-repo', |
| repo, |
| '-l', |
| '$address:$port', |
| '-f', |
| portPath, |
| ]; |
| stdout.writeln('Running ${pmCommand.join(' ')}'); |
| _pmServerProcess = await processManager.start(pmCommand); |
| await Future<void>.delayed(const Duration(seconds: 5), () async { |
| final String portString = await fileSystem.file(portPath).readAsString(); |
| _serverPort = int.parse(portString); |
| }); |
| _pmServerProcess.stdout |
| .transform(utf8.decoder) |
| .transform(const LineSplitter()) |
| .listen(stdout.writeln); |
| _pmServerProcess.stderr |
| .transform(utf8.decoder) |
| .transform(const LineSplitter()) |
| .listen(stderr.writeln); |
| } |
| |
| /// Closes a running server. |
| /// |
| /// Calling this before calling [serveRepo] will result in a [StateError]. |
| Future<OperationResult> close() async { |
| if (_pmServerProcess == null) { |
| throw StateError('Must call serveRepo before calling close.'); |
| } |
| await fileSystem.file(portPath).delete(); |
| _pmServerProcess.kill(); |
| final int exitCode = await _pmServerProcess.exitCode; |
| _pmServerProcess = null; |
| if (exitCode == 0) { |
| return OperationResult.success(); |
| } |
| return OperationResult.error( |
| 'The "pm" executable exited with non-zero exit code.'); |
| } |
| } |