591 lines
18 KiB
Dart
591 lines
18 KiB
Dart
|
|
import 'dart:async';
|
|||
|
|
import 'dart:collection';
|
|||
|
|
import 'dart:convert';
|
|||
|
|
import 'dart:io';
|
|||
|
|
|
|||
|
|
import 'package:flutter/services.dart';
|
|||
|
|
import 'package:guru_analytics_flutter/events_constants.dart';
|
|||
|
|
import 'package:guru_analytics_flutter/events_constants.dart';
|
|||
|
|
import 'package:guru_app/account/account_data_store.dart';
|
|||
|
|
import 'package:guru_app/analytics/guru_analytics.dart';
|
|||
|
|
import 'package:guru_app/firebase/remoteconfig/remote_config_manager.dart';
|
|||
|
|
import 'package:guru_app/guru_app.dart';
|
|||
|
|
import 'package:guru_app/property/app_property.dart';
|
|||
|
|
import 'package:guru_app/property/settings/guru_settings.dart';
|
|||
|
|
import 'package:guru_app/utils/guru_file_utils_extension.dart';
|
|||
|
|
import 'package:guru_utils/core/ext.dart';
|
|||
|
|
import 'package:guru_utils/file/file_utils.dart';
|
|||
|
|
import 'package:guru_utils/log/log.dart';
|
|||
|
|
import 'package:guru_utils/property/runtime_property.dart';
|
|||
|
|
import 'package:guru_utils/quiver/cache.dart';
|
|||
|
|
import 'package:guru_utils/quiver/collection.dart';
|
|||
|
|
import 'package:guru_utils/settings/settings.dart';
|
|||
|
|
import 'package:guru_utils/tuple/tuple.dart';
|
|||
|
|
|
|||
|
|
abstract class EventMatcher {
|
|||
|
|
bool match(String eventName);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class UniversalMatcher extends EventMatcher {
|
|||
|
|
@override
|
|||
|
|
bool match(String eventName) => true;
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
String toString() {
|
|||
|
|
return 'UniversalMatcher';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class RegexMatcher extends EventMatcher {
|
|||
|
|
final RegExp re;
|
|||
|
|
|
|||
|
|
RegexMatcher(String pattern) : re = RegExp(pattern);
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
bool match(String eventName) => re.hasMatch(eventName);
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
String toString() {
|
|||
|
|
return 'RegexMatcher:${re.pattern}';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class WildcardMatcher extends RegexMatcher {
|
|||
|
|
final String wildcard;
|
|||
|
|
|
|||
|
|
WildcardMatcher(this.wildcard) : super("^${wildcard.replaceAll("*", ".*")}\$");
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
bool match(String eventName) => super.match(eventName);
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
String toString() {
|
|||
|
|
return 'WildcardMatcher:$wildcard => ${re.pattern}';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
abstract class StrategyValidator {
|
|||
|
|
bool get alwaysVerify => false;
|
|||
|
|
|
|||
|
|
const StrategyValidator();
|
|||
|
|
|
|||
|
|
bool validate();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class UnlimitedValidator extends StrategyValidator {
|
|||
|
|
const UnlimitedValidator();
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
bool validate() => true;
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
String toString() {
|
|||
|
|
return 'UnlimitedValidator';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class DisabledValidator extends StrategyValidator {
|
|||
|
|
@override
|
|||
|
|
bool validate() => false;
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
String toString() {
|
|||
|
|
return 'DisabledValidator';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class PlatformValidator extends StrategyValidator {
|
|||
|
|
final String platform;
|
|||
|
|
|
|||
|
|
PlatformValidator(this.platform);
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
bool validate() => Platform.isAndroid ? platform == "android" : platform == "ios";
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
String toString() {
|
|||
|
|
return 'PlatformValidator($platform)';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class CountryCodeValidator extends StrategyValidator {
|
|||
|
|
final Set<String> included;
|
|||
|
|
final Set<String> excluded;
|
|||
|
|
|
|||
|
|
CountryCodeValidator(this.included, this.excluded);
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
bool validate() {
|
|||
|
|
final countryCode = AccountDataStore.instance.countryCode;
|
|||
|
|
|
|||
|
|
// 如果excluded不为空,证明存在排除选项,该validate将只判断所有excluded中的逻辑,
|
|||
|
|
// 将不会在判断included中的逻辑
|
|||
|
|
if (excluded.isNotEmpty) {
|
|||
|
|
return !excluded.contains(countryCode);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (included.contains(countryCode)) {
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
String toString() {
|
|||
|
|
return 'CountryCodeValidator{included: $included, excluded: $excluded}';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class UserPropertyValidator extends StrategyValidator {
|
|||
|
|
@override
|
|||
|
|
bool get alwaysVerify => true;
|
|||
|
|
|
|||
|
|
final List<Tuple2<String, String>> validProperties;
|
|||
|
|
|
|||
|
|
UserPropertyValidator(this.validProperties);
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
bool validate() {
|
|||
|
|
for (var tuple in validProperties) {
|
|||
|
|
if (GuruAnalytics.instance.getProperty(tuple.item1) != tuple.item2) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
String toString() {
|
|||
|
|
return 'UserPropertyValidator{validProperties: $validProperties}';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class RandomValidator extends StrategyValidator {
|
|||
|
|
final int percent;
|
|||
|
|
|
|||
|
|
RandomValidator(int percent) : percent = percent.clamp(10, 90);
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
bool validate() {
|
|||
|
|
final firstInstallTime =
|
|||
|
|
RuntimeProperty.instance.getInt(UtilsSettingsKeys.firstInstallTime, defValue: -1);
|
|||
|
|
return (firstInstallTime % 9) >= (percent ~/ 10 - 1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
String toString() {
|
|||
|
|
return 'RandomValidator{percent: $percent}';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class VersionValidator extends StrategyValidator {
|
|||
|
|
final String opt;
|
|||
|
|
final String buildId;
|
|||
|
|
|
|||
|
|
VersionValidator(this.opt, this.buildId);
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
bool validate() {
|
|||
|
|
final buildNumber = GuruSettings.instance.buildNumber.get();
|
|||
|
|
switch (opt) {
|
|||
|
|
case "ve":
|
|||
|
|
return buildNumber == buildId;
|
|||
|
|
case "vg":
|
|||
|
|
return buildNumber.compareTo(buildId) > 0;
|
|||
|
|
case "vge":
|
|||
|
|
return buildNumber.compareTo(buildId) >= 0;
|
|||
|
|
case "vl":
|
|||
|
|
return buildNumber.compareTo(buildId) < 0;
|
|||
|
|
case "vle":
|
|||
|
|
return buildNumber.compareTo(buildId) <= 0;
|
|||
|
|
default:
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
String toString() {
|
|||
|
|
return 'VersionValidator{opt: $opt, buildId: $buildId}';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class StrategyRuleTypeException implements Exception {
|
|||
|
|
final String message;
|
|||
|
|
|
|||
|
|
StrategyRuleTypeException([this.message = "Type mismatch: Expected a StrategyRuleItem."]);
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
String toString() => "StrategyRuleTypeException: $message";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class StrategyRule {
|
|||
|
|
final EventMatcher? matcher;
|
|||
|
|
|
|||
|
|
final StrategyValidator validator;
|
|||
|
|
|
|||
|
|
final AppEventCapabilities appEventCapabilities;
|
|||
|
|
|
|||
|
|
final String? adjustToken;
|
|||
|
|
|
|||
|
|
AppEventOptions? _options;
|
|||
|
|
|
|||
|
|
StrategyRule(this.validator, this.appEventCapabilities, {this.matcher, this.adjustToken});
|
|||
|
|
|
|||
|
|
AppEventOptions? getAppEventOptions() {
|
|||
|
|
if ((_options != null && !validator.alwaysVerify) || validator.validate()) {
|
|||
|
|
return _options ??= AppEventOptions(capabilities: appEventCapabilities);
|
|||
|
|
}
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
String toString() {
|
|||
|
|
return 'StrategyRule{matcher: $matcher, validator: $validator, appEventCapabilities: $appEventCapabilities, adjustToken: $adjustToken}';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class StrategyRuleParser {
|
|||
|
|
static final invalidWildcardReg = RegExp(r'[^a-zA-Z=0-9_*]');
|
|||
|
|
static final adjustTokenReg = RegExp(r'^[a-z0-9]{6}$');
|
|||
|
|
static final randomStrategyReg = RegExp(r'^r([1-9]0)$');
|
|||
|
|
static final userPropertyStrategyReg = RegExp(r'^up:(.+)=(.+)$');
|
|||
|
|
static final versionStrategyReg = RegExp(r'^(ve|vg|vl|vge|vle)(\d{8})$');
|
|||
|
|
static final countryStrategyReg = RegExp(r'^cc:(.+)$');
|
|||
|
|
static final countryCodeValidReg = RegExp(r'^[a-z]{2}|\![a-z]{2}$');
|
|||
|
|
|
|||
|
|
final List<String> fields;
|
|||
|
|
|
|||
|
|
StrategyRuleParser(this.fields);
|
|||
|
|
|
|||
|
|
EventMatcher? createEventMatcher(String event) {
|
|||
|
|
if (event == "_all_") {
|
|||
|
|
return UniversalMatcher();
|
|||
|
|
} else if (!invalidWildcardReg.hasMatch(event)) {
|
|||
|
|
if (event.contains("*")) {
|
|||
|
|
return WildcardMatcher(event);
|
|||
|
|
} else {
|
|||
|
|
// 返回空的话,表示精确匹配,无需提供matcher
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
return RegexMatcher(event);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
StrategyValidator? createStrategyValidator(String strategy) {
|
|||
|
|
if (strategy == "unlimited") {
|
|||
|
|
return const UnlimitedValidator();
|
|||
|
|
} else if (strategy == "disabled") {
|
|||
|
|
return DisabledValidator();
|
|||
|
|
} else if (strategy == "android" || strategy == "ios") {
|
|||
|
|
return PlatformValidator(strategy);
|
|||
|
|
} else {
|
|||
|
|
final randomMatch = randomStrategyReg.firstMatch(strategy);
|
|||
|
|
final randomPercent = randomMatch?.group(1);
|
|||
|
|
if (!DartExt.isBlank(randomPercent)) {
|
|||
|
|
return RandomValidator(int.parse(randomPercent!));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
final userPropertyMatch = userPropertyStrategyReg.firstMatch(strategy);
|
|||
|
|
final userPropertyKey = userPropertyMatch?.group(1);
|
|||
|
|
final userPropertyValue = userPropertyMatch?.group(2);
|
|||
|
|
if (!DartExt.isBlank(userPropertyKey) && !DartExt.isBlank(userPropertyValue)) {
|
|||
|
|
return UserPropertyValidator([Tuple2(userPropertyKey!, userPropertyValue!)]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
final versionMatch = versionStrategyReg.firstMatch(strategy);
|
|||
|
|
final versionOpt = versionMatch?.group(1);
|
|||
|
|
final versionBuildId = versionMatch?.group(2);
|
|||
|
|
if (!DartExt.isBlank(versionOpt) && !DartExt.isBlank(versionBuildId)) {
|
|||
|
|
return VersionValidator(versionOpt!, versionBuildId!);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
final countryCodeMatch = countryStrategyReg.firstMatch(strategy);
|
|||
|
|
final countryCodeExpression = countryCodeMatch?.group(1);
|
|||
|
|
if (!DartExt.isBlank(countryCodeExpression)) {
|
|||
|
|
final included = <String>{};
|
|||
|
|
final excluded = <String>{};
|
|||
|
|
final countryCodes = countryCodeExpression!
|
|||
|
|
.split("|")
|
|||
|
|
.where((cc) => countryCodeValidReg.hasMatch(cc))
|
|||
|
|
.toSet();
|
|||
|
|
for (var cc in countryCodes) {
|
|||
|
|
if (cc.startsWith("!")) {
|
|||
|
|
excluded.add(cc.substring(1));
|
|||
|
|
} else {
|
|||
|
|
included.add(cc);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return CountryCodeValidator(included, excluded);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
StrategyRuleItem? fromData(List<String> data) {
|
|||
|
|
if (data.length != fields.length) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
String? event;
|
|||
|
|
EventMatcher? eventMatcher;
|
|||
|
|
StrategyValidator? validator;
|
|||
|
|
int appEventCapabilitiesFlag = 0;
|
|||
|
|
String? adjustToken;
|
|||
|
|
for (int i = 0; i < fields.length; ++i) {
|
|||
|
|
final field = fields[i];
|
|||
|
|
final value = data[i];
|
|||
|
|
|
|||
|
|
if (field == "event") {
|
|||
|
|
event = value;
|
|||
|
|
if (event.isEmpty) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
try {
|
|||
|
|
eventMatcher = createEventMatcher(value);
|
|||
|
|
Log.d("eventMatcher:$eventMatcher");
|
|||
|
|
} catch (error, stacktrace) {
|
|||
|
|
Log.w("createEventMatcher error! $error", stackTrace: stacktrace);
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
} else if (field == "guru") {
|
|||
|
|
if (value == '1') {
|
|||
|
|
appEventCapabilitiesFlag |= AppEventCapabilities.guru;
|
|||
|
|
}
|
|||
|
|
} else if (field == "firebase") {
|
|||
|
|
if (value == '1') {
|
|||
|
|
appEventCapabilitiesFlag |= AppEventCapabilities.firebase;
|
|||
|
|
}
|
|||
|
|
} else if (field == "facebook") {
|
|||
|
|
if (value == '1') {
|
|||
|
|
appEventCapabilitiesFlag |= AppEventCapabilities.facebook;
|
|||
|
|
}
|
|||
|
|
} else if (field == "adjust") {
|
|||
|
|
if (value == '1') {}
|
|||
|
|
} else if (field == "strategy") {
|
|||
|
|
validator = createStrategyValidator(value);
|
|||
|
|
} else if ((Platform.isAndroid && field == "ata") || (Platform.isIOS && field == "ati")) {
|
|||
|
|
if (value.isNotEmpty && adjustTokenReg.hasMatch(value)) {
|
|||
|
|
adjustToken = value;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (event != null && validator != null) {
|
|||
|
|
return StrategyRuleItem(
|
|||
|
|
event,
|
|||
|
|
StrategyRule(validator, AppEventCapabilities(appEventCapabilitiesFlag),
|
|||
|
|
matcher: eventMatcher, adjustToken: adjustToken));
|
|||
|
|
}
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class StrategyRuleItem extends Comparable {
|
|||
|
|
final String eventName;
|
|||
|
|
final StrategyRule rule;
|
|||
|
|
|
|||
|
|
StrategyRuleItem(this.eventName, this.rule);
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
int compareTo(other) {
|
|||
|
|
if (other is StrategyRuleItem) {
|
|||
|
|
return eventName.compareTo(other.eventName);
|
|||
|
|
}
|
|||
|
|
throw StrategyRuleTypeException();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class GuruAnalyticsStrategy {
|
|||
|
|
static const String tag = "GuruAnalyticsStrategy";
|
|||
|
|
final List<StrategyRule> priorityRules = [];
|
|||
|
|
final SplayTreeMap<String, StrategyRule> explicitRules = SplayTreeMap();
|
|||
|
|
final Map<String, AdjustEventConverter> iosAdjustEventConverters = {};
|
|||
|
|
final Map<String, AdjustEventConverter> androidAdjustEventConverts = {};
|
|||
|
|
|
|||
|
|
bool loaded = false;
|
|||
|
|
|
|||
|
|
final LinkedLruHashMap<String, StrategyRule> eventRules = LinkedLruHashMap(maximumSize: 128);
|
|||
|
|
|
|||
|
|
GuruAnalyticsStrategy._();
|
|||
|
|
|
|||
|
|
static final GuruAnalyticsStrategy instance = GuruAnalyticsStrategy._();
|
|||
|
|
|
|||
|
|
void reset() {
|
|||
|
|
priorityRules.clear();
|
|||
|
|
explicitRules.clear();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static const guruAnalyticsStrategyExtension = ".gas"; // Guru Analytics Strategy
|
|||
|
|
|
|||
|
|
Future<File?> checkAndCreateLocalStrategyFile() async {
|
|||
|
|
final currentLocalStrategy =
|
|||
|
|
"${GuruSettings.instance.buildNumber.get()}$guruAnalyticsStrategyExtension";
|
|||
|
|
final file = await FileUtils.instance.getGuruConfigFile("analytics", currentLocalStrategy);
|
|||
|
|
if (!file.existsSync()) {
|
|||
|
|
try {
|
|||
|
|
final data = await rootBundle.loadString("assets/guru/analytics_strategy.csv");
|
|||
|
|
file.writeAsStringSync(data);
|
|||
|
|
Log.i("load local strategy success! [$currentLocalStrategy]", tag: tag);
|
|||
|
|
return file;
|
|||
|
|
} catch (error, stacktrace) {
|
|||
|
|
Log.w("not config local strategy!", tag: tag);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Future load() async {
|
|||
|
|
try {
|
|||
|
|
final analyticsConfig = RemoteConfigManager.instance.getAnalyticsConfig();
|
|||
|
|
|
|||
|
|
if (!GuruApp.instance.appSpec.deployment.enabledGuruAnalyticsStrategy ||
|
|||
|
|
!analyticsConfig.enabledStrategy) {
|
|||
|
|
Log.w("analytics strategy disabled!", tag: tag);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
final String remoteAnalyticsStrategy = analyticsConfig.strategy;
|
|||
|
|
final latestAnalyticsStrategy = await AppProperty.getInstance().getLatestAnalyticsStrategy();
|
|||
|
|
if (remoteAnalyticsStrategy != latestAnalyticsStrategy) {
|
|||
|
|
loaded = false;
|
|||
|
|
}
|
|||
|
|
if (loaded) {
|
|||
|
|
Log.w("already loaded! ignore!", tag: tag);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
File? file;
|
|||
|
|
// 如果remoteAnalyticsStrategy非空表示云控配置了strategy
|
|||
|
|
if (!DartExt.isBlank(remoteAnalyticsStrategy)) {
|
|||
|
|
file = await FileUtils.instance.getGuruConfigFile("analytics", remoteAnalyticsStrategy);
|
|||
|
|
if (!file.existsSync()) {
|
|||
|
|
try {
|
|||
|
|
await FileUtils.instance.downloadFile(
|
|||
|
|
"${GuruApp.instance.details.storagePrefix}/guru%2Fanalytics%2F$remoteAnalyticsStrategy?alt=media",
|
|||
|
|
file);
|
|||
|
|
Log.i("download analytics strategy[$remoteAnalyticsStrategy] success", tag: tag);
|
|||
|
|
} catch (error, stacktrace) {
|
|||
|
|
Log.w("downloadFile error! $error try to fallback", tag: tag);
|
|||
|
|
// 这里没有使用上一次的strategy做回滚的原因,
|
|||
|
|
// 主要是考虑到上一次的云端strategy可能没有本地的strategy可靠,
|
|||
|
|
// SDK假设本地的strategy比Firebase Storage中配置的strategy更可靠
|
|||
|
|
// 因此这里在出现下载异常的情况下,会回滚到本地strategy上
|
|||
|
|
// 如果不想使用这个机制,可以在自己的项目中不配置任何strategy
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (remoteAnalyticsStrategy != latestAnalyticsStrategy) {
|
|||
|
|
AppProperty.getInstance().setLatestAnalyticsStrategy(remoteAnalyticsStrategy);
|
|||
|
|
final latestStrategyFile =
|
|||
|
|
await FileUtils.instance.getGuruConfigFile("analytics", latestAnalyticsStrategy);
|
|||
|
|
if (latestStrategyFile.existsSync()) {
|
|||
|
|
FileUtils.instance.deleteFile(latestStrategyFile);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果当前文件为空或是不存在,证明有可能相应的strategy下载失败,或是没有设置
|
|||
|
|
// 因此这种情况下尝试使用本地的strategy进行加载
|
|||
|
|
if (file?.existsSync() != true) {
|
|||
|
|
file = await checkAndCreateLocalStrategyFile();
|
|||
|
|
if (file?.existsSync() != true) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
final Stream<String> strategyTextStream = file!.openRead().transform(utf8.decoder);
|
|||
|
|
|
|||
|
|
StrategyRule? newDefaultRule;
|
|||
|
|
final List<StrategyRule> newPriorityRules = [];
|
|||
|
|
final Map<String, StrategyRule> newExplicitRules = {};
|
|||
|
|
StrategyRuleParser? parser;
|
|||
|
|
int lineNum = 0;
|
|||
|
|
await for (var line in strategyTextStream.transform(const LineSplitter())) {
|
|||
|
|
final list = line.split(",");
|
|||
|
|
Log.d("[${lineNum++}] $list", tag: tag);
|
|||
|
|
if (parser == null) {
|
|||
|
|
parser = StrategyRuleParser(list);
|
|||
|
|
} else {
|
|||
|
|
final ruleItem = parser.fromData(list);
|
|||
|
|
if (ruleItem == null) {
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
if (ruleItem.eventName == "_all_") {
|
|||
|
|
newDefaultRule = ruleItem.rule;
|
|||
|
|
} else if (ruleItem.rule.matcher != null) {
|
|||
|
|
newPriorityRules.add(ruleItem.rule);
|
|||
|
|
} else {
|
|||
|
|
newExplicitRules[ruleItem.eventName] = ruleItem.rule;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (ruleItem.rule.adjustToken != null) {
|
|||
|
|
if (Platform.isAndroid) {
|
|||
|
|
androidAdjustEventConverts[ruleItem.eventName] =
|
|||
|
|
(_) => AdjustEvent(ruleItem.rule.adjustToken!);
|
|||
|
|
} else if (Platform.isIOS) {
|
|||
|
|
iosAdjustEventConverters[ruleItem.eventName] =
|
|||
|
|
(_) => AdjustEvent(ruleItem.rule.adjustToken!);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
reset();
|
|||
|
|
priorityRules.addAll(newPriorityRules.reversed);
|
|||
|
|
if (newDefaultRule != null) {
|
|||
|
|
priorityRules.add(newDefaultRule);
|
|||
|
|
}
|
|||
|
|
explicitRules.addAll(newExplicitRules);
|
|||
|
|
|
|||
|
|
loaded = true;
|
|||
|
|
Log.d(
|
|||
|
|
"analytics strategy loaded! ${eventRules.length} ${explicitRules.length} ${priorityRules.length}",
|
|||
|
|
tag: tag);
|
|||
|
|
} catch (error, stacktrace) {}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
StrategyRule? getStrategyRule(String eventName) {
|
|||
|
|
Log.d(
|
|||
|
|
"[$loaded]getStrategyRule:$eventName ${eventRules.length} ${explicitRules.length} ${priorityRules.length}");
|
|||
|
|
if (!loaded) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
final rule = eventRules[eventName];
|
|||
|
|
if (rule != null) {
|
|||
|
|
return rule;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
final explicitRule = explicitRules[eventName];
|
|||
|
|
if (explicitRule != null) {
|
|||
|
|
return explicitRule;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for (var rule in priorityRules) {
|
|||
|
|
Log.d("matcher: ${rule.matcher} eventName: $eventName ${rule.matcher?.match(eventName)}",
|
|||
|
|
tag: tag);
|
|||
|
|
if (rule.matcher?.match(eventName) == true) {
|
|||
|
|
return rule;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// 如果没有启用strategy,默认按之前逻辑处理
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
AdjustEventConverter? getAdjustEventConverter(String eventName) {
|
|||
|
|
if (Platform.isAndroid) {
|
|||
|
|
return androidAdjustEventConverts[eventName];
|
|||
|
|
} else if (Platform.isIOS) {
|
|||
|
|
return iosAdjustEventConverters[eventName];
|
|||
|
|
}
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void testRule(String eventName) {
|
|||
|
|
final rule = getStrategyRule(eventName);
|
|||
|
|
if (rule?.matcher?.match(eventName) != false) {
|
|||
|
|
Log.d("testMatch: $eventName => $rule success!", tag: tag);
|
|||
|
|
} else {
|
|||
|
|
Log.d("testMatch: $eventName => $rule error!", tag: tag);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|