Refactor geo assets
This commit is contained in:
@@ -4,18 +4,17 @@ import 'package:dio/dio.dart';
|
||||
import 'package:hiddify/core/core_providers.dart';
|
||||
import 'package:hiddify/core/prefs/general_prefs.dart';
|
||||
import 'package:hiddify/data/api/clash_api.dart';
|
||||
import 'package:hiddify/data/local/dao/dao.dart';
|
||||
import 'package:hiddify/data/local/dao/profiles_dao.dart';
|
||||
import 'package:hiddify/data/local/database.dart';
|
||||
import 'package:hiddify/data/repository/app_repository_impl.dart';
|
||||
import 'package:hiddify/data/repository/config_options_store.dart';
|
||||
import 'package:hiddify/data/repository/geo_assets_repository.dart';
|
||||
import 'package:hiddify/data/repository/repository.dart';
|
||||
import 'package:hiddify/domain/app/app.dart';
|
||||
import 'package:hiddify/domain/constants.dart';
|
||||
import 'package:hiddify/domain/core_facade.dart';
|
||||
import 'package:hiddify/domain/profiles/profiles.dart';
|
||||
import 'package:hiddify/domain/rules/geo_assets_repository.dart';
|
||||
import 'package:hiddify/domain/singbox/singbox.dart';
|
||||
import 'package:hiddify/features/geo_asset/data/geo_asset_data_providers.dart';
|
||||
import 'package:hiddify/services/service_providers.dart';
|
||||
import 'package:native_dio_adapter/native_dio_adapter.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
@@ -70,38 +69,25 @@ AppRepository appRepository(AppRepositoryRef ref) =>
|
||||
@Riverpod(keepAlive: true)
|
||||
ClashApi clashApi(ClashApiRef ref) => ClashApi(Defaults.clashApiPort);
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
GeoAssetsDao geoAssetsDao(GeoAssetsDaoRef ref) => GeoAssetsDao(
|
||||
ref.watch(appDatabaseProvider),
|
||||
);
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
GeoAssetsRepository geoAssetsRepository(GeoAssetsRepositoryRef ref) {
|
||||
return GeoAssetsRepositoryImpl(
|
||||
geoAssetsDao: ref.watch(geoAssetsDaoProvider),
|
||||
dio: ref.watch(dioProvider),
|
||||
filesEditor: ref.watch(filesEditorServiceProvider),
|
||||
);
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<ConfigOptions> configOptions(ConfigOptionsRef ref) async {
|
||||
final geoAssets = await ref
|
||||
.watch(geoAssetsRepositoryProvider)
|
||||
.watch(geoAssetRepositoryProvider)
|
||||
.requireValue
|
||||
.getActivePair()
|
||||
.getOrElse((l) => throw l)
|
||||
.run();
|
||||
final filesEditor = ref.watch(filesEditorServiceProvider);
|
||||
final geoAssetsPathResolver = ref.watch(geoAssetPathResolverProvider);
|
||||
|
||||
final serviceMode = ref.watch(serviceModeStoreProvider);
|
||||
return ref.watch(configPreferencesProvider).copyWith(
|
||||
enableTun: serviceMode == ServiceMode.tun,
|
||||
setSystemProxy: serviceMode == ServiceMode.systemProxy,
|
||||
geoipPath: filesEditor.geoAssetRelativePath(
|
||||
geoipPath: geoAssetsPathResolver.relativePath(
|
||||
geoAssets.geoip.providerName,
|
||||
geoAssets.geoip.fileName,
|
||||
),
|
||||
geositePath: filesEditor.geoAssetRelativePath(
|
||||
geositePath: geoAssetsPathResolver.relativePath(
|
||||
geoAssets.geosite.providerName,
|
||||
geoAssets.geosite.fileName,
|
||||
),
|
||||
@@ -112,6 +98,7 @@ Future<ConfigOptions> configOptions(ConfigOptionsRef ref) async {
|
||||
CoreFacade coreFacade(CoreFacadeRef ref) => CoreFacadeImpl(
|
||||
ref.watch(singboxServiceProvider),
|
||||
ref.watch(filesEditorServiceProvider),
|
||||
ref.watch(geoAssetPathResolverProvider),
|
||||
ref.watch(platformServicesProvider),
|
||||
ref.watch(clashApiProvider),
|
||||
ref.read(debugModeNotifierProvider),
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export 'geo_assets_dao.dart';
|
||||
export 'profiles_dao.dart';
|
||||
@@ -1,46 +0,0 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:hiddify/data/local/data_mappers.dart';
|
||||
import 'package:hiddify/data/local/database.dart';
|
||||
import 'package:hiddify/data/local/tables.dart';
|
||||
import 'package:hiddify/domain/rules/geo_asset.dart';
|
||||
import 'package:hiddify/utils/custom_loggers.dart';
|
||||
|
||||
part 'geo_assets_dao.g.dart';
|
||||
|
||||
@DriftAccessor(tables: [GeoAssetEntries])
|
||||
class GeoAssetsDao extends DatabaseAccessor<AppDatabase>
|
||||
with _$GeoAssetsDaoMixin, InfraLogger {
|
||||
GeoAssetsDao(super.db);
|
||||
|
||||
Future<void> add(GeoAsset geoAsset) async {
|
||||
await into(geoAssetEntries).insert(geoAsset.toCompanion());
|
||||
}
|
||||
|
||||
Future<GeoAsset?> getActive(GeoAssetType type) async {
|
||||
return (geoAssetEntries.select()
|
||||
..where((tbl) => tbl.active.equals(true))
|
||||
..where((tbl) => tbl.type.equalsValue(type))
|
||||
..limit(1))
|
||||
.map(GeoAssetMapper.fromEntry)
|
||||
.getSingleOrNull();
|
||||
}
|
||||
|
||||
Stream<List<GeoAsset>> watchAll() {
|
||||
return geoAssetEntries.select().map(GeoAssetMapper.fromEntry).watch();
|
||||
}
|
||||
|
||||
Future<void> edit(GeoAsset patch) async {
|
||||
await transaction(
|
||||
() async {
|
||||
if (patch.active) {
|
||||
await (update(geoAssetEntries)
|
||||
..where((tbl) => tbl.active.equals(true))
|
||||
..where((tbl) => tbl.type.equalsValue(patch.type)))
|
||||
.write(const GeoAssetEntriesCompanion(active: Value(false)));
|
||||
}
|
||||
await (update(geoAssetEntries)..where((tbl) => tbl.id.equals(patch.id)))
|
||||
.write(patch.toCompanion());
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:hiddify/data/local/database.dart';
|
||||
import 'package:hiddify/domain/profiles/profiles.dart';
|
||||
import 'package:hiddify/domain/rules/geo_asset.dart';
|
||||
|
||||
extension ProfileMapper on Profile {
|
||||
ProfileEntriesCompanion toCompanion() {
|
||||
@@ -72,29 +71,3 @@ extension ProfileMapper on Profile {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
extension GeoAssetMapper on GeoAsset {
|
||||
GeoAssetEntriesCompanion toCompanion() {
|
||||
return GeoAssetEntriesCompanion.insert(
|
||||
id: id,
|
||||
type: type,
|
||||
active: active,
|
||||
name: name,
|
||||
providerName: providerName,
|
||||
version: Value(version),
|
||||
lastCheck: Value(lastCheck),
|
||||
);
|
||||
}
|
||||
|
||||
static GeoAsset fromEntry(GeoAssetEntry e) {
|
||||
return GeoAsset(
|
||||
id: e.id,
|
||||
name: e.name,
|
||||
type: e.type,
|
||||
active: e.active,
|
||||
providerName: e.providerName,
|
||||
version: e.version,
|
||||
lastCheck: e.lastCheck,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,22 +2,19 @@ import 'dart:io';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:hiddify/data/local/dao/dao.dart';
|
||||
import 'package:hiddify/data/local/data_mappers.dart';
|
||||
import 'package:hiddify/data/local/schema_versions.dart';
|
||||
import 'package:hiddify/data/local/tables.dart';
|
||||
import 'package:hiddify/data/local/type_converters.dart';
|
||||
import 'package:hiddify/domain/profiles/profiles.dart';
|
||||
import 'package:hiddify/domain/rules/geo_asset.dart';
|
||||
import 'package:hiddify/features/geo_asset/data/geo_asset_data_mapper.dart';
|
||||
import 'package:hiddify/features/geo_asset/model/default_geo_assets.dart';
|
||||
import 'package:hiddify/features/geo_asset/model/geo_asset_entity.dart';
|
||||
import 'package:hiddify/services/files_editor_service.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
part 'database.g.dart';
|
||||
|
||||
@DriftDatabase(
|
||||
tables: [ProfileEntries, GeoAssetEntries],
|
||||
daos: [ProfilesDao, GeoAssetsDao],
|
||||
)
|
||||
@DriftDatabase(tables: [ProfileEntries, GeoAssetEntries])
|
||||
class AppDatabase extends _$AppDatabase {
|
||||
AppDatabase({required QueryExecutor connection}) : super(connection);
|
||||
|
||||
@@ -57,7 +54,7 @@ class AppDatabase extends _$AppDatabase {
|
||||
|
||||
Future<void> _prePopulateGeoAssets() async {
|
||||
await transaction(() async {
|
||||
final geoAssets = defaultGeoAssets.map((e) => e.toCompanion());
|
||||
final geoAssets = defaultGeoAssets.map((e) => e.toEntry());
|
||||
for (final geoAsset in geoAssets) {
|
||||
await into(geoAssetEntries).insert(geoAsset);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:hiddify/data/local/type_converters.dart';
|
||||
import 'package:hiddify/domain/profiles/profiles.dart';
|
||||
import 'package:hiddify/domain/rules/geo_asset.dart';
|
||||
import 'package:hiddify/features/geo_asset/model/geo_asset_entity.dart';
|
||||
|
||||
@DataClassName('ProfileEntry')
|
||||
class ProfileEntries extends Table {
|
||||
|
||||
@@ -10,6 +10,7 @@ import 'package:hiddify/domain/constants.dart';
|
||||
import 'package:hiddify/domain/core_facade.dart';
|
||||
import 'package:hiddify/domain/core_service_failure.dart';
|
||||
import 'package:hiddify/domain/singbox/singbox.dart';
|
||||
import 'package:hiddify/features/geo_asset/data/geo_asset_path_resolver.dart';
|
||||
import 'package:hiddify/services/files_editor_service.dart';
|
||||
import 'package:hiddify/services/platform_services.dart';
|
||||
import 'package:hiddify/services/singbox/singbox_service.dart';
|
||||
@@ -19,6 +20,7 @@ class CoreFacadeImpl with ExceptionHandler, InfraLogger implements CoreFacade {
|
||||
CoreFacadeImpl(
|
||||
this.singbox,
|
||||
this.filesEditor,
|
||||
this.geoAssetPathResolver,
|
||||
this.platformServices,
|
||||
this.clash,
|
||||
this.debug,
|
||||
@@ -27,6 +29,7 @@ class CoreFacadeImpl with ExceptionHandler, InfraLogger implements CoreFacade {
|
||||
|
||||
final SingboxService singbox;
|
||||
final FilesEditorService filesEditor;
|
||||
final GeoAssetPathResolver geoAssetPathResolver;
|
||||
final PlatformServices platformServices;
|
||||
final ClashApi clash;
|
||||
final bool debug;
|
||||
@@ -38,8 +41,8 @@ class CoreFacadeImpl with ExceptionHandler, InfraLogger implements CoreFacade {
|
||||
return exceptionHandler(
|
||||
() async {
|
||||
final options = await configOptions();
|
||||
final geoip = filesEditor.resolveGeoAssetPath(options.geoipPath);
|
||||
final geosite = filesEditor.resolveGeoAssetPath(options.geositePath);
|
||||
final geoip = geoAssetPathResolver.resolvePath(options.geoipPath);
|
||||
final geosite = geoAssetPathResolver.resolvePath(options.geositePath);
|
||||
if (!await File(geoip).exists() || !await File(geosite).exists()) {
|
||||
return left(const CoreMissingGeoAssets());
|
||||
}
|
||||
|
||||
@@ -1,168 +0,0 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dartx/dartx_io.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:fpdart/fpdart.dart';
|
||||
import 'package:hiddify/data/local/dao/dao.dart';
|
||||
import 'package:hiddify/data/repository/exception_handlers.dart';
|
||||
import 'package:hiddify/domain/rules/geo_asset.dart';
|
||||
import 'package:hiddify/domain/rules/geo_asset_failure.dart';
|
||||
import 'package:hiddify/domain/rules/geo_assets_repository.dart';
|
||||
import 'package:hiddify/services/files_editor_service.dart';
|
||||
import 'package:hiddify/utils/custom_loggers.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
import 'package:watcher/watcher.dart';
|
||||
|
||||
class GeoAssetsRepositoryImpl
|
||||
with ExceptionHandler, InfraLogger
|
||||
implements GeoAssetsRepository {
|
||||
GeoAssetsRepositoryImpl({
|
||||
required this.geoAssetsDao,
|
||||
required this.dio,
|
||||
required this.filesEditor,
|
||||
});
|
||||
|
||||
final GeoAssetsDao geoAssetsDao;
|
||||
final Dio dio;
|
||||
final FilesEditorService filesEditor;
|
||||
|
||||
@override
|
||||
TaskEither<GeoAssetFailure, ({GeoAsset geoip, GeoAsset geosite})>
|
||||
getActivePair() {
|
||||
return exceptionHandler(
|
||||
() async {
|
||||
final geoip = await geoAssetsDao.getActive(GeoAssetType.geoip);
|
||||
final geosite = await geoAssetsDao.getActive(GeoAssetType.geosite);
|
||||
if (geoip == null || geosite == null) {
|
||||
return left(const GeoAssetFailure.activeAssetNotFound());
|
||||
}
|
||||
return right((geoip: geoip, geosite: geosite));
|
||||
},
|
||||
GeoAssetFailure.unexpected,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<Either<GeoAssetFailure, List<GeoAssetWithFileSize>>> watchAll() {
|
||||
final persistedStream = geoAssetsDao.watchAll();
|
||||
final filesStream = _watchGeoFiles();
|
||||
|
||||
return Rx.combineLatest2(
|
||||
persistedStream,
|
||||
filesStream,
|
||||
(assets, files) => assets.map(
|
||||
(e) {
|
||||
final path = filesEditor.geoAssetPath(e.providerName, e.fileName);
|
||||
final file = files.firstOrNullWhere((e) => e.path == path);
|
||||
final stat = file?.statSync();
|
||||
return (e, stat?.size);
|
||||
},
|
||||
).toList(),
|
||||
).handleExceptions(GeoAssetUnexpectedFailure.new);
|
||||
}
|
||||
|
||||
Iterable<File> _geoFiles = [];
|
||||
Stream<Iterable<File>> _watchGeoFiles() async* {
|
||||
yield await _readGeoFiles();
|
||||
yield* Watcher(
|
||||
filesEditor.geoAssetsDir.path,
|
||||
pollingDelay: const Duration(seconds: 1),
|
||||
).events.asyncMap((event) async {
|
||||
await _readGeoFiles();
|
||||
return _geoFiles;
|
||||
});
|
||||
}
|
||||
|
||||
Future<Iterable<File>> _readGeoFiles() async {
|
||||
return _geoFiles = Directory(filesEditor.geoAssetsDir.path)
|
||||
.listSync()
|
||||
.whereType<File>()
|
||||
.where((e) => e.extension == '.db');
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<GeoAssetFailure, Unit> update(GeoAsset geoAsset) {
|
||||
return exceptionHandler(
|
||||
() async {
|
||||
loggy.debug(
|
||||
"checking latest release of [${geoAsset.name}] on [${geoAsset.repositoryUrl}]",
|
||||
);
|
||||
final response = await dio.get<Map>(geoAsset.repositoryUrl);
|
||||
if (response.statusCode != 200 || response.data == null) {
|
||||
return left(
|
||||
GeoAssetFailure.unexpected("invalid response", StackTrace.current),
|
||||
);
|
||||
}
|
||||
|
||||
final path =
|
||||
filesEditor.geoAssetPath(geoAsset.providerName, geoAsset.name);
|
||||
final tagName = response.data!['tag_name'] as String;
|
||||
loggy.debug("latest release of [${geoAsset.name}]: [$tagName]");
|
||||
if (tagName == geoAsset.version && await File(path).exists()) {
|
||||
await geoAssetsDao.edit(geoAsset.copyWith(lastCheck: DateTime.now()));
|
||||
return left(const GeoAssetFailure.noUpdateAvailable());
|
||||
}
|
||||
|
||||
final assets = (response.data!['assets'] as List)
|
||||
.whereType<Map<String, dynamic>>();
|
||||
final asset =
|
||||
assets.firstOrNullWhere((e) => e["name"] == geoAsset.name);
|
||||
if (asset == null) {
|
||||
return left(
|
||||
GeoAssetFailure.unexpected(
|
||||
"couldn't find [${geoAsset.name}] on [${geoAsset.repositoryUrl}]",
|
||||
StackTrace.current,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final downloadUrl = asset["browser_download_url"] as String;
|
||||
loggy.debug("[${geoAsset.name}] download url: [$downloadUrl]");
|
||||
final tempPath = "$path.tmp";
|
||||
await File(path).parent.create(recursive: true);
|
||||
await dio.download(downloadUrl, tempPath);
|
||||
await File(tempPath).rename(path);
|
||||
|
||||
await geoAssetsDao.edit(
|
||||
geoAsset.copyWith(
|
||||
version: tagName,
|
||||
lastCheck: DateTime.now(),
|
||||
),
|
||||
);
|
||||
|
||||
return right(unit);
|
||||
},
|
||||
GeoAssetFailure.unexpected,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<GeoAssetFailure, Unit> markAsActive(GeoAsset geoAsset) {
|
||||
return exceptionHandler(
|
||||
() async {
|
||||
await geoAssetsDao.edit(geoAsset.copyWith(active: true));
|
||||
return right(unit);
|
||||
},
|
||||
GeoAssetFailure.unexpected,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<GeoAssetFailure, Unit> addRecommended() {
|
||||
return exceptionHandler(
|
||||
() async {
|
||||
final persistedIds = await geoAssetsDao
|
||||
.watchAll()
|
||||
.first
|
||||
.then((value) => value.map((e) => e.id));
|
||||
final missing =
|
||||
recommendedGeoAssets.where((e) => !persistedIds.contains(e.id));
|
||||
for (final geoAsset in missing) {
|
||||
await geoAssetsDao.add(geoAsset);
|
||||
}
|
||||
return right(unit);
|
||||
},
|
||||
GeoAssetFailure.unexpected,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:fpdart/fpdart.dart';
|
||||
import 'package:hiddify/data/local/dao/dao.dart';
|
||||
import 'package:hiddify/data/local/dao/profiles_dao.dart';
|
||||
import 'package:hiddify/data/repository/exception_handlers.dart';
|
||||
import 'package:hiddify/domain/enums.dart';
|
||||
import 'package:hiddify/domain/profiles/profiles.dart';
|
||||
|
||||
Reference in New Issue
Block a user