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

feat: use XDG directories on Linux #1188

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 4 additions & 0 deletions pkgs/unified_analytics/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 6.2.0

- Use XDG directories on Linux.

## 6.1.4

- Fix formatting and remove dependency on `package:intl`.
Expand Down
17 changes: 11 additions & 6 deletions pkgs/unified_analytics/example/serving_surveys.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'package:file/memory.dart';
import 'package:unified_analytics/src/constants.dart';
import 'package:unified_analytics/src/enums.dart';
import 'package:unified_analytics/src/survey_handler.dart';
import 'package:unified_analytics/src/utils.dart';
import 'package:unified_analytics/unified_analytics.dart';

/// This example code is intended to only be used as guidance for
Expand All @@ -22,6 +23,8 @@ void main() async {
late final MemoryFileSystem fs;
late final Analytics analytics;
late final Directory home;
late final Directory dataDirectory;
late final Directory configDirectory;
// We need to initialize with a fake clock since the surveys have
// a period of time they are valid for
await withClock(Clock.fixed(DateTime(2023, 3, 3, 12, 0)), () async {
Expand All @@ -30,6 +33,7 @@ void main() async {
fs = MemoryFileSystem(style: FileSystemStyle.posix);
home = fs.directory('home');
home.createSync();
(dataDirectory, configDirectory) = getToolDirectories(fs)!;

// The purpose of `initialAnalytics` is so that the tool is able to
// send events after its first run; this instance won't be used below
Expand All @@ -38,6 +42,8 @@ void main() async {
final initialAnalytics = Analytics.fake(
tool: DashTool.flutterTool,
homeDirectory: home,
dataDirectory: dataDirectory,
configDirectory: configDirectory,
dartVersion: 'dartVersion',
fs: fs,
platform: DevicePlatform.macos,
Expand All @@ -49,13 +55,14 @@ void main() async {
analytics = Analytics.fake(
tool: DashTool.flutterTool,
homeDirectory: home,
dataDirectory: dataDirectory,
configDirectory: configDirectory,
dartVersion: 'dartVersion',
fs: fs,
platform: DevicePlatform.macos,
surveyHandler: FakeSurveyHandler.fromList(
dismissedSurveyFile: home
.childDirectory(kDartToolDirectoryName)
.childFile(kDismissedSurveyFileName),
dismissedSurveyFile:
dataDirectory.childFile(kDismissedSurveyFileName),
initializedSurveys: [
Survey(
uniqueId: 'uniqueId',
Expand Down Expand Up @@ -113,9 +120,7 @@ void main() async {
analytics.surveyShown(survey);

// Get the file where this is persisted to show it getting updated
final persistedSurveyFile = home
.childDirectory(kDartToolDirectoryName)
.childFile(kDismissedSurveyFileName);
final persistedSurveyFile = dataDirectory.childFile(kDismissedSurveyFileName);
print('The contents of the json file '
'after invoking `analytics.surveyShown(survey);`');
print('${persistedSurveyFile.readAsStringSync()}\n');
Expand Down
63 changes: 39 additions & 24 deletions pkgs/unified_analytics/lib/src/analytics.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ abstract class Analytics {
return const NoOpAnalytics();
}

final (dataDirectory, configDirectory) = getToolDirectories(fs)!;

// Resolve the OS using dart:io
final DevicePlatform platform;
if (io.Platform.operatingSystem == 'linux') {
Expand All @@ -77,11 +79,17 @@ abstract class Analytics {
apiSecret: kGoogleAnalyticsApiSecret,
);

final firstRun = runInitialization(homeDirectory: homeDirectory);
final firstRun = runInitialization(
homeDirectory: homeDirectory,
configDirectory: configDirectory,
dataDirectory: dataDirectory,
);

return AnalyticsImpl(
tool: tool,
homeDirectory: homeDirectory,
dataDirectory: dataDirectory,
configDirectory: configDirectory,
flutterChannel: flutterChannel,
flutterVersion: flutterVersion,
dartVersion: dartVersion,
Expand All @@ -90,9 +98,7 @@ abstract class Analytics {
fs: fs,
gaClient: gaClient,
surveyHandler: SurveyHandler(
dismissedSurveyFile: homeDirectory
.childDirectory(kDartToolDirectoryName)
.childFile(kDismissedSurveyFileName),
dismissedSurveyFile: dataDirectory.childFile(kDismissedSurveyFileName),
),
enableAsserts: enableAsserts,
clientIde: clientIde,
Expand Down Expand Up @@ -133,6 +139,8 @@ abstract class Analytics {
throw Exception('Permissions error on the home directory!');
}

final (configDirectory, dataDirectory) = getToolDirectories(fs)!;

// Resolve the OS using dart:io
final DevicePlatform platform;
if (io.Platform.operatingSystem == 'linux') {
Expand All @@ -154,11 +162,17 @@ abstract class Analytics {
apiSecret: kTestApiSecret,
);

final firstRun = runInitialization(homeDirectory: homeDirectory);
final firstRun = runInitialization(
homeDirectory: homeDirectory,
configDirectory: configDirectory,
dataDirectory: dataDirectory,
);

return AnalyticsImpl(
tool: tool,
homeDirectory: homeDirectory,
dataDirectory: dataDirectory,
configDirectory: configDirectory,
flutterChannel: flutterChannel,
flutterVersion: flutterVersion,
dartVersion: dartVersion,
Expand All @@ -167,9 +181,7 @@ abstract class Analytics {
fs: fs,
gaClient: gaClient,
surveyHandler: SurveyHandler(
dismissedSurveyFile: homeDirectory
.childDirectory(kDartToolDirectoryName)
.childFile(kDismissedSurveyFileName),
dismissedSurveyFile: dataDirectory.childFile(kDismissedSurveyFileName),
),
enableAsserts: enableAsserts,
clientIde: clientIde,
Expand Down Expand Up @@ -289,6 +301,8 @@ abstract class Analytics {
static FakeAnalytics fake({
required DashTool tool,
required Directory homeDirectory,
required Directory configDirectory,
required Directory dataDirectory,
required String dartVersion,
required MemoryFileSystem fs,
String? flutterChannel,
Expand All @@ -302,11 +316,17 @@ abstract class Analytics {
String toolsMessage = kToolsMessage,
bool enableAsserts = true,
}) {
final firstRun = runInitialization(homeDirectory: homeDirectory);
final firstRun = runInitialization(
homeDirectory: homeDirectory,
configDirectory: configDirectory,
dataDirectory: dataDirectory,
);

return FakeAnalytics._(
tool: tool,
homeDirectory: homeDirectory,
dataDirectory: dataDirectory,
configDirectory: configDirectory,
flutterChannel: flutterChannel,
toolsMessageVersion: toolsMessageVersion,
flutterVersion: flutterVersion,
Expand All @@ -315,9 +335,8 @@ abstract class Analytics {
fs: fs,
surveyHandler: surveyHandler ??
FakeSurveyHandler.fromList(
dismissedSurveyFile: homeDirectory
.childDirectory(kDartToolDirectoryName)
.childFile(kDismissedSurveyFileName),
dismissedSurveyFile:
dataDirectory.childFile(kDismissedSurveyFileName),
initializedSurveys: [],
),
gaClient: gaClient ?? const FakeGAClient(),
Expand Down Expand Up @@ -373,6 +392,8 @@ class AnalyticsImpl implements Analytics {
AnalyticsImpl({
required this.tool,
required Directory homeDirectory,
required Directory dataDirectory,
required Directory configDirectory,
required String? flutterChannel,
required String? flutterVersion,
required String? clientIde,
Expand All @@ -388,13 +409,9 @@ class AnalyticsImpl implements Analytics {
}) : _gaClient = gaClient,
_surveyHandler = surveyHandler,
_enableAsserts = enableAsserts,
_clientIdFile = homeDirectory
.childDirectory(kDartToolDirectoryName)
.childFile(kClientIdFileName),
_clientIdFile = dataDirectory.childFile(kClientIdFileName),
_userProperty = UserProperty(
sessionFile: homeDirectory
.childDirectory(kDartToolDirectoryName)
.childFile(kSessionFileName),
sessionFile: dataDirectory.childFile(kSessionFileName),
flutterChannel: flutterChannel,
host: platform.label,
flutterVersion: flutterVersion,
Expand All @@ -410,14 +427,10 @@ class AnalyticsImpl implements Analytics {
),
_configHandler = ConfigHandler(
homeDirectory: homeDirectory,
configFile: homeDirectory
.childDirectory(kDartToolDirectoryName)
.childFile(kConfigFileName),
configFile: configDirectory.childFile(kConfigFileName),
),
_logHandler = LogHandler(
logFile: homeDirectory
.childDirectory(kDartToolDirectoryName)
.childFile(kLogFileName),
logFile: dataDirectory.childFile(kLogFileName),
) {
// This initializer class will let the instance know
// if it was the first run; if it is, nothing will be sent
Expand Down Expand Up @@ -747,6 +760,8 @@ class FakeAnalytics extends AnalyticsImpl {
FakeAnalytics._({
required super.tool,
required super.homeDirectory,
required super.dataDirectory,
required super.configDirectory,
required super.dartVersion,
required super.platform,
required super.fs,
Expand Down
2 changes: 1 addition & 1 deletion pkgs/unified_analytics/lib/src/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const String kContextualSurveyUrl =

/// Name of the directory where all of the files necessary for this package
/// will be located.
const String kDartToolDirectoryName = '.dart-tool';
const String kDartToolDirectoryName = 'dart-tool';

/// The default time to wait before closing the http connection to allow for
/// pending events to be sent.
Expand Down
15 changes: 7 additions & 8 deletions pkgs/unified_analytics/lib/src/initializer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,14 @@ DateTime createSessionFile({required File sessionFile}) {
/// - Dismissed survey JSON file
bool runInitialization({
required Directory homeDirectory,
required Directory configDirectory,
required Directory dataDirectory,
}) {
var firstRun = false;
final dartToolDirectory =
homeDirectory.childDirectory(kDartToolDirectoryName);

// When the config file doesn't exist, initialize it with the default tools
// and the current date.
final configFile = dartToolDirectory.childFile(kConfigFileName);
final configFile = configDirectory.childFile(kConfigFileName);
if (!configFile.existsSync()) {
firstRun = true;
createConfigFile(
Expand All @@ -90,26 +90,25 @@ bool runInitialization({
}

// Begin initialization checks for the client id.
final clientFile = dartToolDirectory.childFile(kClientIdFileName);
final clientFile = dataDirectory.childFile(kClientIdFileName);
if (!clientFile.existsSync()) {
createClientIdFile(clientIdFile: clientFile);
}

// Begin initialization checks for the session file.
final sessionFile = dartToolDirectory.childFile(kSessionFileName);
final sessionFile = dataDirectory.childFile(kSessionFileName);
if (!sessionFile.existsSync()) {
createSessionFile(sessionFile: sessionFile);
}

// Begin initialization checks for the log file to persist events locally.
final logFile = dartToolDirectory.childFile(kLogFileName);
final logFile = dataDirectory.childFile(kLogFileName);
if (!logFile.existsSync()) {
createLogFile(logFile: logFile);
}

// Begin initialization checks for the dismissed survey file.
final dismissedSurveyFile =
dartToolDirectory.childFile(kDismissedSurveyFileName);
final dismissedSurveyFile = dataDirectory.childFile(kDismissedSurveyFileName);
if (!dismissedSurveyFile.existsSync()) {
createDismissedSurveyFile(dismissedSurveyFile: dismissedSurveyFile);
}
Expand Down
35 changes: 35 additions & 0 deletions pkgs/unified_analytics/lib/src/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'package:clock/clock.dart';
import 'package:convert/convert.dart';
import 'package:file/file.dart';

import 'constants.dart';
import 'enums.dart';
import 'event.dart';
import 'survey_handler.dart';
Expand Down Expand Up @@ -108,6 +109,40 @@ Directory? getHomeDirectory(FileSystem fs) {
return fs.directory(home);
}

/// When using Linux, use the XDG Base Directory Specification to determine
/// the location of the config and data directories. If the tool directory
/// exists at the old location, use it as a fallback.
(Directory data, Directory config)? getToolDirectories(FileSystem fs) {
final home = getHomeDirectory(fs);
if (home == null) return null;

final oldConfigDirectory = home.childDirectory('.dart-tool');

if (!io.Platform.isLinux) {
return (oldConfigDirectory, oldConfigDirectory);
}

final xdgDataHome =
io.Platform.environment['XDG_DATA_HOME'] ?? '~/.local/share';
final dataDirectory =
fs.directory(xdgDataHome).childDirectory(kDartToolDirectoryName);

final xdgConfigHome =
io.Platform.environment['XDG_CONFIG_HOME'] ?? '~/.config';
final configDirectory =
fs.directory(xdgConfigHome).childDirectory(kDartToolDirectoryName);

if (dataDirectory.existsSync() && configDirectory.existsSync()) {
return (dataDirectory, configDirectory);
}

if (oldConfigDirectory.existsSync()) {
return (oldConfigDirectory, oldConfigDirectory);
}

return (dataDirectory, configDirectory);
}

/// Returns `true` if user has opted out of legacy analytics in
/// Dart or Flutter.
///
Expand Down
2 changes: 1 addition & 1 deletion pkgs/unified_analytics/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: >-
# LINT.IfChange
# When updating this, keep the version consistent with the changelog and the
# value in lib/src/constants.dart.
version: 6.1.4
version: 6.2.0
# LINT.ThenChange(lib/src/constants.dart)
repository: https://github.com/dart-lang/tools/tree/main/pkgs/unified_analytics

Expand Down
Loading