33// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
44
55import 'dart:convert' ;
6- import 'dart:io' ;
6+ import 'dart:io' as io show Platform, File ;
77
8+ import 'package:file/file.dart' ;
9+ import 'package:file/local.dart' ;
810import 'package:path/path.dart' as path;
911
1012import 'devtools_store.dart' ;
1113
12- /// A namespace local file system utlities.
13- extension LocalFileSystem on Never {
14- static String _userHomeDir () {
15- final envKey = Platform .operatingSystem == 'windows' ? 'APPDATA' : 'HOME' ;
16- return Platform .environment[envKey] ?? '.' ;
14+ /// The real, local file system, which can be avoided in tests.
15+ const FileSystem fileSystem = LocalFileSystem ();
16+
17+ extension FileSystemExtension on FileSystem {
18+ static String get _userHomeDir {
19+ final envKey = io.Platform .operatingSystem == 'windows'
20+ ? 'APPDATA'
21+ : 'HOME' ;
22+ return io.Platform .environment[envKey] ?? '.' ;
1723 }
1824
19- /// Returns the path to the DevTools storage directory.
20- static String devToolsDir () {
21- return path.join (_userHomeDir () , '.flutter-devtools' );
25+ /// The path to the DevTools storage directory.
26+ static String get devToolsDir {
27+ return path.join (_userHomeDir, '.flutter-devtools' );
2228 }
2329
24- /// Moves the .devtools file to ~/.flutter-devtools/.devtools if the .devtools
25- /// file exists in the user's home directory.
26- static void maybeMoveLegacyDevToolsStore () {
27- final file = File (path.join (_userHomeDir () , DevToolsUsage .storeName));
28- if (file .existsSync ()) {
29- ensureDevToolsDirectory ();
30- file .copySync (devToolsStoreLocation () );
31- file .deleteSync ();
30+ /// Moves the ` .devtools` file to ` ~/.flutter-devtools/.devtools` if the
31+ /// `.devtools` file exists in the user's home directory.
32+ void maybeMoveLegacyDevToolsStore () {
33+ final storeFile = file (path.join (_userHomeDir, DevToolsUsage .storeName));
34+ if (storeFile .existsSync ()) {
35+ _ensureDevToolsDirectory ();
36+ storeFile .copySync (devToolsStoreLocation);
37+ storeFile .deleteSync ();
3238 }
3339 }
3440
35- static String devToolsStoreLocation () {
36- return path.join (devToolsDir () , DevToolsUsage .storeName);
41+ static String get devToolsStoreLocation {
42+ return path.join (devToolsDir, DevToolsUsage .storeName);
3743 }
3844
39- /// Creates the ~/.flutter-devtools directory if it does not already exist.
40- static void ensureDevToolsDirectory () {
41- Directory (devToolsDir () ).createSync ();
45+ /// Creates the ` ~/.flutter-devtools` directory if it does not already exist.
46+ void _ensureDevToolsDirectory () {
47+ directory (devToolsDir).createSync ();
4248 }
4349
4450 /// Returns a DevTools file from the given path.
4551 ///
4652 /// Only files within ~/.flutter-devtools/ can be accessed.
47- static File ? devToolsFileFromPath (String pathFromDevToolsDir) {
48- if (pathFromDevToolsDir.contains ('..' ) ||
49- path.isAbsolute (pathFromDevToolsDir)) {
53+ File ? devToolsFileFromPath (String relativePath) {
54+ if (relativePath.contains ('..' ) || path.isAbsolute (relativePath)) {
5055 // The passed in path should not be able to walk up the directory tree
51- // outside of the ~/.flutter-devtools/ directory. It must also not be an
52- // absolute path: path.join() discards the base directory when its second
53- // argument is absolute, which would otherwise allow reading an arbitrary
54- // file on disk (e.g. an absolute path to a credentials .json file).
56+ // outside of the `~/.flutter-devtools/` directory. It must also not be an
57+ // absolute path: `path.join()` discards the base directory when its
58+ // second argument is absolute, which would otherwise allow reading an
59+ // arbitrary file on disk (e.g. an absolute path to a credentials `.json`
60+ // file).
5561 return null ;
5662 }
5763
58- ensureDevToolsDirectory ();
59- final devToolsDirPath = devToolsDir ();
60- final file = File (path.join (devToolsDirPath, pathFromDevToolsDir));
64+ _ensureDevToolsDirectory ();
65+ final targetFile = file (path.join (devToolsDir, relativePath));
6166 // Defense in depth: ensure the resolved path is actually contained within
6267 // the DevTools directory.
63- if (! path.isWithin (devToolsDirPath, file.path)) {
64- return null ;
65- }
66- if (! file.existsSync ()) {
67- return null ;
68- }
69- return file;
68+ if (! path.isWithin (devToolsDir, targetFile.path)) return null ;
69+ if (! targetFile.existsSync ()) return null ;
70+ return targetFile;
7071 }
7172
72- /// Returns a DevTools file from the given path as encoded json .
73+ /// Returns a DevTools file from the given path as encoded JSON .
7374 ///
74- /// Only files within ~/.flutter-devtools/ can be accessed.
75- static String ? devToolsFileAsJson (String pathFromDevToolsDir ) {
76- final file = devToolsFileFromPath (pathFromDevToolsDir );
77- if (file == null ) return null ;
75+ /// Only files within ` ~/.flutter-devtools/` can be accessed.
76+ String ? devToolsFileAsJson (String relativePath ) {
77+ final targetFile = devToolsFileFromPath (relativePath );
78+ if (targetFile == null ) return null ;
7879
79- final fileName = path.basename (file .path);
80+ final fileName = path.basename (targetFile .path);
8081 if (! fileName.endsWith ('.json' )) return null ;
8182
82- final content = file .readAsStringSync ();
83+ final content = targetFile .readAsStringSync ();
8384 final json = jsonDecode (content) as Map ;
84- json['lastModifiedTime' ] = file .lastModifiedSync ().toString ();
85+ json['lastModifiedTime' ] = targetFile .lastModifiedSync ().toString ();
8586 return jsonEncode (json);
8687 }
8788
8889 /// Whether the flutter store file exists.
89- static bool flutterStoreExists () {
90- final flutterStore = File (path.join (_userHomeDir () , '.flutter' ));
90+ bool get flutterStoreExists {
91+ final flutterStore = file (path.join (_userHomeDir, '.flutter' ));
9192 return flutterStore.existsSync ();
9293 }
9394}
9495
9596class IOPersistentProperties {
96- IOPersistentProperties (this .name, {String ? documentDirPath}) {
97+ IOPersistentProperties (
98+ this .name, {
99+ String ? documentDirPath,
100+ FileSystem fs = fileSystem,
101+ }) : _fs = fs {
97102 final fileName = name.replaceAll (' ' , '_' );
98- documentDirPath ?? = LocalFileSystem ._userHomeDir () ;
99- _file = File (path.join (documentDirPath, fileName));
103+ documentDirPath ?? = FileSystemExtension ._userHomeDir;
104+ _file = _fs. file (path.join (documentDirPath, fileName));
100105 if (! _file.existsSync ()) {
101106 _file.createSync (recursive: true );
102107 }
103108 syncSettings ();
104109 }
105110
106- IOPersistentProperties .fromFile (File file) : name = path.basename (file.path) {
107- _file = file;
111+ // TODO(srawlins): This is unused in any devtools code. Even in tests. Either
112+ // test it, if it is needed by users, or remove it.
113+ IOPersistentProperties .fromFile (io.File file)
114+ : name = path.basename (file.path),
115+ _fs = file is File ? file.fileSystem : fileSystem {
116+ _file = file is File ? file : _fs.file (file.path);
108117 if (! _file.existsSync ()) {
109118 _file.createSync (recursive: true );
110119 }
@@ -113,7 +122,9 @@ class IOPersistentProperties {
113122
114123 final String name;
115124
116- late File _file;
125+ final FileSystem _fs;
126+
127+ late final File _file;
117128
118129 late Map <String , Object ?> _map;
119130
0 commit comments