diff --git a/packages/devtools_shared/CHANGELOG.md b/packages/devtools_shared/CHANGELOG.md index eaf9a53f5df..aef5175c04d 100644 --- a/packages/devtools_shared/CHANGELOG.md +++ b/packages/devtools_shared/CHANGELOG.md @@ -4,6 +4,26 @@ Use of this source code is governed by a BSD-style license that can be found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd. --> +# 13.1.0 +* The `LocalFileSystem` extension APIs are deprecated in favor of a new + `FileSystemExtension` extension, which is based on the `FileSystem` class + from the `file` package. In detail: + * `fileSystem` is a new top-level constant which represents the local (real) + file system. + * `LocalFileSystem.devToolsDir()` is replaced by a static getter, + `FileSystemExtension.devToolsDir`. + * `LocalFileSystem.maybeMoveLegacyDevToolsStore()` is replaced by an instance + method, `FileSystemExtension.maybeMoveLegacyDevToolsStore()`. + * `LocalFileSystem.devToolsStoreLocation()` is replaced by a static getter, + `FileSystemExtension.devToolsStoreLocation`. + * `LocalFileSystem.ensureDevToolsDirectory()` will be removed. + * `LocalFileSystem.devToolsFileFromPath()` is replaced by an instance method, + `FileSystemExtension.devToolsFileFromPath()`. + * `LocalFileSystem.devToolsFileAsJson()` is replaced by an instance method, + `FileSystemExtension.devToolsFileAsJson()`. + * `LocalFileSystem.flutterStoreExists()` is replaced by an instance getter, + `FileSystemExtension.flutterStoreExists`. + # 13.0.2 * Validate the `devtoolsOptionsUri` query parameter in the extension enabled state handler so it must be a `file:` URI named `devtools_options.yaml`, diff --git a/packages/devtools_shared/lib/src/server/file_system.dart b/packages/devtools_shared/lib/src/server/file_system.dart index 6a2036f1de9..620227a1aa4 100644 --- a/packages/devtools_shared/lib/src/server/file_system.dart +++ b/packages/devtools_shared/lib/src/server/file_system.dart @@ -3,28 +3,37 @@ // found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd. import 'dart:convert'; -import 'dart:io'; +import 'dart:io' as io show Platform, File, Directory; +import 'package:file/file.dart' as file; +import 'package:file/local.dart' as file; import 'package:path/path.dart' as path; import 'devtools_store.dart'; +/// The real, local file system, which can be avoided in tests. +const file.FileSystem fileSystem = file.LocalFileSystem(); + /// A namespace local file system utlities. extension LocalFileSystem on Never { static String _userHomeDir() { - final envKey = Platform.operatingSystem == 'windows' ? 'APPDATA' : 'HOME'; - return Platform.environment[envKey] ?? '.'; + final envKey = io.Platform.operatingSystem == 'windows' + ? 'APPDATA' + : 'HOME'; + return io.Platform.environment[envKey] ?? '.'; } /// Returns the path to the DevTools storage directory. + @Deprecated("Replaced by 'FileSystemExtension.devToolsDir'") 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. + @Deprecated("Replaced by 'FileSystemExtension.maybeMoveLegacyDevToolsStore'") static void maybeMoveLegacyDevToolsStore() { - final file = File(path.join(_userHomeDir(), DevToolsUsage.storeName)); + final file = io.File(path.join(_userHomeDir(), DevToolsUsage.storeName)); if (file.existsSync()) { ensureDevToolsDirectory(); file.copySync(devToolsStoreLocation()); @@ -32,19 +41,22 @@ extension LocalFileSystem on Never { } } + @Deprecated("Replaced by 'FileSystemExtension.devToolsStoreLocation'") static String devToolsStoreLocation() { return path.join(devToolsDir(), DevToolsUsage.storeName); } /// Creates the ~/.flutter-devtools directory if it does not already exist. + @Deprecated('To be removed') static void ensureDevToolsDirectory() { - Directory(devToolsDir()).createSync(); + io.Directory(devToolsDir()).createSync(); } /// Returns a DevTools file from the given path. /// /// Only files within ~/.flutter-devtools/ can be accessed. - static File? devToolsFileFromPath(String pathFromDevToolsDir) { + @Deprecated("Replaced by 'FileSystemExtension.devToolsFileFromPath'") + static io.File? devToolsFileFromPath(String pathFromDevToolsDir) { if (pathFromDevToolsDir.contains('..') || path.isAbsolute(pathFromDevToolsDir)) { // The passed in path should not be able to walk up the directory tree @@ -57,7 +69,7 @@ extension LocalFileSystem on Never { ensureDevToolsDirectory(); final devToolsDirPath = devToolsDir(); - final file = File(path.join(devToolsDirPath, pathFromDevToolsDir)); + final file = io.File(path.join(devToolsDirPath, pathFromDevToolsDir)); // Defense in depth: ensure the resolved path is actually contained within // the DevTools directory. if (!path.isWithin(devToolsDirPath, file.path)) { @@ -72,6 +84,7 @@ extension LocalFileSystem on Never { /// Returns a DevTools file from the given path as encoded json. /// /// Only files within ~/.flutter-devtools/ can be accessed. + @Deprecated("Replaced by 'FileSystemExtension.devToolsFileAsJson'") static String? devToolsFileAsJson(String pathFromDevToolsDir) { final file = devToolsFileFromPath(pathFromDevToolsDir); if (file == null) return null; @@ -86,8 +99,90 @@ extension LocalFileSystem on Never { } /// Whether the flutter store file exists. + @Deprecated("Replaced by 'FileSystemExtension.flutterStoreExists'") static bool flutterStoreExists() { - final flutterStore = File(path.join(_userHomeDir(), '.flutter')); + final flutterStore = io.File(path.join(_userHomeDir(), '.flutter')); + return flutterStore.existsSync(); + } +} + +extension FileSystemExtension on file.FileSystem { + static String get _userHomeDir { + final envKey = io.Platform.operatingSystem == 'windows' + ? 'APPDATA' + : 'HOME'; + return io.Platform.environment[envKey] ?? '.'; + } + + /// The path to the DevTools storage directory. + static String get 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. + void maybeMoveLegacyDevToolsStore() { + final storeFile = this.file( + path.join(_userHomeDir, DevToolsUsage.storeName), + ); + if (storeFile.existsSync()) { + _ensureDevToolsDirectory(); + storeFile.copySync(devToolsStoreLocation); + storeFile.deleteSync(); + } + } + + static String get devToolsStoreLocation { + return path.join(devToolsDir, DevToolsUsage.storeName); + } + + /// Creates the `~/.flutter-devtools` directory if it does not already exist. + void _ensureDevToolsDirectory() { + directory(devToolsDir).createSync(); + } + + /// Returns a DevTools file from the given path. + /// + /// Only files within ~/.flutter-devtools/ can be accessed. + file.File? devToolsFileFromPath(String relativePath) { + if (relativePath.contains('..') || path.isAbsolute(relativePath)) { + // The passed in path should not be able to walk up the directory tree + // outside of the `~/.flutter-devtools/` directory. It must also not be an + // absolute path: `path.join()` discards the base directory when its + // second argument is absolute, which would otherwise allow reading an + // arbitrary file on disk (e.g. an absolute path to a credentials `.json` + // file). + return null; + } + + _ensureDevToolsDirectory(); + final targetFile = this.file(path.join(devToolsDir, relativePath)); + // Defense in depth: ensure the resolved path is actually contained within + // the DevTools directory. + if (!path.isWithin(devToolsDir, targetFile.path)) return null; + if (!targetFile.existsSync()) return null; + return targetFile; + } + + /// Returns a DevTools file from the given path as encoded JSON. + /// + /// Only files within `~/.flutter-devtools/` can be accessed. + String? devToolsFileAsJson(String relativePath) { + final targetFile = devToolsFileFromPath(relativePath); + if (targetFile == null) return null; + + final fileName = path.basename(targetFile.path); + if (!fileName.endsWith('.json')) return null; + + final content = targetFile.readAsStringSync(); + final json = jsonDecode(content) as Map; + json['lastModifiedTime'] = targetFile.lastModifiedSync().toString(); + return jsonEncode(json); + } + + /// Whether the flutter store file exists. + bool get flutterStoreExists { + final flutterStore = this.file(path.join(_userHomeDir, '.flutter')); return flutterStore.existsSync(); } } @@ -96,14 +191,15 @@ class IOPersistentProperties { IOPersistentProperties(this.name, {String? documentDirPath}) { final fileName = name.replaceAll(' ', '_'); documentDirPath ??= LocalFileSystem._userHomeDir(); - _file = File(path.join(documentDirPath, fileName)); + _file = io.File(path.join(documentDirPath, fileName)); if (!_file.existsSync()) { _file.createSync(recursive: true); } syncSettings(); } - IOPersistentProperties.fromFile(File file) : name = path.basename(file.path) { + IOPersistentProperties.fromFile(io.File file) + : name = path.basename(file.path) { _file = file; if (!_file.existsSync()) { _file.createSync(recursive: true); @@ -113,7 +209,7 @@ class IOPersistentProperties { final String name; - late File _file; + late io.File _file; late Map _map; diff --git a/packages/devtools_shared/pubspec.yaml b/packages/devtools_shared/pubspec.yaml index 332adaa9421..16cd6c8a5cc 100644 --- a/packages/devtools_shared/pubspec.yaml +++ b/packages/devtools_shared/pubspec.yaml @@ -4,7 +4,7 @@ name: devtools_shared description: Package of shared Dart structures between devtools_app, dds, and other tools. -version: 13.0.2 +version: 13.1.0 repository: https://github.com/flutter/devtools/tree/master/packages/devtools_shared @@ -18,6 +18,7 @@ dependencies: collection: ^1.15.0 dtd: ^4.0.0 extension_discovery: ^2.1.0 + file: ^7.0.0 meta: ^1.9.1 path: ^1.8.0 shelf: ^1.1.0 diff --git a/pubspec.lock b/pubspec.lock index cc8b58e6e9b..1c67b639f11 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -555,10 +555,10 @@ packages: dependency: transitive description: name: meta - sha256: c82594181e3312f3d0695fc95aaaf7758d75b8d4ae2bbecf223b9fd5109a059d + sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9" url: "https://pub.dev" source: hosted - version: "1.18.3" + version: "1.19.0" mime: dependency: transitive description: