blob: 752b34c3fcb9daa3480c4bc705dc1dc1a553d7ab [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:file/file.dart';
import 'package:file/local.dart';
import 'package:process/process.dart';
import 'package:path/path.dart' as path;
import 'package:uuid/uuid.dart';
import 'package:fuchsia_ctl/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 = 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.');
}
}