diff --git a/pkg/dds/analysis_options.yaml b/pkg/dds/analysis_options.yaml index 413f41273bb..1f8365834e1 100644 --- a/pkg/dds/analysis_options.yaml +++ b/pkg/dds/analysis_options.yaml @@ -2,5 +2,6 @@ include: package:pedantic/analysis_options.1.8.0.yaml linter: rules: + - depend_on_referenced_packages - directives_ordering - prefer_generic_function_type_aliases diff --git a/pkg/dds/lib/src/devtools/devtools_client.dart b/pkg/dds/lib/src/devtools/devtools_client.dart index 8bc35647878..e5ec54e6961 100644 --- a/pkg/dds/lib/src/devtools/devtools_client.dart +++ b/pkg/dds/lib/src/devtools/devtools_client.dart @@ -4,12 +4,11 @@ import 'dart:async'; +import 'package:devtools_shared/devtools_server.dart'; import 'package:json_rpc_2/src/server.dart' as json_rpc; import 'package:sse/src/server/sse_handler.dart'; import 'package:stream_channel/stream_channel.dart'; -import 'server_api.dart'; - class LoggingMiddlewareSink implements StreamSink { LoggingMiddlewareSink(this.sink); diff --git a/pkg/dds/lib/src/devtools/devtools_handler.dart b/pkg/dds/lib/src/devtools/devtools_handler.dart index 87c4fa65221..3b55bfbf91d 100644 --- a/pkg/dds/lib/src/devtools/devtools_handler.dart +++ b/pkg/dds/lib/src/devtools/devtools_handler.dart @@ -5,13 +5,13 @@ import 'dart:async'; import 'package:dds/src/constants.dart'; +import 'package:devtools_shared/devtools_server.dart'; import 'package:shelf/shelf.dart'; import 'package:shelf_static/shelf_static.dart'; import 'package:sse/server/sse_handler.dart'; import '../dds_impl.dart'; import 'devtools_client.dart'; -import 'server_api.dart'; /// Returns a [Handler] which handles serving DevTools and the DevTools server /// API under $DDS_URI/devtools/. diff --git a/pkg/dds/lib/src/devtools/file_system.dart b/pkg/dds/lib/src/devtools/file_system.dart deleted file mode 100644 index d5d6c6a51ae..00000000000 --- a/pkg/dds/lib/src/devtools/file_system.dart +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2021 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. - -// TODO(bkonyi): remove once package:devtools_server_api is available -// See https://github.com/flutter/devtools/issues/2958. - -import 'dart:convert'; -import 'dart:io'; - -import 'package:path/path.dart' as path; - -import 'usage.dart'; - -class LocalFileSystem { - static String _userHomeDir() { - final String envKey = - Platform.operatingSystem == 'windows' ? 'APPDATA' : 'HOME'; - final String? value = Platform.environment[envKey]; - return value == null ? '.' : value; - } - - /// Returns the path to the DevTools storage directory. - static String devToolsDir() { - return path.join(_userHomeDir(), '.flutter-devtools'); - } - - /// Moves the .devtools file to ~/.flutter-devtools/.devtools if the .devtools file - /// exists in the user's home directory. - static void maybeMoveLegacyDevToolsStore() { - final file = File(path.join(_userHomeDir(), DevToolsUsage.storeName)); - if (file.existsSync()) { - ensureDevToolsDirectory(); - file.copySync(path.join(devToolsDir(), DevToolsUsage.storeName)); - file.deleteSync(); - } - } - - /// Creates the ~/.flutter-devtools directory if it does not already exist. - static void ensureDevToolsDirectory() { - Directory('${LocalFileSystem.devToolsDir()}').createSync(); - } - - /// Returns a DevTools file from the given path. - /// - /// Only files within ~/.flutter-devtools/ can be accessed. - static File? devToolsFileFromPath(String pathFromDevToolsDir) { - if (pathFromDevToolsDir.contains('..')) { - // The passed in path should not be able to walk up the directory tree - // outside of the ~/.flutter-devtools/ directory. - return null; - } - ensureDevToolsDirectory(); - final file = File(path.join(devToolsDir(), pathFromDevToolsDir)); - if (!file.existsSync()) { - return null; - } - return file; - } - - /// Returns a DevTools file from the given path as encoded json. - /// - /// Only files within ~/.flutter-devtools/ can be accessed. - static String? devToolsFileAsJson(String pathFromDevToolsDir) { - final file = devToolsFileFromPath(pathFromDevToolsDir); - if (file == null) return null; - - final fileName = path.basename(file.path); - if (!fileName.endsWith('.json')) return null; - - final content = file.readAsStringSync(); - final json = jsonDecode(content); - json['lastModifiedTime'] = file.lastModifiedSync().toString(); - return jsonEncode(json); - } - - /// Whether the flutter store file exists. - static bool flutterStoreExists() { - final flutterStore = File('${_userHomeDir()}/.flutter'); - return flutterStore.existsSync(); - } -} diff --git a/pkg/dds/lib/src/devtools/server_api.dart b/pkg/dds/lib/src/devtools/server_api.dart deleted file mode 100644 index 5f21a03a4da..00000000000 --- a/pkg/dds/lib/src/devtools/server_api.dart +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright 2021 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. - -// TODO(bkonyi): remove once package:devtools_server_api is available -// See https://github.com/flutter/devtools/issues/2958. - -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; - -import 'package:devtools_shared/devtools_shared.dart'; -import 'package:shelf/shelf.dart' as shelf; - -import 'file_system.dart'; -import 'usage.dart'; - -/// The DevTools server API. -/// -/// This defines endpoints that serve all requests that come in over api/. -class ServerApi { - static const errorNoActiveSurvey = 'ERROR: setActiveSurvey not called.'; - - /// Determines whether or not [request] is an API call. - static bool canHandle(shelf.Request request) { - return request.url.path.startsWith(apiPrefix); - } - - /// Handles all requests. - /// - /// To override an API call, pass in a subclass of [ServerApi]. - static FutureOr handle( - shelf.Request request, [ - ServerApi? api, - ]) { - api ??= ServerApi(); - switch (request.url.path) { - // ----- Flutter Tool GA store. ----- - case apiGetFlutterGAEnabled: - // Is Analytics collection enabled? - return api.getCompleted( - request, - json.encode(FlutterUsage.doesStoreExist ? _usage!.enabled : null), - ); - case apiGetFlutterGAClientId: - // Flutter Tool GA clientId - ONLY get Flutter's clientId if enabled is - // true. - return (FlutterUsage.doesStoreExist) - ? api.getCompleted( - request, - json.encode(_usage!.enabled ? _usage!.clientId : null), - ) - : api.getCompleted( - request, - json.encode(null), - ); - - // ----- DevTools GA store. ----- - - case apiResetDevTools: - _devToolsUsage.reset(); - return api.getCompleted(request, json.encode(true)); - case apiGetDevToolsFirstRun: - // Has DevTools been run first time? To bring up welcome screen. - return api.getCompleted( - request, - json.encode(_devToolsUsage.isFirstRun), - ); - case apiGetDevToolsEnabled: - // Is DevTools Analytics collection enabled? - return api.getCompleted(request, json.encode(_devToolsUsage.enabled)); - case apiSetDevToolsEnabled: - // Enable or disable DevTools analytics collection. - final queryParams = request.requestedUri.queryParameters; - if (queryParams.containsKey(devToolsEnabledPropertyName)) { - _devToolsUsage.enabled = - json.decode(queryParams[devToolsEnabledPropertyName]!); - } - return api.setCompleted(request, json.encode(_devToolsUsage.enabled)); - - // ----- DevTools survey store. ----- - - case apiSetActiveSurvey: - // Assume failure. - bool result = false; - - // Set the active survey used to store subsequent apiGetSurveyActionTaken, - // apiSetSurveyActionTaken, apiGetSurveyShownCount, and - // apiIncrementSurveyShownCount calls. - final queryParams = request.requestedUri.queryParameters; - if (queryParams.keys.length == 1 && - queryParams.containsKey(activeSurveyName)) { - final String theSurveyName = queryParams[activeSurveyName]!; - - // Set the current activeSurvey. - _devToolsUsage.activeSurvey = theSurveyName; - result = true; - } - - return api.getCompleted(request, json.encode(result)); - case apiGetSurveyActionTaken: - // Request setActiveSurvey has not been requested. - if (_devToolsUsage.activeSurvey == null) { - return api.badRequest('$errorNoActiveSurvey ' - '- $apiGetSurveyActionTaken'); - } - // SurveyActionTaken has the survey been acted upon (taken or dismissed) - return api.getCompleted( - request, - json.encode(_devToolsUsage.surveyActionTaken), - ); - // TODO(terry): remove the query param logic for this request. - // setSurveyActionTaken should only be called with the value of true, so - // we can remove the extra complexity. - case apiSetSurveyActionTaken: - // Request setActiveSurvey has not been requested. - if (_devToolsUsage.activeSurvey == null) { - return api.badRequest('$errorNoActiveSurvey ' - '- $apiSetSurveyActionTaken'); - } - // Set the SurveyActionTaken. - // Has the survey been taken or dismissed.. - final queryParams = request.requestedUri.queryParameters; - if (queryParams.containsKey(surveyActionTakenPropertyName)) { - _devToolsUsage.surveyActionTaken = - json.decode(queryParams[surveyActionTakenPropertyName]!); - } - return api.setCompleted( - request, - json.encode(_devToolsUsage.surveyActionTaken), - ); - case apiGetSurveyShownCount: - // Request setActiveSurvey has not been requested. - if (_devToolsUsage.activeSurvey == null) { - return api.badRequest('$errorNoActiveSurvey ' - '- $apiGetSurveyShownCount'); - } - // SurveyShownCount how many times have we asked to take survey. - return api.getCompleted( - request, - json.encode(_devToolsUsage.surveyShownCount), - ); - case apiIncrementSurveyShownCount: - // Request setActiveSurvey has not been requested. - if (_devToolsUsage.activeSurvey == null) { - return api.badRequest('$errorNoActiveSurvey ' - '- $apiIncrementSurveyShownCount'); - } - // Increment the SurveyShownCount, we've asked about the survey. - _devToolsUsage.incrementSurveyShownCount(); - return api.getCompleted( - request, - json.encode(_devToolsUsage.surveyShownCount), - ); - case apiGetBaseAppSizeFile: - final queryParams = request.requestedUri.queryParameters; - if (queryParams.containsKey(baseAppSizeFilePropertyName)) { - final filePath = queryParams[baseAppSizeFilePropertyName]!; - final fileJson = LocalFileSystem.devToolsFileAsJson(filePath); - if (fileJson == null) { - return api.badRequest('No JSON file available at $filePath.'); - } - return api.getCompleted(request, fileJson); - } - return api.badRequest('Request for base app size file does not ' - 'contain a query parameter with the expected key: ' - '$baseAppSizeFilePropertyName'); - case apiGetTestAppSizeFile: - final queryParams = request.requestedUri.queryParameters; - if (queryParams.containsKey(testAppSizeFilePropertyName)) { - final filePath = queryParams[testAppSizeFilePropertyName]!; - final fileJson = LocalFileSystem.devToolsFileAsJson(filePath); - if (fileJson == null) { - return api.badRequest('No JSON file available at $filePath.'); - } - return api.getCompleted(request, fileJson); - } - return api.badRequest('Request for test app size file does not ' - 'contain a query parameter with the expected key: ' - '$testAppSizeFilePropertyName'); - default: - return api.notImplemented(request); - } - } - - // Accessing Flutter usage file e.g., ~/.flutter. - // NOTE: Only access the file if it exists otherwise Flutter Tool hasn't yet - // been run. - static final FlutterUsage? _usage = - FlutterUsage.doesStoreExist ? FlutterUsage() : null; - - // Accessing DevTools usage file e.g., ~/.devtools - static final DevToolsUsage _devToolsUsage = DevToolsUsage(); - - static DevToolsUsage get devToolsPreferences => _devToolsUsage; - - /// Logs a page view in the DevTools server. - /// - /// In the open-source version of DevTools, Google Analytics handles this - /// without any need to involve the server. - FutureOr logScreenView(shelf.Request request) => - notImplemented(request); - - /// Return the value of the property. - FutureOr getCompleted(shelf.Request request, String value) => - shelf.Response.ok('$value'); - - /// Return the value of the property after the property value has been set. - FutureOr setCompleted(shelf.Request request, String value) => - shelf.Response.ok('$value'); - - /// A [shelf.Response] for API calls that encountered a request problem e.g., - /// setActiveSurvey not called. - /// - /// This is a 400 Bad Request response. - FutureOr badRequest([String? logError]) { - if (logError != null) print(logError); - return shelf.Response(HttpStatus.badRequest); - } - - /// A [shelf.Response] for API calls that have not been implemented in this - /// server. - /// - /// This is a no-op 204 No Content response because returning 404 Not Found - /// creates unnecessary noise in the console. - FutureOr notImplemented(shelf.Request request) => - shelf.Response(HttpStatus.noContent); -} diff --git a/pkg/dds/lib/src/devtools/usage.dart b/pkg/dds/lib/src/devtools/usage.dart deleted file mode 100644 index 8e61b900d0a..00000000000 --- a/pkg/dds/lib/src/devtools/usage.dart +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright 2021 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. - -// TODO(bkonyi): remove once package:devtools_server_api is available -// See https://github.com/flutter/devtools/issues/2958. - -import 'dart:convert'; -import 'dart:io'; - -import 'package:path/path.dart' as path; -import 'package:usage/usage_io.dart'; - -import 'file_system.dart'; - -/// Access the file '~/.flutter'. -class FlutterUsage { - /// Create a new Usage instance; [versionOverride] and [configDirOverride] are - /// used for testing. - FlutterUsage({ - String settingsName = 'flutter', - String? versionOverride, - String? configDirOverride, - }) { - _analytics = AnalyticsIO('', settingsName, ''); - } - - late Analytics _analytics; - - /// Does the .flutter store exist? - static bool get doesStoreExist { - return LocalFileSystem.flutterStoreExists(); - } - - bool get isFirstRun => _analytics.firstRun; - - bool get enabled => _analytics.enabled; - - set enabled(bool value) => _analytics.enabled = value; - - String get clientId => _analytics.clientId; -} - -// Access the DevTools on disk store (~/.devtools/.devtools). -class DevToolsUsage { - /// Create a new Usage instance; [versionOverride] and [configDirOverride] are - /// used for testing. - DevToolsUsage({ - String? versionOverride, - String? configDirOverride, - }) { - LocalFileSystem.maybeMoveLegacyDevToolsStore(); - properties = IOPersistentProperties( - storeName, - documentDirPath: LocalFileSystem.devToolsDir(), - ); - } - - static const storeName = '.devtools'; - - /// The activeSurvey is the property name of a top-level property - /// existing or created in the file ~/.devtools - /// If the property doesn't exist it is created with default survey values: - /// - /// properties[activeSurvey]['surveyActionTaken'] = false; - /// properties[activeSurvey]['surveyShownCount'] = 0; - /// - /// It is a requirement that the API apiSetActiveSurvey must be called before - /// calling any survey method on DevToolsUsage (addSurvey, rewriteActiveSurvey, - /// surveyShownCount, incrementSurveyShownCount, or surveyActionTaken). - String? _activeSurvey; - - late IOPersistentProperties properties; - - static const _surveyActionTaken = 'surveyActionTaken'; - static const _surveyShownCount = 'surveyShownCount'; - - void reset() { - properties.remove('firstRun'); - properties['enabled'] = false; - } - - bool get isFirstRun { - properties['firstRun'] = properties['firstRun'] == null; - return properties['firstRun']; - } - - bool get enabled { - if (properties['enabled'] == null) { - properties['enabled'] = false; - } - - return properties['enabled']; - } - - set enabled(bool? value) { - properties['enabled'] = value; - return properties['enabled']; - } - - bool surveyNameExists(String? surveyName) => properties[surveyName] != null; - - void _addSurvey(String? surveyName) { - assert(activeSurvey == surveyName); - rewriteActiveSurvey(false, 0); - } - - String? get activeSurvey => _activeSurvey; - - set activeSurvey(String? surveyName) { - _activeSurvey = surveyName; - - if (!surveyNameExists(activeSurvey)) { - // Create the survey if property is non-existent in ~/.devtools - _addSurvey(activeSurvey); - } - } - - /// Need to rewrite the entire survey structure for property to be persisted. - void rewriteActiveSurvey(bool? actionTaken, int? shownCount) { - properties[activeSurvey] = { - _surveyActionTaken: actionTaken, - _surveyShownCount: shownCount, - }; - } - - int? get surveyShownCount { - final prop = properties[activeSurvey]; - if (prop[_surveyShownCount] == null) { - rewriteActiveSurvey(prop[_surveyActionTaken], 0); - } - return properties[activeSurvey][_surveyShownCount]; - } - - void incrementSurveyShownCount() { - surveyShownCount; // Ensure surveyShownCount has been initialized. - final prop = properties[activeSurvey]; - rewriteActiveSurvey(prop[_surveyActionTaken], prop[_surveyShownCount] + 1); - } - - bool get surveyActionTaken { - return properties[activeSurvey][_surveyActionTaken] == true; - } - - set surveyActionTaken(bool? value) { - final prop = properties[activeSurvey]; - rewriteActiveSurvey(value, prop[_surveyShownCount]); - } -} - -abstract class PersistentProperties { - PersistentProperties(this.name); - - final String name; - - dynamic operator [](String key); - - void operator []=(String key, dynamic value); - - /// Re-read settings from the backing store. - /// - /// May be a no-op on some platforms. - void syncSettings(); -} - -const JsonEncoder _jsonEncoder = JsonEncoder.withIndent(' '); - -class IOPersistentProperties extends PersistentProperties { - IOPersistentProperties( - String name, { - String? documentDirPath, - }) : super(name) { - final String fileName = name.replaceAll(' ', '_'); - documentDirPath ??= LocalFileSystem.devToolsDir(); - _file = File(path.join(documentDirPath, fileName)); - if (!_file.existsSync()) { - _file.createSync(recursive: true); - } - syncSettings(); - } - - IOPersistentProperties.fromFile(File file) : super(path.basename(file.path)) { - _file = file; - if (!_file.existsSync()) { - _file.createSync(recursive: true); - } - syncSettings(); - } - - late File _file; - - Map? _map; - - @override - dynamic operator [](String? key) => _map![key]; - - @override - void operator []=(String? key, dynamic value) { - if (value == null && !_map!.containsKey(key)) return; - if (_map![key] == value) return; - - if (value == null) { - _map!.remove(key); - } else { - _map![key] = value; - } - - try { - _file.writeAsStringSync(_jsonEncoder.convert(_map) + '\n'); - } catch (_) {} - } - - @override - void syncSettings() { - try { - String contents = _file.readAsStringSync(); - if (contents.isEmpty) contents = '{}'; - _map = jsonDecode(contents); - } catch (_) { - _map = {}; - } - } - - void remove(String propertyName) { - _map!.remove(propertyName); - } -} diff --git a/pkg/dds/pubspec.yaml b/pkg/dds/pubspec.yaml index 2bf3136eea4..62298b1ae7a 100644 --- a/pkg/dds/pubspec.yaml +++ b/pkg/dds/pubspec.yaml @@ -24,7 +24,6 @@ dependencies: shelf_web_socket: ^1.0.0 sse: ^4.0.0 stream_channel: ^2.0.0 - usage: ^4.0.0 vm_service: ^7.0.0 web_socket_channel: ^2.0.0 diff --git a/third_party/devtools/update.sh b/third_party/devtools/update.sh index ae3aa496f60..90533c53df7 100755 --- a/third_party/devtools/update.sh +++ b/third_party/devtools/update.sh @@ -38,4 +38,3 @@ cipd create \ -install-mode copy \ -preserve-writable \ -tag git_revision:$1 -