Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix a regression with accessing the Flutter store file. #7896

Merged
merged 2 commits into from
Jun 7, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 8 additions & 16 deletions packages/devtools_app/lib/src/shared/server/_analytics_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,22 +57,17 @@ Future<void> setAnalyticsEnabled([bool value = true]) async {
// '/api/devToolsEnabled' returns the value (identical VM service) and
// '/api/devToolsEnabled?value=true' sets the value.

/// Request Flutter tool stored property value enabled (GA enabled) stored in
/// the file '~\.flutter'.
/// Whether GA is enabled in the Flutter store file ~\.flutter.
///
/// Return bool.
/// Return value of false implies either GA is disabled or the Flutter Tool has
/// never been run (null returned from the server).
/// A return value of false implies either GA is disabled or the Flutter Tool
/// has never been run.
Future<bool> _isFlutterGAEnabled() async {
bool enabled = false;

if (isDevToolsServerAvailable) {
final resp = await request(apiGetFlutterGAEnabled);
if (resp?.statusOk ?? false) {
// A return value of 'null' implies Flutter tool has never been run so
// return false for Flutter GA enabled.
final responseValue = json.decode(resp!.body);
enabled = responseValue ?? false;
enabled = json.decode(resp!.body) as bool;
} else {
logWarning(resp, apiGetFlutterGAEnabled);
}
Expand All @@ -81,12 +76,12 @@ Future<bool> _isFlutterGAEnabled() async {
return enabled;
}

/// Request Flutter tool stored property value clientID (GA enabled) stored in
/// the file '~\.flutter'.
/// Requests the Flutter client id from the Flutter store file ~\.flutter.
///
/// Return as a String, empty string implies Flutter Tool has never been run.
/// If an empty String is returned, this means that Flutter Tool has never been
/// run.
Future<String> flutterGAClientID() async {
// Default empty string, Flutter tool never run.
// Default empty string, Flutter tool never ran.
String clientId = '';

if (isDevToolsServerAvailable) {
Expand All @@ -97,9 +92,6 @@ Future<String> flutterGAClientID() async {
if (resp?.statusOk ?? false) {
clientId = json.decode(resp!.body);
if (clientId.isEmpty) {
// Requested value of 'null' (Flutter tool never ran). Server request
// apiGetFlutterGAClientId should not happen because the
// isFlutterGAEnabled test should have been false.
_log.warning('$apiGetFlutterGAClientId is empty');
}
} else {
Expand Down
1 change: 1 addition & 0 deletions packages/devtools_shared/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Added helper `deserialize` and `deserializeNullable`
* Extended serialization for `HeapSample` and `ExtensionEvents`
* Added mixin `Serializable`
* Fix a regression with accessing the Flutter store file.

# 10.0.0-dev.2
* Support detecting package roots for nested Dart projects in the
Expand Down
2 changes: 1 addition & 1 deletion packages/devtools_shared/lib/devtools_server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

export 'src/server/devtools_store.dart';
export 'src/server/file_system.dart';
export 'src/server/server_api.dart';
export 'src/server/usage.dart';
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:convert';
import 'dart:io';

import 'package:path/path.dart' as path;

import 'file_system.dart';

// Access the DevTools on disk store (~/.flutter-devtools/.devtools).
/// Provides access to the local DevTools store (~/.flutter-devtools/.devtools).
class DevToolsUsage {
DevToolsUsage() {
LocalFileSystem.maybeMoveLegacyDevToolsStore();
Expand Down Expand Up @@ -158,84 +153,3 @@ extension type _ActiveSurveyJson(Map<String, Object?> json) {
bool get surveyActionTaken => json[DevToolsUsage._surveyActionTaken] as bool;
int? get surveyShownCount => json[DevToolsUsage._surveyShownCount] as int?;
}

abstract class PersistentProperties {
PersistentProperties(this.name);

final String name;

// ignore: avoid-dynamic, dynamic by design.
dynamic operator [](String key);

// ignore: avoid-dynamic, dynamic by design.
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;

late Map<String, Object?> _map;

@override
// ignore: avoid-dynamic, necessary here.
dynamic operator [](String key) => _map[key];

@override
void operator []=(String key, Object? 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) as Map).cast<String, Object>();
} catch (_) {
_map = {};
}
}

void remove(String propertyName) {
_map.remove(propertyName);
}
}
90 changes: 88 additions & 2 deletions packages/devtools_shared/lib/src/server/file_system.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import 'dart:io';

import 'package:path/path.dart' as path;

import 'usage.dart';
import 'devtools_store.dart';

// ignore: avoid_classes_with_only_static_members, requires refactor.
class LocalFileSystem {
Expand Down Expand Up @@ -76,9 +76,95 @@ class LocalFileSystem {
return jsonEncode(json);
}

/// The location of the Flutter store file, ~/.flutter.
static String flutterStoreLocation() {
kenzieschmoll marked this conversation as resolved.
Show resolved Hide resolved
return path.join(_userHomeDir(), '.flutter');
}

/// Whether the flutter store file exists.
static bool flutterStoreExists() {
final flutterStore = File(path.join(_userHomeDir(), '.flutter'));
final flutterStore = File(flutterStoreLocation());
return flutterStore.existsSync();
}
}

class IOPersistentProperties extends PersistentProperties {
IOPersistentProperties(
String name, {
String? documentDirPath,
}) : super(name) {
final String fileName = name.replaceAll(' ', '_');
documentDirPath ??= LocalFileSystem._userHomeDir();
_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;

late Map<String, Object?> _map;

@override
// ignore: avoid-dynamic, necessary here.
kenzieschmoll marked this conversation as resolved.
Show resolved Hide resolved
dynamic operator [](String key) => _map[key];

@override
void operator []=(String key, Object? 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) as Map).cast<String, Object>();
} catch (_) {
_map = {};
}
}

void remove(String propertyName) {
_map.remove(propertyName);
}
}

abstract class PersistentProperties {
PersistentProperties(this.name);

final String name;

// ignore: avoid-dynamic, dynamic by design.
dynamic operator [](String key);

// ignore: avoid-dynamic, dynamic by design.
void operator []=(String key, dynamic value);
kenzieschmoll marked this conversation as resolved.
Show resolved Hide resolved

/// Re-read settings from the backing store.
///
/// May be a no-op on some platforms.
void syncSettings();
}

const JsonEncoder _jsonEncoder = JsonEncoder.withIndent(' ');
21 changes: 21 additions & 0 deletions packages/devtools_shared/lib/src/server/flutter_store.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright 2024 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.

import 'file_system.dart';

/// Provides access to the local Flutter store (~/.flutter).
class FlutterStore {
static const storeName = '.flutter';
static const firstRunKey = 'firstRun';
static const gaEnabledKey = 'enabled';
static const flutterClientIdKey = 'clientId';

final properties = IOPersistentProperties(storeName);

bool get isFirstRun => properties[firstRunKey] == true;

bool get gaEnabled => properties[gaEnabledKey] == true;

String get flutterClientId => properties[flutterClientIdKey] as String;
}
Loading
Loading