From ba49e4e75d3c86aef5c2213c1c4969bc99cd4a78 Mon Sep 17 00:00:00 2001 From: NBA2K1 <78034913+NBA2K1@users.noreply.github.com> Date: Sat, 12 Jul 2025 05:28:25 +0200 Subject: [PATCH 01/13] Put the hive database into the app's folder On Windows and Linux the files were created in the ~/Documents folder. Now they will be created in ~/Documents/Mangayomi/databases instead --- lib/main.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/main.dart b/lib/main.dart index 0ecc6222..ae521ec6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -59,7 +59,11 @@ void main(List args) async { } } isar = await StorageProvider().initDB(null, inspector: kDebugMode); - await Hive.initFlutter(); + if (Platform.isAndroid || Platform.isIOS || Platform.isMacOS) { + await Hive.initFlutter("databases"); + } else { + await Hive.initFlutter(p.join("Mangayomi", "databases")); + } Hive.registerAdapter(TrackSearchAdapter()); runApp(const ProviderScope(child: MyApp())); From 4d564b93088a961edc7ed76bf11abd3b3127e3d7 Mon Sep 17 00:00:00 2001 From: NBA2K1 <78034913+NBA2K1@users.noreply.github.com> Date: Sat, 12 Jul 2025 05:30:26 +0200 Subject: [PATCH 02/13] Put the database into its separate folder On Android, iOS and macOS just like how it's done on Windows and Linux. Before the database files were in the top directory. --- lib/providers/storage_provider.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/providers/storage_provider.dart b/lib/providers/storage_provider.dart index f7b2dfe6..31d0e18d 100644 --- a/lib/providers/storage_provider.dart +++ b/lib/providers/storage_provider.dart @@ -136,7 +136,9 @@ class StorageProvider { Future getDatabaseDirectory() async { final dir = await getApplicationDocumentsDirectory(); if (Platform.isAndroid || Platform.isIOS || Platform.isMacOS) { - return dir; + String dbDir = path.join(dir.path, 'databases'); + await Directory(dbDir).create(recursive: true); + return Directory(dbDir); } else { String dbDir = path.join(dir.path, 'Mangayomi', 'databases'); await Directory(dbDir).create(recursive: true); From 5e43cf0468cdb88041165a88de65b21b9f347633 Mon Sep 17 00:00:00 2001 From: NBA2K1 <78034913+NBA2K1@users.noreply.github.com> Date: Sat, 12 Jul 2025 05:34:18 +0200 Subject: [PATCH 03/13] Do not create Mangayomi folder inside AppDir macOS and iOS already create a separate "Mangayomi" folder. There is no need to nest the downloads and backup folders in another "Mangayomi" folder. --- lib/providers/storage_provider.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/providers/storage_provider.dart b/lib/providers/storage_provider.dart index 31d0e18d..e18cda02 100644 --- a/lib/providers/storage_provider.dart +++ b/lib/providers/storage_provider.dart @@ -56,6 +56,7 @@ class StorageProvider { directory = Directory("/storage/emulated/0/Mangayomi/"); } else { final dir = await getApplicationDocumentsDirectory(); + if (Platform.isIOS || Platform.isMacOS) return dir; directory = Directory(path.join(dir.path, 'Mangayomi')); } return directory; @@ -92,6 +93,7 @@ class StorageProvider { } else { final dir = await getApplicationDocumentsDirectory(); final p = dPath.isEmpty ? dir.path : dPath; + if (Platform.isIOS || Platform.isMacOS) return Directory(p); directory = Directory(path.join(p, 'Mangayomi')); } return directory; From 609e8196db89367010aa14f074b8a340a008466a Mon Sep 17 00:00:00 2001 From: NBA2K1 <78034913+NBA2K1@users.noreply.github.com> Date: Sat, 12 Jul 2025 05:35:21 +0200 Subject: [PATCH 04/13] Update storage_provider.dart to make the requestPermission() a little shorter. It's just optics. The logic stays the same. --- lib/providers/storage_provider.dart | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/lib/providers/storage_provider.dart b/lib/providers/storage_provider.dart index e18cda02..a2aa368b 100644 --- a/lib/providers/storage_provider.dart +++ b/lib/providers/storage_provider.dart @@ -23,21 +23,14 @@ import 'package:path/path.dart' as path; class StorageProvider { static bool _hasPermission = false; Future requestPermission() async { - if (_hasPermission) return true; - if (Platform.isAndroid) { - Permission permission = Permission.manageExternalStorage; - if (await permission.isGranted) { - return true; - } else { - final result = await permission.request(); - if (result == PermissionStatus.granted) { - _hasPermission = true; - return true; - } - return false; - } + if (_hasPermission || !Platform.isAndroid) return true; + Permission permission = Permission.manageExternalStorage; + if (await permission.isGranted) return true; + if (await permission.request().isGranted) { + _hasPermission = true; + return true; } - return true; + return false; } Future deleteBtDirectory() async { @@ -191,9 +184,7 @@ class StorageProvider { final settings = await isar.settings.filter().idEqualTo(227).findFirst(); if (settings == null) { - await isar.writeTxn(() async { - isar.settings.put(Settings()); - }); + await isar.writeTxn(() async => isar.settings.put(Settings())); } return isar; From 1f423dd3ca4b7eed219e406da7b1fdaed4bbe584 Mon Sep 17 00:00:00 2001 From: NBA2K1 <78034913+NBA2K1@users.noreply.github.com> Date: Sat, 12 Jul 2025 06:05:52 +0200 Subject: [PATCH 05/13] typo --- lib/providers/storage_provider.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/providers/storage_provider.dart b/lib/providers/storage_provider.dart index a2aa368b..118d3241 100644 --- a/lib/providers/storage_provider.dart +++ b/lib/providers/storage_provider.dart @@ -56,22 +56,22 @@ class StorageProvider { } Future getBtDirectory() async { - final gefaultDirectory = await getDefaultDirectory(); - String dbDir = path.join(gefaultDirectory!.path, 'torrents'); + final defaultDirectory = await getDefaultDirectory(); + String dbDir = path.join(defaultDirectory!.path, 'torrents'); await Directory(dbDir).create(recursive: true); return Directory(dbDir); } Future getTmpDirectory() async { - final gefaultDirectory = await getDirectory(); - String dbDir = path.join(gefaultDirectory!.path, 'tmp'); + final defaultDirectory = await getDirectory(); + String dbDir = path.join(defaultDirectory!.path, 'tmp'); await Directory(dbDir).create(recursive: true); return Directory(dbDir); } Future getIosBackupDirectory() async { - final gefaultDirectory = await getDefaultDirectory(); - String dbDir = path.join(gefaultDirectory!.path, 'backup'); + final defaultDirectory = await getDefaultDirectory(); + String dbDir = path.join(defaultDirectory!.path, 'backup'); await Directory(dbDir).create(recursive: true); return Directory(dbDir); } From 7d13a80a11bd3a9d32accc1611edbd69e09bd028 Mon Sep 17 00:00:00 2001 From: NBA2K1 <78034913+NBA2K1@users.noreply.github.com> Date: Sat, 12 Jul 2025 06:38:57 +0200 Subject: [PATCH 06/13] No need to make inspector nullable why make it nullable, set a default value and then use a non-null-assertion (!)? --- lib/providers/storage_provider.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/providers/storage_provider.dart b/lib/providers/storage_provider.dart index 118d3241..28b0a35a 100644 --- a/lib/providers/storage_provider.dart +++ b/lib/providers/storage_provider.dart @@ -152,7 +152,7 @@ class StorageProvider { return Directory(gPath); } - Future initDB(String? path, {bool? inspector = false}) async { + Future initDB(String? path, {bool inspector = false}) async { Directory? dir; if (path == null) { dir = await getDatabaseDirectory(); @@ -179,7 +179,7 @@ class StorageProvider { ], directory: dir!.path, name: "mangayomiDb", - inspector: inspector!, + inspector: inspector, ); final settings = await isar.settings.filter().idEqualTo(227).findFirst(); From eb4f82f040d94016e1617c58a18f746b5b887dea Mon Sep 17 00:00:00 2001 From: NBA2K1 <78034913+NBA2K1@users.noreply.github.com> Date: Sat, 12 Jul 2025 06:41:21 +0200 Subject: [PATCH 07/13] Why create and then delete TpmDirectory? When deleteTmpDirectory() calls getTmpDirectory(), the directory is first being created and then it is being deleted. Redundant operation. --- lib/providers/storage_provider.dart | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/providers/storage_provider.dart b/lib/providers/storage_provider.dart index 28b0a35a..3dc83085 100644 --- a/lib/providers/storage_provider.dart +++ b/lib/providers/storage_provider.dart @@ -39,8 +39,8 @@ class StorageProvider { } Future deleteTmpDirectory() async { - final d = await getTmpDirectory(); - await Directory(d!.path).delete(recursive: true); + final tmpDir = Directory(await _tempDirectoryPath()); + if (await tmpDir.exists()) await tmpDir.delete(recursive: true); } Future getDefaultDirectory() async { @@ -63,10 +63,14 @@ class StorageProvider { } Future getTmpDirectory() async { + String tmpPath = await _tempDirectoryPath(); + await Directory(tmpPath).create(recursive: true); + return Directory(tmpPath); + } + + Future _tempDirectoryPath() async { final defaultDirectory = await getDirectory(); - String dbDir = path.join(defaultDirectory!.path, 'tmp'); - await Directory(dbDir).create(recursive: true); - return Directory(dbDir); + return path.join(defaultDirectory!.path, 'tmp'); } Future getIosBackupDirectory() async { From 18d6079420ff30b3ae65fd125d04431e1a95b89c Mon Sep 17 00:00:00 2001 From: NBA2K1 <78034913+NBA2K1@users.noreply.github.com> Date: Sat, 12 Jul 2025 06:45:39 +0200 Subject: [PATCH 08/13] Why create and then delete BtDirectory? Same as previous commit --- lib/providers/storage_provider.dart | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/providers/storage_provider.dart b/lib/providers/storage_provider.dart index 3dc83085..05afad96 100644 --- a/lib/providers/storage_provider.dart +++ b/lib/providers/storage_provider.dart @@ -34,8 +34,8 @@ class StorageProvider { } Future deleteBtDirectory() async { - final d = await getBtDirectory(); - await Directory(d!.path).delete(recursive: true); + final btDir = Directory(await _btDirectoryPath()); + if (await btDir.exists()) await btDir.delete(recursive: true); } Future deleteTmpDirectory() async { @@ -56,12 +56,16 @@ class StorageProvider { } Future getBtDirectory() async { - final defaultDirectory = await getDefaultDirectory(); - String dbDir = path.join(defaultDirectory!.path, 'torrents'); + String dbDir = await _btDirectoryPath(); await Directory(dbDir).create(recursive: true); return Directory(dbDir); } + Future _btDirectoryPath() async { + final defaultDirectory = await getDefaultDirectory(); + return path.join(defaultDirectory!.path, 'torrents'); + } + Future getTmpDirectory() async { String tmpPath = await _tempDirectoryPath(); await Directory(tmpPath).create(recursive: true); From 5a18d5f0f0eea9d4630a4f927f8a9867cff03905 Mon Sep 17 00:00:00 2001 From: NBA2K1 <78034913+NBA2K1@users.noreply.github.com> Date: Wed, 16 Jul 2025 18:21:04 +0200 Subject: [PATCH 09/13] storage permission potentially fixes #430 --- lib/main.dart | 23 ++-- .../download/providers/download_provider.dart | 3 +- .../providers/auto_backup.dart | 63 +++++------ .../custom_extended_image_provider.dart | 4 +- lib/providers/storage_provider.dart | 104 +++++++++++++----- .../m3u8/m3u8_downloader.dart | 12 +- 6 files changed, 127 insertions(+), 82 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index ae521ec6..be8479ad 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -58,21 +58,20 @@ void main(List 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 _postLaunchInit() async { - await StorageProvider().requestPermission(); - await StorageProvider().deleteBtDirectory(); +Future _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 { diff --git a/lib/modules/manga/download/providers/download_provider.dart b/lib/modules/manga/download/providers/download_provider.dart index ddbc5fed..acbacbc7 100644 --- a/lib/modules/manga/download/providers/download_provider.dart +++ b/lib/modules/manga/download/providers/download_provider.dart @@ -85,7 +85,7 @@ Future downloadChapter( chapter, mangaMainDirectory: mangaMainDirectory, ))!; - await Directory(chapterDirectory.path).create(recursive: true); + await storageProvider.createDirectorySafely(chapterDirectory.path); Map videoHeader = {}; Map htmlHeader = { "Priority": "u=0, i", @@ -258,6 +258,7 @@ Future 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())) { diff --git a/lib/modules/more/data_and_storage/providers/auto_backup.dart b/lib/modules/more/data_and_storage/providers/auto_backup.dart index 2370e8de..49d7320f 100644 --- a/lib/modules/more/data_and_storage/providers/auto_backup.dart +++ b/lib/modules/more/data_and_storage/providers/auto_backup.dart @@ -73,42 +73,35 @@ class AutoBackupLocationState extends _$AutoBackupLocationState { @riverpod Future 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) { diff --git a/lib/modules/widgets/custom_extended_image_provider.dart b/lib/modules/widgets/custom_extended_image_provider.dart index 3ddb657d..90248ab0 100644 --- a/lib/modules/widgets/custom_extended_image_provider.dart +++ b/lib/modules/widgets/custom_extended_image_provider.dart @@ -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) { diff --git a/lib/providers/storage_provider.dart b/lib/providers/storage_provider.dart index 05afad96..2a612992 100644 --- a/lib/providers/storage_provider.dart +++ b/lib/providers/storage_provider.dart @@ -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 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 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 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 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 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 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 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 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 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 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; diff --git a/lib/services/download_manager/m3u8/m3u8_downloader.dart b/lib/services/download_manager/m3u8/m3u8_downloader.dart index ec5398e5..5a4537c9 100644 --- a/lib/services/download_manager/m3u8/m3u8_downloader.dart +++ b/lib/services/download_manager/m3u8/m3u8_downloader.dart @@ -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 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, From 72ac4605885618250897424b19bbcdb27b09961e Mon Sep 17 00:00:00 2001 From: NBA2K1 <78034913+NBA2K1@users.noreply.github.com> Date: Mon, 21 Jul 2025 14:55:33 +0200 Subject: [PATCH 10/13] Add TODO comment to unused variable --- lib/modules/novel/novel_reader_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/novel/novel_reader_view.dart b/lib/modules/novel/novel_reader_view.dart index 6384e778..d6b414f0 100644 --- a/lib/modules/novel/novel_reader_view.dart +++ b/lib/modules/novel/novel_reader_view.dart @@ -467,7 +467,7 @@ class _NovelWebViewState extends ConsumerState _readerController.getChapterIndex().$2, ); bool hasNextChapter = _readerController.getChapterIndex().$1 != 0; - final novelTextAlign = ref.watch(novelTextAlignStateProvider); + // final novelTextAlign = ref.watch(novelTextAlignStateProvider); // TODO. The variable is never used/modified return Positioned( bottom: 0, From 0b83be6d7a6eb2b1f77b4901fa4153e812ff5987 Mon Sep 17 00:00:00 2001 From: NBA2K1 <78034913+NBA2K1@users.noreply.github.com> Date: Thu, 24 Jul 2025 16:50:55 +0200 Subject: [PATCH 11/13] Revert Android changes --- lib/main.dart | 4 ++-- lib/providers/storage_provider.dart | 12 +++++------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index a7d325cc..9b58df13 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -68,10 +68,10 @@ void main(List args) async { } Future _postLaunchInit(StorageProvider storage) async { - final hivePath = (Platform.isIOS || Platform.isMacOS || Platform.isAndroid) + final hivePath = (Platform.isIOS || Platform.isMacOS) ? "databases" : p.join("Mangayomi", "databases"); - await Hive.initFlutter(hivePath); + await Hive.initFlutter(Platform.isAndroid ? "" : hivePath); Hive.registerAdapter(TrackSearchAdapter()); if (Platform.isMacOS || Platform.isLinux || Platform.isWindows) { discordRpc = DiscordRPC(applicationId: "1395040506677039157"); diff --git a/lib/providers/storage_provider.dart b/lib/providers/storage_provider.dart index 2a612992..39837c27 100644 --- a/lib/providers/storage_provider.dart +++ b/lib/providers/storage_provider.dart @@ -49,8 +49,7 @@ class StorageProvider { Future getDefaultDirectory() async { Directory? directory; if (Platform.isAndroid) { - final dir = await getExternalStorageDirectory(); - directory = Directory(path.join(dir!.path, 'Mangayomi')); + directory = Directory("/storage/emulated/0/Mangayomi/"); } else { final dir = await getApplicationDocumentsDirectory(); // The documents dir in iOS and macOS is already named "Mangayomi". @@ -101,9 +100,8 @@ class StorageProvider { debugPrint("Could not get downloadLocation from Isar settings: $e"); } if (Platform.isAndroid) { - final dir = await getExternalStorageDirectory(); directory = Directory( - dPath.isEmpty ? path.join(dir!.path, 'Mangayomi') : "$dPath/", + dPath.isEmpty ? "/storage/emulated/0/Mangayomi/" : "$dPath/", ); } else { final dir = await getApplicationDocumentsDirectory(); @@ -156,7 +154,8 @@ class StorageProvider { Future getDatabaseDirectory() async { final dir = await getApplicationDocumentsDirectory(); String dbDir; - if (Platform.isAndroid || Platform.isIOS || Platform.isMacOS) { + if (Platform.isAndroid) return dir; + if (Platform.isIOS || Platform.isMacOS) { // The documents dir in iOS and macOS is already named "Mangayomi". // Appending "Mangayomi" to the documents dir would create // unnecessarily nested Mangayomi/Mangayomi/ folder. @@ -171,8 +170,7 @@ class StorageProvider { Future getGalleryDirectory() async { String gPath; if (Platform.isAndroid) { - final dir = await getExternalStorageDirectory(); - gPath = path.join(dir!.path, 'Mangayomi', 'Pictures'); + gPath = "/storage/emulated/0/Pictures/Mangayomi/"; } else { gPath = path.join((await getDirectory())!.path, 'Pictures'); } From cbeb70b79e99f292bb635dbbab75a871f0998c8f Mon Sep 17 00:00:00 2001 From: NBA2K1 <78034913+NBA2K1@users.noreply.github.com> Date: Thu, 24 Jul 2025 16:58:21 +0200 Subject: [PATCH 12/13] edit comment --- lib/providers/storage_provider.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/providers/storage_provider.dart b/lib/providers/storage_provider.dart index 39837c27..78af0fc5 100644 --- a/lib/providers/storage_provider.dart +++ b/lib/providers/storage_provider.dart @@ -156,9 +156,8 @@ class StorageProvider { String dbDir; if (Platform.isAndroid) return dir; if (Platform.isIOS || Platform.isMacOS) { - // The documents dir in iOS and macOS is already named "Mangayomi". - // Appending "Mangayomi" to the documents dir would create - // unnecessarily nested Mangayomi/Mangayomi/ folder. + // Put the database files inside /databases like on Windows, Linux + // So they are not just in the app folders root dir dbDir = path.join(dir.path, 'databases'); } else { dbDir = path.join(dir.path, 'Mangayomi', 'databases'); From fa86025ed0acaa79703ab8ab9f3781d3ea70acbc Mon Sep 17 00:00:00 2001 From: NBA2K1 <78034913+NBA2K1@users.noreply.github.com> Date: Fri, 8 Aug 2025 17:46:50 +0200 Subject: [PATCH 13/13] migrate function for iOS --- lib/main.dart | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/lib/main.dart b/lib/main.dart index 9b58df13..abe838f4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -62,6 +62,7 @@ void main(List args) async { } final storage = StorageProvider(); await storage.requestPermission(); + await _migrateOldLayout(); isar = await storage.initDB(null, inspector: kDebugMode); runApp(const ProviderScope(child: MyApp())); unawaited(_postLaunchInit(storage)); // Defer non-essential async operations @@ -80,6 +81,50 @@ Future _postLaunchInit(StorageProvider storage) async { await storage.deleteBtDirectory(); } +/// This can be removed after next release (v0.6.40?) +/// It is a one-time thing to migrate the database and folders to the new +/// iOS and macOS location (PR #517) +Future _migrateOldLayout() async { + if (!(Platform.isIOS || Platform.isMacOS)) return; + final root = await getApplicationDocumentsDirectory(); + final oldRoot = Directory(p.join(root.path, 'Mangayomi')); + if (!await oldRoot.exists()) return; + final newDbDir = Directory(p.join(root.path, 'databases')); + await newDbDir.create(recursive: true); + // Move database files to new directory + for (final filename in [ + 'mangayomiDb.isar', + 'mangayomiDb.isar.lock', + 'tracker_library.hive', + 'tracker_library.lock', + ]) { + final oldFile = File(p.join(root.path, filename)); + if (await oldFile.exists()) { + final newFile = File(p.join(newDbDir.path, filename)); + await oldFile.rename(newFile.path); + } + } + // Move subfolders up a level + for (final sub in ['backup', 'downloads', 'Pictures', 'local']) { + final oldSubDir = Directory(p.join(oldRoot.path, sub)); + final newSubDir = Directory(p.join(root.path, sub)); + if (!await oldSubDir.exists()) continue; + // If by chance newSubDir is empty, safe to rename; otherwise, move contents + if (!(await newSubDir.exists())) { + await oldSubDir.rename(newSubDir.path); + } else { + // merge contents + await for (final entity in oldSubDir.list()) { + await entity.rename(p.join(newSubDir.path, p.basename(entity.path))); + } + } + // remove subfolder if empty + if (await oldSubDir.list().isEmpty) await oldSubDir.delete(); + } + // Clean up old empty folder + if (await oldRoot.list().isEmpty) await oldRoot.delete(); +} + class MyApp extends ConsumerStatefulWidget { const MyApp({super.key});