This reverts commit 59a6ba7cf81093267b5e8ac1a9b35e3fae836db1.
This commit is contained in:
parent
2f4e3ce5b0
commit
6c9259ae7c
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// @dart = 2.8
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:pool/pool.dart';
|
||||
|
||||
@ -27,15 +29,15 @@ class BundleBuilder {
|
||||
/// The default `mainPath` is `lib/main.dart`.
|
||||
/// The default `manifestPath` is `pubspec.yaml`
|
||||
Future<void> build({
|
||||
required TargetPlatform platform,
|
||||
required BuildInfo buildInfo,
|
||||
FlutterProject? project,
|
||||
String? mainPath,
|
||||
@required TargetPlatform platform,
|
||||
@required BuildInfo buildInfo,
|
||||
FlutterProject project,
|
||||
String mainPath,
|
||||
String manifestPath = defaultManifestPath,
|
||||
String? applicationKernelFilePath,
|
||||
String? depfilePath,
|
||||
String? assetDirPath,
|
||||
@visibleForTesting BuildSystem? buildSystem,
|
||||
String applicationKernelFilePath,
|
||||
String depfilePath,
|
||||
String assetDirPath,
|
||||
@visibleForTesting BuildSystem buildSystem
|
||||
}) async {
|
||||
project ??= FlutterProject.current();
|
||||
mainPath ??= defaultMainPath;
|
||||
@ -50,7 +52,7 @@ class BundleBuilder {
|
||||
buildDir: project.dartTool.childDirectory('flutter_build'),
|
||||
cacheDir: globals.cache.getRoot(),
|
||||
flutterRootDir: globals.fs.directory(Cache.flutterRoot),
|
||||
engineVersion: globals.artifacts!.isLocalEngine
|
||||
engineVersion: globals.artifacts.isLocalEngine
|
||||
? null
|
||||
: globals.flutterVersion.engineRevision,
|
||||
defines: <String, String>{
|
||||
@ -60,7 +62,7 @@ class BundleBuilder {
|
||||
kDeferredComponents: 'false',
|
||||
...buildInfo.toBuildSystemEnvironment(),
|
||||
},
|
||||
artifacts: globals.artifacts!,
|
||||
artifacts: globals.artifacts,
|
||||
fileSystem: globals.fs,
|
||||
logger: globals.logger,
|
||||
processManager: globals.processManager,
|
||||
@ -70,7 +72,7 @@ class BundleBuilder {
|
||||
final Target target = buildInfo.mode == BuildMode.debug
|
||||
? const CopyFlutterBundle()
|
||||
: const ReleaseCopyFlutterBundle();
|
||||
final BuildResult result = await buildSystem!.build(target, environment);
|
||||
final BuildResult result = await buildSystem.build(target, environment);
|
||||
|
||||
if (!result.success) {
|
||||
for (final ExceptionMeasurement measurement in result.exceptions.values) {
|
||||
@ -106,14 +108,14 @@ class BundleBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
Future<AssetBundle?> buildAssets({
|
||||
required String manifestPath,
|
||||
String? assetDirPath,
|
||||
String? packagesPath,
|
||||
TargetPlatform? targetPlatform,
|
||||
Future<AssetBundle> buildAssets({
|
||||
String manifestPath,
|
||||
String assetDirPath,
|
||||
@required String packagesPath,
|
||||
TargetPlatform targetPlatform,
|
||||
}) async {
|
||||
assetDirPath ??= getAssetBuildDirectory();
|
||||
packagesPath ??= globals.fs.path.absolute('.packages');
|
||||
packagesPath ??= globals.fs.path.absolute(packagesPath);
|
||||
|
||||
// Build the asset bundle.
|
||||
final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle();
|
||||
@ -133,7 +135,7 @@ Future<AssetBundle?> buildAssets({
|
||||
Future<void> writeBundle(
|
||||
Directory bundleDir,
|
||||
Map<String, DevFSContent> assetEntries,
|
||||
{ Logger? loggerOverride }
|
||||
{ Logger loggerOverride }
|
||||
) async {
|
||||
loggerOverride ??= globals.logger;
|
||||
if (bundleDir.existsSync()) {
|
||||
|
@ -2,8 +2,11 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// @dart = 2.8
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:process/process.dart';
|
||||
|
||||
import 'application_package.dart';
|
||||
@ -23,12 +26,12 @@ import 'protocol_discovery.dart';
|
||||
/// from, containing implementations that are common to all desktop devices.
|
||||
abstract class DesktopDevice extends Device {
|
||||
DesktopDevice(String identifier, {
|
||||
required PlatformType platformType,
|
||||
required bool ephemeral,
|
||||
required Logger logger,
|
||||
required ProcessManager processManager,
|
||||
required FileSystem fileSystem,
|
||||
required OperatingSystemUtils operatingSystemUtils,
|
||||
@required PlatformType platformType,
|
||||
@required bool ephemeral,
|
||||
@required Logger logger,
|
||||
@required ProcessManager processManager,
|
||||
@required FileSystem fileSystem,
|
||||
@required OperatingSystemUtils operatingSystemUtils,
|
||||
}) : _logger = logger,
|
||||
_processManager = processManager,
|
||||
_fileSystem = fileSystem,
|
||||
@ -57,7 +60,7 @@ abstract class DesktopDevice extends Device {
|
||||
@override
|
||||
Future<bool> isAppInstalled(
|
||||
ApplicationPackage app, {
|
||||
String? userIdentifier,
|
||||
String userIdentifier,
|
||||
}) async => true;
|
||||
|
||||
// Since the host and target devices are the same, no work needs to be done
|
||||
@ -70,7 +73,7 @@ abstract class DesktopDevice extends Device {
|
||||
@override
|
||||
Future<bool> installApp(
|
||||
ApplicationPackage app, {
|
||||
String? userIdentifier,
|
||||
String userIdentifier,
|
||||
}) async => true;
|
||||
|
||||
// Since the host and target devices are the same, no work needs to be done
|
||||
@ -78,14 +81,14 @@ abstract class DesktopDevice extends Device {
|
||||
@override
|
||||
Future<bool> uninstallApp(
|
||||
ApplicationPackage app, {
|
||||
String? userIdentifier,
|
||||
String userIdentifier,
|
||||
}) async => true;
|
||||
|
||||
@override
|
||||
Future<bool> get isLocalEmulator async => false;
|
||||
|
||||
@override
|
||||
Future<String?> get emulatorId async => null;
|
||||
Future<String> get emulatorId async => null;
|
||||
|
||||
@override
|
||||
DevicePortForwarder get portForwarder => const NoOpDevicePortForwarder();
|
||||
@ -98,7 +101,7 @@ abstract class DesktopDevice extends Device {
|
||||
|
||||
@override
|
||||
DeviceLogReader getLogReader({
|
||||
ApplicationPackage? app,
|
||||
ApplicationPackage app,
|
||||
bool includePastLogs = false,
|
||||
}) {
|
||||
assert(!includePastLogs, 'Past log reading not supported on desktop.');
|
||||
@ -111,13 +114,13 @@ abstract class DesktopDevice extends Device {
|
||||
@override
|
||||
Future<LaunchResult> startApp(
|
||||
ApplicationPackage package, {
|
||||
String? mainPath,
|
||||
String? route,
|
||||
required DebuggingOptions debuggingOptions,
|
||||
String mainPath,
|
||||
String route,
|
||||
@required DebuggingOptions debuggingOptions,
|
||||
Map<String, dynamic> platformArgs = const <String, dynamic>{},
|
||||
bool prebuiltApplication = false,
|
||||
bool ipv6 = false,
|
||||
String? userIdentifier,
|
||||
String userIdentifier,
|
||||
}) async {
|
||||
if (!prebuiltApplication) {
|
||||
await buildForDevice(
|
||||
@ -128,9 +131,9 @@ abstract class DesktopDevice extends Device {
|
||||
}
|
||||
|
||||
// Ensure that the executable is locatable.
|
||||
final BuildMode buildMode = debuggingOptions.buildInfo.mode;
|
||||
final bool traceStartup = platformArgs['trace-startup'] as bool? ?? false;
|
||||
final String? executable = executablePathForDevice(package, buildMode);
|
||||
final BuildMode buildMode = debuggingOptions?.buildInfo?.mode;
|
||||
final bool traceStartup = platformArgs['trace-startup'] as bool ?? false;
|
||||
final String executable = executablePathForDevice(package, buildMode);
|
||||
if (executable == null) {
|
||||
_logger.printError('Unable to find executable to run');
|
||||
return LaunchResult.failed();
|
||||
@ -139,7 +142,7 @@ abstract class DesktopDevice extends Device {
|
||||
Process process;
|
||||
final List<String> command = <String>[
|
||||
executable,
|
||||
...debuggingOptions.dartEntrypointArgs,
|
||||
...?debuggingOptions?.dartEntrypointArgs,
|
||||
];
|
||||
try {
|
||||
process = await _processManager.start(
|
||||
@ -154,17 +157,17 @@ abstract class DesktopDevice extends Device {
|
||||
unawaited(process.exitCode.then((_) => _runningProcesses.remove(process)));
|
||||
|
||||
_deviceLogReader.initializeProcess(process);
|
||||
if (debuggingOptions.buildInfo.isRelease == true) {
|
||||
if (debuggingOptions?.buildInfo?.isRelease == true) {
|
||||
return LaunchResult.succeeded();
|
||||
}
|
||||
final ProtocolDiscovery observatoryDiscovery = ProtocolDiscovery.observatory(_deviceLogReader,
|
||||
devicePort: debuggingOptions.deviceVmServicePort,
|
||||
hostPort: debuggingOptions.hostVmServicePort,
|
||||
devicePort: debuggingOptions?.deviceVmServicePort,
|
||||
hostPort: debuggingOptions?.hostVmServicePort,
|
||||
ipv6: ipv6,
|
||||
logger: _logger,
|
||||
);
|
||||
try {
|
||||
final Uri? observatoryUri = await observatoryDiscovery.uri;
|
||||
final Uri observatoryUri = await observatoryDiscovery.uri;
|
||||
if (observatoryUri != null) {
|
||||
onAttached(package, buildMode, process);
|
||||
return LaunchResult.succeeded(observatoryUri: observatoryUri);
|
||||
@ -184,7 +187,7 @@ abstract class DesktopDevice extends Device {
|
||||
@override
|
||||
Future<bool> stopApp(
|
||||
ApplicationPackage app, {
|
||||
String? userIdentifier,
|
||||
String userIdentifier,
|
||||
}) async {
|
||||
bool succeeded = true;
|
||||
// Walk a copy of _runningProcesses, since the exit handler removes from the
|
||||
@ -197,19 +200,19 @@ abstract class DesktopDevice extends Device {
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
await portForwarder.dispose();
|
||||
await portForwarder?.dispose();
|
||||
}
|
||||
|
||||
/// Builds the current project for this device, with the given options.
|
||||
Future<void> buildForDevice(
|
||||
ApplicationPackage package, {
|
||||
String? mainPath,
|
||||
String mainPath,
|
||||
BuildInfo buildInfo,
|
||||
});
|
||||
|
||||
/// Returns the path to the executable to run for [package] on this device for
|
||||
/// the given [buildMode].
|
||||
String? executablePathForDevice(ApplicationPackage package, BuildMode buildMode);
|
||||
String executablePathForDevice(ApplicationPackage package, BuildMode buildMode);
|
||||
|
||||
/// Called after a process is attached, allowing any device-specific extra
|
||||
/// steps to be run.
|
||||
@ -222,7 +225,7 @@ abstract class DesktopDevice extends Device {
|
||||
/// The format of the environment variables is:
|
||||
/// * FLUTTER_ENGINE_SWITCHES to the number of switches.
|
||||
/// * FLUTTER_ENGINE_SWITCH_<N> (indexing from 1) to the individual switches.
|
||||
Map<String, String> _computeEnvironment(DebuggingOptions debuggingOptions, bool traceStartup, String? route) {
|
||||
Map<String, String> _computeEnvironment(DebuggingOptions debuggingOptions, bool traceStartup, String route) {
|
||||
int flags = 0;
|
||||
final Map<String, String> environment = <String, String>{};
|
||||
|
||||
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// @dart = 2.8
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
@ -35,10 +37,10 @@ BundleBuilder _defaultBundleBuilder() {
|
||||
/// device is not currently discoverable.
|
||||
class PreviewDevice extends Device {
|
||||
PreviewDevice({
|
||||
required Platform platform,
|
||||
required ProcessManager processManager,
|
||||
required Logger logger,
|
||||
required FileSystem fileSystem,
|
||||
@required Platform platform,
|
||||
@required ProcessManager processManager,
|
||||
@required Logger logger,
|
||||
@required FileSystem fileSystem,
|
||||
@visibleForTesting BundleBuilderFactory builderFactory = _defaultBundleBuilder,
|
||||
}) : _platform = platform,
|
||||
_processManager = processManager,
|
||||
@ -60,18 +62,18 @@ class PreviewDevice extends Device {
|
||||
Future<void> dispose() async { }
|
||||
|
||||
@override
|
||||
Future<String?> get emulatorId async => null;
|
||||
Future<String> get emulatorId async => null;
|
||||
|
||||
final DesktopLogReader _logReader = DesktopLogReader();
|
||||
|
||||
@override
|
||||
FutureOr<DeviceLogReader> getLogReader({covariant ApplicationPackage? app, bool includePastLogs = false}) => _logReader;
|
||||
FutureOr<DeviceLogReader> getLogReader({covariant ApplicationPackage app, bool includePastLogs = false}) => _logReader;
|
||||
|
||||
@override
|
||||
Future<bool> installApp(covariant ApplicationPackage? app, {String? userIdentifier}) async => true;
|
||||
Future<bool> installApp(covariant ApplicationPackage app, {String userIdentifier}) async => true;
|
||||
|
||||
@override
|
||||
Future<bool> isAppInstalled(covariant ApplicationPackage app, {String? userIdentifier}) async => false;
|
||||
Future<bool> isAppInstalled(covariant ApplicationPackage app, {String userIdentifier}) async => false;
|
||||
|
||||
@override
|
||||
Future<bool> isLatestBuildInstalled(covariant ApplicationPackage app) async => false;
|
||||
@ -94,23 +96,24 @@ class PreviewDevice extends Device {
|
||||
@override
|
||||
Future<String> get sdkNameAndVersion async => 'preview';
|
||||
|
||||
Process? _process;
|
||||
Process _process;
|
||||
Directory _assetDirectory;
|
||||
|
||||
@override
|
||||
Future<LaunchResult> startApp(covariant ApplicationPackage package, {
|
||||
String? mainPath,
|
||||
String? route,
|
||||
required DebuggingOptions debuggingOptions,
|
||||
Map<String, dynamic> platformArgs = const <String, dynamic>{},
|
||||
String mainPath,
|
||||
String route,
|
||||
@required DebuggingOptions debuggingOptions,
|
||||
Map<String, dynamic> platformArgs,
|
||||
bool prebuiltApplication = false,
|
||||
bool ipv6 = false,
|
||||
String? userIdentifier,
|
||||
String userIdentifier,
|
||||
}) async {
|
||||
final Directory assetDirectory = _fileSystem.systemTempDirectory
|
||||
_assetDirectory = _fileSystem.systemTempDirectory
|
||||
.createTempSync('flutter_preview.');
|
||||
|
||||
// Build assets and perform initial compilation.
|
||||
Status? status;
|
||||
Status status;
|
||||
try {
|
||||
status = _logger.startProgress('Compiling application for preview...');
|
||||
await _bundleBuilderFactory().build(
|
||||
@ -121,32 +124,32 @@ class PreviewDevice extends Device {
|
||||
);
|
||||
copyDirectory(_fileSystem.directory(
|
||||
getAssetBuildDirectory()),
|
||||
assetDirectory.childDirectory('data').childDirectory('flutter_assets'),
|
||||
_assetDirectory.childDirectory('data').childDirectory('flutter_assets'),
|
||||
);
|
||||
} finally {
|
||||
status?.stop();
|
||||
status.stop();
|
||||
}
|
||||
|
||||
// Merge with precompiled executable.
|
||||
final Directory precompiledDirectory = _fileSystem.directory(_fileSystem.path.join(Cache.flutterRoot!, 'artifacts_temp', 'Debug'));
|
||||
copyDirectory(precompiledDirectory, assetDirectory);
|
||||
final Directory precompiledDirectory = _fileSystem.directory(_fileSystem.path.join(Cache.flutterRoot, 'artifacts_temp', 'Debug'));
|
||||
copyDirectory(precompiledDirectory, _assetDirectory);
|
||||
|
||||
final Process process = await _processManager.start(
|
||||
<String>[
|
||||
assetDirectory.childFile('splash').path,
|
||||
_assetDirectory.childFile('splash').path,
|
||||
],
|
||||
);
|
||||
_process = process;
|
||||
_logReader.initializeProcess(process);
|
||||
|
||||
final ProtocolDiscovery observatoryDiscovery = ProtocolDiscovery.observatory(_logReader,
|
||||
devicePort: debuggingOptions.deviceVmServicePort,
|
||||
hostPort: debuggingOptions.hostVmServicePort,
|
||||
devicePort: debuggingOptions?.deviceVmServicePort,
|
||||
hostPort: debuggingOptions?.hostVmServicePort,
|
||||
ipv6: ipv6,
|
||||
logger: _logger,
|
||||
);
|
||||
try {
|
||||
final Uri? observatoryUri = await observatoryDiscovery.uri;
|
||||
final Uri observatoryUri = await observatoryDiscovery.uri;
|
||||
if (observatoryUri != null) {
|
||||
return LaunchResult.succeeded(observatoryUri: observatoryUri);
|
||||
}
|
||||
@ -163,8 +166,8 @@ class PreviewDevice extends Device {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> stopApp(covariant ApplicationPackage app, {String? userIdentifier}) async {
|
||||
return _process?.kill() == true;
|
||||
Future<bool> stopApp(covariant ApplicationPackage app, {String userIdentifier}) async {
|
||||
return _process?.kill();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -176,7 +179,7 @@ class PreviewDevice extends Device {
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> uninstallApp(covariant ApplicationPackage app, {String? userIdentifier}) async {
|
||||
Future<bool> uninstallApp(covariant ApplicationPackage app, {String userIdentifier}) async {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// @dart = 2.8
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:file/memory.dart';
|
||||
@ -16,6 +18,7 @@ import 'package:flutter_tools/src/device.dart';
|
||||
import 'package:flutter_tools/src/device_port_forwarder.dart';
|
||||
import 'package:flutter_tools/src/project.dart';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:test/fake.dart';
|
||||
|
||||
import '../src/common.dart';
|
||||
@ -47,8 +50,8 @@ void main() {
|
||||
testWithoutContext('Install checks always return true', () async {
|
||||
final FakeDesktopDevice device = setUpDesktopDevice();
|
||||
|
||||
expect(await device.isAppInstalled(FakeApplicationPackage()), true);
|
||||
expect(await device.isLatestBuildInstalled(FakeApplicationPackage()), true);
|
||||
expect(await device.isAppInstalled(null), true);
|
||||
expect(await device.isLatestBuildInstalled(null), true);
|
||||
expect(device.category, Category.desktop);
|
||||
});
|
||||
|
||||
@ -86,7 +89,7 @@ void main() {
|
||||
),
|
||||
]);
|
||||
final FakeDesktopDevice device = setUpDesktopDevice(processManager: processManager, fileSystem: fileSystem);
|
||||
final String? executableName = device.executablePathForDevice(FakeApplicationPackage(), BuildMode.debug);
|
||||
final String executableName = device.executablePathForDevice(null, BuildMode.debug);
|
||||
fileSystem.file(executableName).writeAsStringSync('\n');
|
||||
final FakeApplicationPackage package = FakeApplicationPackage();
|
||||
final LaunchResult result = await device.startApp(
|
||||
@ -245,7 +248,7 @@ void main() {
|
||||
testWithoutContext('createDevFSWriter returns a LocalDevFSWriter', () {
|
||||
final FakeDesktopDevice device = setUpDesktopDevice();
|
||||
|
||||
expect(device.createDevFSWriter(FakeApplicationPackage(), ''), isA<LocalDevFSWriter>());
|
||||
expect(device.createDevFSWriter(null, ''), isA<LocalDevFSWriter>());
|
||||
});
|
||||
|
||||
testWithoutContext('startApp supports dartEntrypointArgs', () async {
|
||||
@ -305,10 +308,10 @@ void main() {
|
||||
}
|
||||
|
||||
FakeDesktopDevice setUpDesktopDevice({
|
||||
FileSystem? fileSystem,
|
||||
Logger? logger,
|
||||
ProcessManager? processManager,
|
||||
OperatingSystemUtils? operatingSystemUtils,
|
||||
FileSystem fileSystem,
|
||||
Logger logger,
|
||||
ProcessManager processManager,
|
||||
OperatingSystemUtils operatingSystemUtils,
|
||||
bool nullExecutablePathForDevice = false,
|
||||
}) {
|
||||
return FakeDesktopDevice(
|
||||
@ -323,11 +326,11 @@ FakeDesktopDevice setUpDesktopDevice({
|
||||
/// A trivial subclass of DesktopDevice for testing the shared functionality.
|
||||
class FakeDesktopDevice extends DesktopDevice {
|
||||
FakeDesktopDevice({
|
||||
required ProcessManager processManager,
|
||||
required Logger logger,
|
||||
required FileSystem fileSystem,
|
||||
required OperatingSystemUtils operatingSystemUtils,
|
||||
this.nullExecutablePathForDevice = false,
|
||||
@required ProcessManager processManager,
|
||||
@required Logger logger,
|
||||
@required FileSystem fileSystem,
|
||||
@required OperatingSystemUtils operatingSystemUtils,
|
||||
this.nullExecutablePathForDevice,
|
||||
}) : super(
|
||||
'dummy',
|
||||
platformType: PlatformType.linux,
|
||||
@ -339,10 +342,10 @@ class FakeDesktopDevice extends DesktopDevice {
|
||||
);
|
||||
|
||||
/// The [mainPath] last passed to [buildForDevice].
|
||||
String? lastBuiltMainPath;
|
||||
String lastBuiltMainPath;
|
||||
|
||||
/// The [buildInfo] last passed to [buildForDevice].
|
||||
BuildInfo? lastBuildInfo;
|
||||
BuildInfo lastBuildInfo;
|
||||
|
||||
final bool nullExecutablePathForDevice;
|
||||
|
||||
@ -361,8 +364,8 @@ class FakeDesktopDevice extends DesktopDevice {
|
||||
@override
|
||||
Future<void> buildForDevice(
|
||||
ApplicationPackage package, {
|
||||
String? mainPath,
|
||||
BuildInfo? buildInfo,
|
||||
String mainPath,
|
||||
BuildInfo buildInfo,
|
||||
}) async {
|
||||
lastBuiltMainPath = mainPath;
|
||||
lastBuildInfo = buildInfo;
|
||||
@ -370,7 +373,7 @@ class FakeDesktopDevice extends DesktopDevice {
|
||||
|
||||
// Dummy implementation that just returns the build mode name.
|
||||
@override
|
||||
String? executablePathForDevice(ApplicationPackage package, BuildMode buildMode) {
|
||||
String executablePathForDevice(ApplicationPackage package, BuildMode buildMode) {
|
||||
if (nullExecutablePathForDevice) {
|
||||
return null;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user