import 'dart:async'; import 'dart:io'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart' as path_provider; import 'package:sqlite3/sqlite3.dart'; import 'package:uuid/uuid.dart'; import 'package:weblibre/domain/entities/profile.dart'; import 'package:weblibre/utils/filesystem.dart' as fs; final filesystem = _Filesystem(); class _Filesystem { late final Directory dataDir; late final Directory profilesDir; late final UuidValue selectedProfile; late final Directory selectedProfileDir; late final Directory profileDatabasesDir; late final String relativeProfilePath; Future> getAvailableProfileDirectories() { return profilesDir.list().transform(fs.profileTransformer).toList(); } Future readProfileMetadata(Directory profileDir) { return fs.readProfileMetadata(profileDir); } Directory getProfileDir(UuidValue uuid) { return fs.getProfileDir(profilesDir, uuid); } Future createNewProfile(Profile profile) { return fs.createNewProfile(profilesDir, profile); } Future updateProfileMetadata(Profile profile) { return fs.writeProfileMetadata(getProfileDir(profile.uuidValue), profile); } Future setStartupProfile(UuidValue profile) { return fs.writeStartupProfile(profilesDir, profile, flush: true); } Future _linkMozillaDir(Directory filesDir) async { final mozillaDir = Directory(p.join(selectedProfileDir.path, 'mozilla')); await mozillaDir.create(); final mozillaLink = Link(p.join(filesDir.path, 'mozilla')); if (await mozillaLink.exists()) { await mozillaLink.delete(); } await mozillaLink.create(mozillaDir.path); } Future _setupSqliteCache() async { // Make sqlite3 pick a more suitable location for temporary files - the // one from the system may be inaccessible due to sandboxing. final cachebase = (await path_provider.getTemporaryDirectory()).path; // We can't access /tmp on Android, which sqlite3 would try by default. // Explicitly tell it about the correct temporary directory. sqlite3.tempDirectory = cachebase; } Future _copyDirectory( Directory source, Directory destination, bool Function(FileSystemEntity e) filter, ) async { // Create destination directory await destination.create(recursive: true); // List all contents await for (final entity in source.list().where(filter)) { final newPath = p.join(destination.path, p.basename(entity.path)); if (entity is Directory) { // Recursively copy subdirectory await _copyDirectory(entity, Directory(newPath), filter); } else if (entity is File) { // Copy file await entity.copy(newPath); } else if (entity is Link) { // Copy link await Link(newPath).create(await entity.target()); } } } Future init() async { final filesDir = await path_provider.getApplicationSupportDirectory(); dataDir = filesDir.parent; profilesDir = Directory(p.join(filesDir.path, fs.profilesDirName)); await profilesDir.create(); final selectedProfile = await fs.selectStartupProfile(profilesDir); if (selectedProfile == null) { final defaultProfile = Profile.create(name: 'Default'); if (!await fs.createNewProfile(profilesDir, defaultProfile)) { throw Exception('Unable to create default profile'); } this.selectedProfile = defaultProfile.uuidValue; await fs.writeStartupProfile(profilesDir, defaultProfile.uuidValue); final mozillaDir = Directory(p.join(filesDir.path, 'mozilla')); if (await mozillaDir.exists()) { final type = await FileSystemEntity.type(mozillaDir.path); if (type != FileSystemEntityType.link) { await _migrate(defaultProfile, mozillaDir, filesDir); } } } else { this.selectedProfile = selectedProfile; } relativeProfilePath = p.join( fs.profilesDirName, '${fs.profileDirPrefix}${this.selectedProfile.uuid}', ); selectedProfileDir = Directory(p.join(filesDir.path, relativeProfilePath)); await selectedProfileDir.create(); profileDatabasesDir = Directory( p.join(selectedProfileDir.path, 'databases'), ); await profileDatabasesDir.create(); await _linkMozillaDir(filesDir); await _setupSqliteCache(); } Future _migrate( Profile defaultProfile, Directory mozillaDir, Directory filesDir, ) async { final profileDir = getProfileDir(defaultProfile.uuidValue); final newMozillaDir = Directory(p.join(profileDir.path, 'mozilla')); await newMozillaDir.create(); await mozillaDir.rename(newMozillaDir.path); await _copyDirectory( filesDir, Directory(p.join(profileDir.path, 'files')), (e) => e is! Directory || p.basename(e.path) != fs.profilesDirName, ); final profileDatabasesDir = Directory(p.join(profileDir.path, 'databases')); await _copyDirectory( Directory(p.join(dataDir.path, 'databases')), profileDatabasesDir, (e) => true, ); final dbFolder = await path_provider.getApplicationDocumentsDirectory(); final bangDb = File(p.join(dbFolder.path, 'bang3.db')); await bangDb.copy(p.join(profileDatabasesDir.path, 'bang.db')); final feedDb = File(p.join(dbFolder.path, 'feed.db')); await feedDb.copy(p.join(profileDatabasesDir.path, 'feed.db')); final tabDb = File(p.join(dbFolder.path, 'tab2.db')); await tabDb.copy(p.join(profileDatabasesDir.path, 'tab.db')); final userDb = File(p.join(dbFolder.path, 'user.db')); await userDb.copy(p.join(profileDatabasesDir.path, 'user.db')); } }