storage permission

potentially fixes #430
This commit is contained in:
NBA2K1 2025-07-16 18:21:04 +02:00
parent 18d6079420
commit 5a18d5f0f0
6 changed files with 127 additions and 82 deletions

View file

@ -58,21 +58,20 @@ void main(List<String> args) async {
);
}
}
isar = await StorageProvider().initDB(null, inspector: kDebugMode);
if (Platform.isAndroid || Platform.isIOS || Platform.isMacOS) {
await Hive.initFlutter("databases");
} else {
await Hive.initFlutter(p.join("Mangayomi", "databases"));
}
Hive.registerAdapter(TrackSearchAdapter());
final storage = StorageProvider();
await storage.requestPermission();
isar = await storage.initDB(null, inspector: kDebugMode);
runApp(const ProviderScope(child: MyApp()));
unawaited(_postLaunchInit()); // Defer non-essential async operations
unawaited(_postLaunchInit(storage)); // Defer non-essential async operations
}
Future<void> _postLaunchInit() async {
await StorageProvider().requestPermission();
await StorageProvider().deleteBtDirectory();
Future<void> _postLaunchInit(StorageProvider storage) async {
final hivePath = (Platform.isIOS || Platform.isMacOS || Platform.isAndroid)
? "databases"
: p.join("Mangayomi", "databases");
await Hive.initFlutter(hivePath);
Hive.registerAdapter(TrackSearchAdapter());
await storage.deleteBtDirectory();
}
class MyApp extends ConsumerStatefulWidget {

View file

@ -85,7 +85,7 @@ Future<void> downloadChapter(
chapter,
mangaMainDirectory: mangaMainDirectory,
))!;
await Directory(chapterDirectory.path).create(recursive: true);
await storageProvider.createDirectorySafely(chapterDirectory.path);
Map<String, String> videoHeader = {};
Map<String, String> htmlHeader = {
"Priority": "u=0, i",
@ -258,6 +258,7 @@ Future<void> downloadChapter(
!mp4FileExist && itemType == ItemType.anime ||
!htmlFileExist && itemType == ItemType.novel) {
final mainDirectory = (await storageProvider.getDirectory())!;
storageProvider.createDirectorySafely(mainDirectory.path);
for (var index = 0; index < pageUrls.length; index++) {
if (Platform.isAndroid) {
if (!(await File(p.join(mainDirectory.path, ".nomedia")).exists())) {

View file

@ -73,42 +73,35 @@ class AutoBackupLocationState extends _$AutoBackupLocationState {
@riverpod
Future<void> checkAndBackup(Ref ref) async {
final settings = isar.settings.getSync(227);
if (settings!.backupFrequency != null) {
final backupFrequency = _duration(settings.backupFrequency);
if (backupFrequency != null) {
if (settings.startDatebackup != null) {
final startDatebackup = DateTime.fromMillisecondsSinceEpoch(
settings.startDatebackup!,
);
if (DateTime.now().isAfter(startDatebackup)) {
_setBackupFrequency(settings.backupFrequency!);
final storageProvider = StorageProvider();
await storageProvider.requestPermission();
final defaulteDirectory = await storageProvider.getDefaultDirectory();
final backupLocation = ref.watch(autoBackupLocationStateProvider).$2;
Directory? backupDirectory;
backupDirectory = Directory(
backupLocation.isEmpty
? p.join(defaulteDirectory!.path, "backup")
: backupLocation,
);
if (Platform.isIOS) {
backupDirectory = await (storageProvider.getIosBackupDirectory());
}
if (!(await backupDirectory!.exists())) {
backupDirectory.create();
}
ref.watch(
doBackUpProvider(
list: ref.watch(backupFrequencyOptionsStateProvider),
path: backupDirectory.path,
context: null,
),
);
}
}
}
final backupFrequency = _duration(settings!.backupFrequency);
if (backupFrequency == null || settings.startDatebackup == null) return;
final startDatebackup = DateTime.fromMillisecondsSinceEpoch(
settings.startDatebackup!,
);
if (!DateTime.now().isAfter(startDatebackup)) return;
_setBackupFrequency(settings.backupFrequency!);
final storageProvider = StorageProvider();
final backupLocation = ref.read(autoBackupLocationStateProvider).$2;
Directory? backupDirectory;
if (Platform.isIOS) {
backupDirectory = await (storageProvider.getIosBackupDirectory());
} else {
final defaultDirectory = await storageProvider.getDefaultDirectory();
backupDirectory = Directory(
backupLocation.isEmpty
? p.join(defaultDirectory!.path, "backup")
: backupLocation,
);
}
await storageProvider.createDirectorySafely(backupDirectory!.path);
ref.read(
doBackUpProvider(
list: ref.read(backupFrequencyOptionsStateProvider),
path: backupDirectory.path,
context: null,
),
);
}
Duration? _duration(int? backupFrequency) {

View file

@ -7,6 +7,7 @@ import 'package:extended_image_library/src/platform.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:http_client_helper/http_client_helper.dart';
import 'package:mangayomi/providers/storage_provider.dart';
import 'package:mangayomi/services/http/m_client.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
@ -194,6 +195,7 @@ class CustomExtendedNetworkImageProvider
),
);
Uint8List? data;
await StorageProvider().createDirectorySafely(cacheImagesDirectory.path);
final File cacheFile = File(join(cacheImagesDirectory.path, md5Key));
// exist, try to find cache image file
if (cacheFile.existsSync()) {
@ -208,8 +210,6 @@ class CustomExtendedNetworkImageProvider
} else {
data = await cacheFile.readAsBytes();
}
} else if (!cacheImagesDirectory.existsSync()) {
await cacheImagesDirectory.create(recursive: true);
}
// load from network
if (data == null) {

View file

@ -1,5 +1,6 @@
// ignore_for_file: depend_on_referenced_packages
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:isar/isar.dart';
import 'package:mangayomi/eval/model/source_preference.dart';
import 'package:mangayomi/main.dart';
@ -21,13 +22,15 @@ import 'package:permission_handler/permission_handler.dart';
import 'package:path/path.dart' as path;
class StorageProvider {
static bool _hasPermission = false;
static final StorageProvider _instance = StorageProvider._internal();
StorageProvider._internal();
factory StorageProvider() => _instance;
Future<bool> requestPermission() async {
if (_hasPermission || !Platform.isAndroid) return true;
if (!Platform.isAndroid) return true;
Permission permission = Permission.manageExternalStorage;
if (await permission.isGranted) return true;
if (await permission.request().isGranted) {
_hasPermission = true;
return true;
}
return false;
@ -46,9 +49,13 @@ class StorageProvider {
Future<Directory?> getDefaultDirectory() async {
Directory? directory;
if (Platform.isAndroid) {
directory = Directory("/storage/emulated/0/Mangayomi/");
final dir = await getExternalStorageDirectory();
directory = Directory(path.join(dir!.path, 'Mangayomi'));
} else {
final dir = await getApplicationDocumentsDirectory();
// The documents dir in iOS and macOS is already named "Mangayomi".
// Appending "Mangayomi" to the documents dir would create
// unnecessarily nested Mangayomi/Mangayomi/ folder.
if (Platform.isIOS || Platform.isMacOS) return dir;
directory = Directory(path.join(dir.path, 'Mangayomi'));
}
@ -56,8 +63,8 @@ class StorageProvider {
}
Future<Directory?> getBtDirectory() async {
String dbDir = await _btDirectoryPath();
await Directory(dbDir).create(recursive: true);
final dbDir = await _btDirectoryPath();
await createDirectorySafely(dbDir);
return Directory(dbDir);
}
@ -67,8 +74,8 @@ class StorageProvider {
}
Future<Directory?> getTmpDirectory() async {
String tmpPath = await _tempDirectoryPath();
await Directory(tmpPath).create(recursive: true);
final tmpPath = await _tempDirectoryPath();
await createDirectorySafely(tmpPath);
return Directory(tmpPath);
}
@ -80,20 +87,30 @@ class StorageProvider {
Future<Directory?> getIosBackupDirectory() async {
final defaultDirectory = await getDefaultDirectory();
String dbDir = path.join(defaultDirectory!.path, 'backup');
await Directory(dbDir).create(recursive: true);
await createDirectorySafely(dbDir);
return Directory(dbDir);
}
Future<Directory?> getDirectory() async {
Directory? directory;
String dPath = isar.settings.getSync(227)!.downloadLocation ?? "";
String dPath = "";
try {
final setting = isar.settings.getSync(227);
dPath = setting?.downloadLocation ?? "";
} catch (e) {
debugPrint("Could not get downloadLocation from Isar settings: $e");
}
if (Platform.isAndroid) {
final dir = await getExternalStorageDirectory();
directory = Directory(
dPath.isEmpty ? "/storage/emulated/0/Mangayomi/" : "$dPath/",
dPath.isEmpty ? path.join(dir!.path, 'Mangayomi') : "$dPath/",
);
} else {
final dir = await getApplicationDocumentsDirectory();
final p = dPath.isEmpty ? dir.path : dPath;
// The documents dir in iOS and macOS is already named "Mangayomi".
// Appending "Mangayomi" to the documents dir would create
// unnecessarily nested Mangayomi/Mangayomi/ folder.
if (Platform.isIOS || Platform.isMacOS) return Directory(p);
directory = Directory(path.join(p, 'Mangayomi'));
}
@ -138,28 +155,48 @@ class StorageProvider {
Future<Directory?> getDatabaseDirectory() async {
final dir = await getApplicationDocumentsDirectory();
String dbDir;
if (Platform.isAndroid || Platform.isIOS || Platform.isMacOS) {
String dbDir = path.join(dir.path, 'databases');
await Directory(dbDir).create(recursive: true);
return Directory(dbDir);
// The documents dir in iOS and macOS is already named "Mangayomi".
// Appending "Mangayomi" to the documents dir would create
// unnecessarily nested Mangayomi/Mangayomi/ folder.
dbDir = path.join(dir.path, 'databases');
} else {
String dbDir = path.join(dir.path, 'Mangayomi', 'databases');
await Directory(dbDir).create(recursive: true);
return Directory(dbDir);
dbDir = path.join(dir.path, 'Mangayomi', 'databases');
}
await createDirectorySafely(dbDir);
return Directory(dbDir);
}
Future<Directory?> getGalleryDirectory() async {
String gPath = (await getDirectory())!.path;
String gPath;
if (Platform.isAndroid) {
gPath = "/storage/emulated/0/Pictures/Mangayomi/";
final dir = await getExternalStorageDirectory();
gPath = path.join(dir!.path, 'Mangayomi', 'Pictures');
} else {
gPath = path.join(gPath, 'Pictures');
gPath = path.join((await getDirectory())!.path, 'Pictures');
}
await Directory(gPath).create(recursive: true);
await createDirectorySafely(gPath);
return Directory(gPath);
}
Future<void> createDirectorySafely(String dirPath) async {
final dir = Directory(dirPath);
try {
await dir.create(recursive: true);
} catch (_) {
if (await requestPermission()) {
try {
await dir.create(recursive: true);
} catch (e) {
debugPrint('Initial directory creation failed for $dirPath: $e');
}
} else {
debugPrint('Permission denied. Cannot create: $dirPath');
}
}
}
Future<Isar> initDB(String? path, {bool inspector = false}) async {
Directory? dir;
if (path == null) {
@ -189,10 +226,27 @@ class StorageProvider {
name: "mangayomiDb",
inspector: inspector,
);
final settings = await isar.settings.filter().idEqualTo(227).findFirst();
if (settings == null) {
await isar.writeTxn(() async => isar.settings.put(Settings()));
try {
final settings = await isar.settings.filter().idEqualTo(227).findFirst();
if (settings == null) {
await isar.writeTxn(() async => isar.settings.put(Settings()));
}
} catch (_) {
if (await requestPermission()) {
try {
final settings = await isar.settings
.filter()
.idEqualTo(227)
.findFirst();
if (settings == null) {
await isar.writeTxn(() async => isar.settings.put(Settings()));
}
} catch (e) {
debugPrint("Failed after retry with permission: $e");
}
} else {
debugPrint("Permission denied during Database init fallback.");
}
}
return isar;

View file

@ -7,6 +7,7 @@ import 'package:flutter/foundation.dart';
import 'package:http/http.dart';
import 'package:mangayomi/models/chapter.dart';
import 'package:mangayomi/models/video.dart';
import 'package:mangayomi/providers/storage_provider.dart';
import 'package:mangayomi/services/http/m_client.dart';
import 'package:mangayomi/services/http/rhttp/src/model/settings.dart';
import 'package:mangayomi/services/download_manager/m3u8/models/download.dart';
@ -122,21 +123,18 @@ class M3u8Downloader {
}
Future<void> download(void Function(DownloadProgress) onProgress) async {
final tempDir = Directory(path.join(downloadDir, 'temp'));
final tempDir = path.join(downloadDir, 'temp');
await StorageProvider().createDirectorySafely(tempDir);
try {
await tempDir.create(recursive: true);
final (tsList, key, iv, mediaSequence) = await _getTsList();
final tsListToDownload = await _filterExistingSegments(
tsList,
tempDir.path,
);
final tsListToDownload = await _filterExistingSegments(tsList, tempDir);
_log('Downloading ${tsListToDownload.length} segments...');
await _downloadSegmentsWithProgress(
tsListToDownload,
tempDir.path,
tempDir,
key,
iv,
mediaSequence,