TLA Line data Source code
1 : // Copyright (c) 2026 Flipdare Pty Ltd. All rights reserved.
2 : //
3 : // This file is part of Flipdare's proprietary software and contains
4 : // confidential and copyrighted material. Unauthorised copying,
5 : // modification, distribution, or use of this file is strictly
6 : // prohibited without prior written permission from Flipdare Pty Ltd.
7 : //
8 : // This software includes third-party components licensed under MIT,
9 : // BSD, and Apache 2.0 licences. See THIRD_PARTY_NOTICES for details.
10 : //
11 :
12 : import 'dart:io' show Directory;
13 :
14 : import 'package:core/core.dart';
15 : import 'package:core/typedefs.dart';
16 : import 'package:cross_file/cross_file.dart';
17 : import 'package:meta/meta.dart';
18 : import 'package:path_provider/path_provider.dart';
19 : import 'package:path/path.dart' as path;
20 :
21 : enum LocalDirectoryType {
22 : temp('temp'),
23 : persistent('persistent');
24 :
25 : final String name;
26 : const LocalDirectoryType(this.name);
27 : }
28 :
29 : /// NOTE: the cache can fallback to persistent if needed,
30 : /// NOTE: but persistent cannot fallback to cache.
31 : /// For persistent:
32 : /// - prefer external if available
33 : /// - else app directory
34 : /// For cache:
35 : /// - prefer temp directory
36 : /// - else system temp directory
37 : class LocalDirectory {
38 : final LocalDirectoryType type;
39 :
40 : final DirCallback? _getAndroidAppDocDir;
41 : final DirCallback? _getAppDocDir;
42 : final DirCallback? _getTempDir;
43 : final MaybeDirCallback? _getExtDir;
44 :
45 : final PermissionCheckInterface perms;
46 :
47 : @visibleForTesting
48 : final bool useSystemTempFallback;
49 :
50 MIS 0 : factory LocalDirectory.create(
51 : LocalDirectoryType type, {
52 : required PermissionCheckInterface perms,
53 : }) {
54 0 : return type == LocalDirectoryType.temp
55 0 : ? LocalDirectory.temp(perms: perms)
56 0 : : LocalDirectory.persistent(perms: perms);
57 : }
58 :
59 0 : const LocalDirectory.temp({required this.perms})
60 : : _getTempDir = getTemporaryDirectory, // cache
61 : useSystemTempFallback = true, // cache
62 : _getAndroidAppDocDir = getApplicationDocumentsDirectory, // persistent
63 : _getAppDocDir = getLibraryDirectory, // persistent
64 : _getExtDir = getExternalStorageDirectory, // persistent
65 : type = LocalDirectoryType.temp;
66 :
67 0 : const LocalDirectory.persistent({required this.perms})
68 : : _getAndroidAppDocDir = getApplicationDocumentsDirectory, // persistent
69 : _getAppDocDir = getLibraryDirectory, // persistent
70 : _getExtDir = getExternalStorageDirectory, // persistent
71 : _getTempDir = null,
72 : useSystemTempFallback = false,
73 : type = LocalDirectoryType.persistent;
74 :
75 : // for testing
76 HIT 1 : const LocalDirectory._custom(
77 : this.type,
78 : this.perms,
79 : this._getAndroidAppDocDir,
80 : this._getAppDocDir,
81 : this._getTempDir,
82 : this._getExtDir,
83 : this.useSystemTempFallback,
84 : );
85 :
86 1 : Future<Directory?> getDirectory() async {
87 2 : if (!await perms.hasStoragePermission()) {
88 MIS 0 : LOG.w('No storage permission, cannot get local directory of type ${type.name}');
89 : return null;
90 : }
91 :
92 HIT 2 : if (type == LocalDirectoryType.persistent) {
93 1 : return await _getPersistent();
94 : } else {
95 1 : return await _getCache();
96 : }
97 : }
98 :
99 1 : Future<XFile?> getFile({required String suffix}) async {
100 2 : if (!await perms.hasStoragePermission()) {
101 MIS 0 : LOG.w('No storage permission, cannot create file of type ${type.name}');
102 : return null;
103 : }
104 :
105 HIT 1 : if (suffix.isEmpty) {
106 MIS 0 : LOG.e('Suffix is empty, cannot create file');
107 : return null;
108 : }
109 :
110 HIT 1 : final dir = await getDirectory();
111 : if (dir == null) {
112 MIS 0 : LOG.d('No directory available for temp file');
113 : return null;
114 : }
115 :
116 HIT 2 : final filePostfix = type.name;
117 3 : final fileName = path.join(dir.path, '${kStripePrefixSmall}_$filePostfix.$suffix');
118 3 : LOG.d('Created file path: $fileName');
119 1 : return XFile(fileName);
120 : }
121 :
122 1 : Future<Directory?> _getPersistent() async {
123 : // try external first
124 2 : Directory? dir = await _tryMaybe(_getExtDir);
125 : if (dir != null) return dir;
126 :
127 : // then app directory
128 3 : dir = await _try(kIsAndroidDevice ? _getAndroidAppDocDir : _getAppDocDir);
129 : if (dir != null) return dir;
130 :
131 2 : LOG.w('Failed to get persistent directory..');
132 : return null;
133 : }
134 :
135 1 : Future<Directory?> _getCache() async {
136 : // try temp directory first
137 2 : Directory? dir = await _try(_getTempDir);
138 : if (dir != null) return dir;
139 :
140 1 : if (useSystemTempFallback) {
141 : try {
142 1 : return Directory.systemTemp;
143 : } catch (e) {
144 MIS 0 : LOG.e('Error getting system temp directory: $e');
145 : return null;
146 : }
147 : }
148 :
149 HIT 2 : LOG.w('Failed to get cache directory..');
150 : return null;
151 : }
152 :
153 1 : Future<Directory?> _try(DirCallback? callback) async {
154 : if (callback == null) return null;
155 : try {
156 1 : Directory dir = await callback();
157 : return dir;
158 : } catch (e) {
159 3 : LOG.e('Error getting directory: $e');
160 : return null;
161 : }
162 : }
163 :
164 1 : Future<Directory?> _tryMaybe(MaybeDirCallback? callback) async {
165 : if (callback == null) return null;
166 : try {
167 1 : Directory? dir = await callback();
168 : return dir;
169 : } catch (e) {
170 3 : LOG.e('Error getting directory: $e');
171 : return null;
172 : }
173 : }
174 : }
175 :
176 : @visibleForTesting
177 : class MockLocalDirectory extends LocalDirectory {
178 1 : const MockLocalDirectory({
179 : required LocalDirectoryType type,
180 : required PermissionCheckInterface perms,
181 : required DirCallback? getAndroidAppDocDir,
182 : required DirCallback? getAppDocDir,
183 : required DirCallback? getTempDir,
184 : required MaybeDirCallback? getExtDir,
185 : required bool useSystemTempFallback,
186 1 : }) : super._custom(
187 : type,
188 : perms,
189 : getAndroidAppDocDir,
190 : getAppDocDir,
191 : getTempDir,
192 : getExtDir,
193 : useSystemTempFallback,
194 : );
195 : }
|