blob: fd36fff2ade1d7eba42830e08788e1134a9b7f5a [file] [log] [blame]
// Copyright 2019 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:fuchsia_ctl/fuchsia_ctl.dart';
import 'package:process/process.dart';
/// A wrapper around the Fuchsia SDK `pm` tool.
class PackageServer {
/// Creates a new package server.
PackageServer(
this.pmPath, {
this.processManager = const LocalProcessManager(),
}) : assert(pmPath != null),
assert(processManager != 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;
Process _pmServerProcess;
/// 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,
}) async {
assert(repo != null);
assert(port != null);
final List<String> pmCommand = <String>[
pmPath,
'serve',
'-repo', repo, //
'-l', '$address:$port',
];
stdout.writeln('Running ${pmCommand.join(' ')}');
_pmServerProcess = await processManager.start(pmCommand);
final Completer<void> serverPortCompleter = Completer<void>();
_pmServerProcess.stdout
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen((String line) {
if (line.contains('serving $repo at http://')) {
_serverPort = int.parse(line.substring(line.lastIndexOf(':') + 1));
serverPortCompleter.complete();
}
stdout.writeln(line);
});
_pmServerProcess.stderr
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen(stderr.writeln);
await serverPortCompleter.future;
}
/// 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.');
}
_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.');
}
}