@umituz/react-native-filesystem 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/domain/entities/File.ts +88 -0
- package/src/infrastructure/services/FileSystemService.ts +112 -217
- package/src/infrastructure/services/cache.service.ts +47 -0
- package/src/infrastructure/services/directory.service.ts +60 -0
- package/src/infrastructure/services/download.service.ts +35 -0
- package/src/infrastructure/services/file-info.service.ts +52 -0
- package/src/infrastructure/services/file-manager.service.ts +62 -0
- package/src/infrastructure/services/file-path.service.ts +22 -0
- package/src/infrastructure/services/file-reader.service.ts +38 -0
- package/src/infrastructure/services/file-writer.service.ts +32 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-filesystem",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Domain-Driven Design filesystem utilities for React Native apps with build-time module loading and runtime file operations",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -131,4 +131,92 @@ export class FileUtils {
|
|
|
131
131
|
.join('/')
|
|
132
132
|
.replace(/\/+/g, '/');
|
|
133
133
|
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Get filename from filepath
|
|
137
|
+
*/
|
|
138
|
+
static getFileName(filepath: string): string {
|
|
139
|
+
return filepath.split('/').pop() || '';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get MIME type from filename
|
|
144
|
+
*/
|
|
145
|
+
static getMimeType(filename: string): string {
|
|
146
|
+
const ext = this.getFileExtension(filename).toLowerCase().replace('.', '');
|
|
147
|
+
|
|
148
|
+
const mimeTypes: Record<string, string> = {
|
|
149
|
+
// Images
|
|
150
|
+
jpg: 'image/jpeg',
|
|
151
|
+
jpeg: 'image/jpeg',
|
|
152
|
+
png: 'image/png',
|
|
153
|
+
gif: 'image/gif',
|
|
154
|
+
webp: 'image/webp',
|
|
155
|
+
svg: 'image/svg+xml',
|
|
156
|
+
|
|
157
|
+
// Documents
|
|
158
|
+
pdf: 'application/pdf',
|
|
159
|
+
doc: 'application/msword',
|
|
160
|
+
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
161
|
+
xls: 'application/vnd.ms-excel',
|
|
162
|
+
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
163
|
+
|
|
164
|
+
// Text
|
|
165
|
+
txt: 'text/plain',
|
|
166
|
+
csv: 'text/csv',
|
|
167
|
+
json: 'application/json',
|
|
168
|
+
|
|
169
|
+
// Video
|
|
170
|
+
mp4: 'video/mp4',
|
|
171
|
+
mov: 'video/quicktime',
|
|
172
|
+
avi: 'video/x-msvideo',
|
|
173
|
+
mkv: 'video/x-matroska',
|
|
174
|
+
webm: 'video/webm',
|
|
175
|
+
|
|
176
|
+
// Audio
|
|
177
|
+
mp3: 'audio/mpeg',
|
|
178
|
+
wav: 'audio/wav',
|
|
179
|
+
m4a: 'audio/mp4',
|
|
180
|
+
ogg: 'audio/ogg',
|
|
181
|
+
flac: 'audio/flac',
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
return mimeTypes[ext] || 'application/octet-stream';
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Check if file is an image
|
|
189
|
+
*/
|
|
190
|
+
static isImageFile(filename: string): boolean {
|
|
191
|
+
const ext = this.getFileExtension(filename).toLowerCase().replace('.', '');
|
|
192
|
+
return ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'].includes(ext);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Check if file is a video
|
|
197
|
+
*/
|
|
198
|
+
static isVideoFile(filename: string): boolean {
|
|
199
|
+
const ext = this.getFileExtension(filename).toLowerCase().replace('.', '');
|
|
200
|
+
return ['mp4', 'mov', 'avi', 'mkv', 'webm'].includes(ext);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Check if file is an audio file
|
|
205
|
+
*/
|
|
206
|
+
static isAudioFile(filename: string): boolean {
|
|
207
|
+
const ext = this.getFileExtension(filename).toLowerCase().replace('.', '');
|
|
208
|
+
return ['mp3', 'wav', 'm4a', 'ogg', 'flac'].includes(ext);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Generate filename with prefix (alternative to generateUniqueFilename)
|
|
213
|
+
*/
|
|
214
|
+
static generateFileName(originalName: string, prefix?: string): string {
|
|
215
|
+
const timestamp = Date.now();
|
|
216
|
+
const random = Math.random().toString(36).substring(2, 9);
|
|
217
|
+
const ext = this.getFileExtension(originalName);
|
|
218
|
+
const baseName = prefix ? `${prefix}_` : '';
|
|
219
|
+
|
|
220
|
+
return `${baseName}${timestamp}_${random}${ext}`;
|
|
221
|
+
}
|
|
134
222
|
}
|
|
@@ -1,265 +1,160 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* @domain filesystem
|
|
8
|
-
* @layer infrastructure/services
|
|
2
|
+
* FileSystem Service
|
|
3
|
+
* Single Responsibility: Facade for all file system operations
|
|
4
|
+
*
|
|
5
|
+
* This is a facade that delegates to specialized services
|
|
6
|
+
* following Single Responsibility Principle
|
|
9
7
|
*/
|
|
10
8
|
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
9
|
+
import { readFile, readFileAsBase64 } from "./file-reader.service";
|
|
10
|
+
import { writeFile } from "./file-writer.service";
|
|
11
|
+
import { deleteFile, copyFile, moveFile } from "./file-manager.service";
|
|
12
|
+
import {
|
|
13
|
+
createDirectory,
|
|
14
|
+
listDirectory,
|
|
15
|
+
getDirectoryPath,
|
|
16
|
+
getDocumentDirectory,
|
|
17
|
+
getCacheDirectory,
|
|
18
|
+
} from "./directory.service";
|
|
19
|
+
import { getFileInfo, fileExists, getFileSize } from "./file-info.service";
|
|
20
|
+
import { downloadFile } from "./download.service";
|
|
21
|
+
import { clearCache, getDirectorySize } from "./cache.service";
|
|
22
|
+
import { generateFilePath } from "./file-path.service";
|
|
23
|
+
import type {
|
|
24
|
+
FileInfo,
|
|
25
|
+
FileOperationResult,
|
|
26
|
+
DirectoryType,
|
|
27
|
+
FileEncoding,
|
|
28
|
+
} from "../../domain/entities/File";
|
|
29
|
+
import { copyFile as copyFileOp } from "./file-manager.service";
|
|
30
|
+
import { FileUtils } from "../../domain/entities/File";
|
|
14
31
|
|
|
15
32
|
/**
|
|
16
|
-
* FileSystem
|
|
17
|
-
*
|
|
18
|
-
* CENTRAL PLACE FOR ALL FILE OPERATIONS
|
|
19
|
-
* - Reading/writing files
|
|
20
|
-
* - Directory management
|
|
21
|
-
* - File downloads
|
|
22
|
-
* - Cache management
|
|
23
|
-
*
|
|
24
|
-
* Used by other domains (media, etc.) for file operations
|
|
33
|
+
* FileSystem Service - Facade for all file operations
|
|
34
|
+
* Delegates to specialized services following SOLID principles
|
|
25
35
|
*/
|
|
26
36
|
export class FileSystemService {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (!info.exists) {
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
uri,
|
|
40
|
-
name: uri.split('/').pop() || '',
|
|
41
|
-
size: info.size || 0,
|
|
42
|
-
exists: info.exists,
|
|
43
|
-
isDirectory: info.isDirectory || false,
|
|
44
|
-
modificationTime: info.modificationTime || 0,
|
|
45
|
-
};
|
|
46
|
-
} catch (error) {
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
37
|
+
// File Reading
|
|
38
|
+
static async readFile(
|
|
39
|
+
uri: string,
|
|
40
|
+
encoding: FileEncoding = "utf8",
|
|
41
|
+
): Promise<string | null> {
|
|
42
|
+
return readFile(uri, encoding);
|
|
49
43
|
}
|
|
50
44
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
*/
|
|
54
|
-
static async readFile(uri: string, encoding: FileEncoding = 'utf8'): Promise<string | null> {
|
|
55
|
-
try {
|
|
56
|
-
const content = await FileSystem.readAsStringAsync(uri, { encoding });
|
|
57
|
-
return content;
|
|
58
|
-
} catch (error) {
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
45
|
+
static async readFileAsBase64(uri: string): Promise<string | null> {
|
|
46
|
+
return readFileAsBase64(uri);
|
|
61
47
|
}
|
|
62
48
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
} catch (error) {
|
|
71
|
-
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
|
|
72
|
-
}
|
|
49
|
+
// File Writing
|
|
50
|
+
static async writeFile(
|
|
51
|
+
uri: string,
|
|
52
|
+
content: string,
|
|
53
|
+
encoding: FileEncoding = "utf8",
|
|
54
|
+
): Promise<FileOperationResult> {
|
|
55
|
+
return writeFile(uri, content, encoding);
|
|
73
56
|
}
|
|
74
57
|
|
|
75
|
-
|
|
76
|
-
* Delete file or directory
|
|
77
|
-
*/
|
|
58
|
+
// File Management
|
|
78
59
|
static async deleteFile(uri: string): Promise<boolean> {
|
|
79
|
-
|
|
80
|
-
await FileSystem.deleteAsync(uri, { idempotent: true });
|
|
81
|
-
return true;
|
|
82
|
-
} catch (error) {
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
60
|
+
return deleteFile(uri);
|
|
85
61
|
}
|
|
86
62
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
await FileSystem.copyAsync({
|
|
93
|
-
from: sourceUri,
|
|
94
|
-
to: destinationUri,
|
|
95
|
-
});
|
|
96
|
-
return { success: true, uri: destinationUri };
|
|
97
|
-
} catch (error) {
|
|
98
|
-
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
|
|
99
|
-
}
|
|
63
|
+
static async copyFile(
|
|
64
|
+
sourceUri: string,
|
|
65
|
+
destinationUri: string,
|
|
66
|
+
): Promise<FileOperationResult> {
|
|
67
|
+
return copyFileOp(sourceUri, destinationUri);
|
|
100
68
|
}
|
|
101
69
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
await FileSystem.moveAsync({
|
|
108
|
-
from: sourceUri,
|
|
109
|
-
to: destinationUri,
|
|
110
|
-
});
|
|
111
|
-
return { success: true, uri: destinationUri };
|
|
112
|
-
} catch (error) {
|
|
113
|
-
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
|
|
114
|
-
}
|
|
70
|
+
static async moveFile(
|
|
71
|
+
sourceUri: string,
|
|
72
|
+
destinationUri: string,
|
|
73
|
+
): Promise<FileOperationResult> {
|
|
74
|
+
return moveFile(sourceUri, destinationUri);
|
|
115
75
|
}
|
|
116
76
|
|
|
117
|
-
|
|
118
|
-
* Create directory
|
|
119
|
-
*/
|
|
77
|
+
// Directory Operations
|
|
120
78
|
static async createDirectory(uri: string): Promise<boolean> {
|
|
121
|
-
|
|
122
|
-
await FileSystem.makeDirectoryAsync(uri, { intermediates: true });
|
|
123
|
-
return true;
|
|
124
|
-
} catch (error) {
|
|
125
|
-
return false;
|
|
126
|
-
}
|
|
79
|
+
return createDirectory(uri);
|
|
127
80
|
}
|
|
128
81
|
|
|
129
|
-
/**
|
|
130
|
-
* List directory contents
|
|
131
|
-
*/
|
|
132
82
|
static async listDirectory(uri: string): Promise<string[]> {
|
|
133
|
-
|
|
134
|
-
const files = await FileSystem.readDirectoryAsync(uri);
|
|
135
|
-
return files;
|
|
136
|
-
} catch (error) {
|
|
137
|
-
return [];
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Check if file exists
|
|
143
|
-
*/
|
|
144
|
-
static async exists(uri: string): Promise<boolean> {
|
|
145
|
-
try {
|
|
146
|
-
const info = await FileSystem.getInfoAsync(uri);
|
|
147
|
-
return info.exists;
|
|
148
|
-
} catch (error) {
|
|
149
|
-
return false;
|
|
150
|
-
}
|
|
83
|
+
return listDirectory(uri);
|
|
151
84
|
}
|
|
152
85
|
|
|
153
|
-
/**
|
|
154
|
-
* Get directory path by type
|
|
155
|
-
*/
|
|
156
86
|
static getDirectoryPath(type: DirectoryType): string {
|
|
157
|
-
|
|
158
|
-
case 'documentDirectory':
|
|
159
|
-
return FileSystem.documentDirectory || '';
|
|
160
|
-
case 'cacheDirectory':
|
|
161
|
-
return FileSystem.cacheDirectory || '';
|
|
162
|
-
default:
|
|
163
|
-
return '';
|
|
164
|
-
}
|
|
87
|
+
return getDirectoryPath(type);
|
|
165
88
|
}
|
|
166
89
|
|
|
167
|
-
/**
|
|
168
|
-
* Get document directory path
|
|
169
|
-
*/
|
|
170
90
|
static getDocumentDirectory(): string {
|
|
171
|
-
return
|
|
91
|
+
return getDocumentDirectory();
|
|
172
92
|
}
|
|
173
93
|
|
|
174
|
-
/**
|
|
175
|
-
* Get cache directory path
|
|
176
|
-
*/
|
|
177
94
|
static getCacheDirectory(): string {
|
|
178
|
-
return
|
|
95
|
+
return getCacheDirectory();
|
|
179
96
|
}
|
|
180
97
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
static generateFilePath(filename: string, directory: DirectoryType = 'documentDirectory'): string {
|
|
185
|
-
const dirPath = FileSystemService.getDirectoryPath(directory);
|
|
186
|
-
const uniqueFilename = FileUtils.generateUniqueFilename(filename);
|
|
187
|
-
return FileUtils.joinPaths(dirPath, uniqueFilename);
|
|
98
|
+
// File Information
|
|
99
|
+
static async getFileInfo(uri: string): Promise<FileInfo | null> {
|
|
100
|
+
return getFileInfo(uri);
|
|
188
101
|
}
|
|
189
102
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
*/
|
|
193
|
-
static async downloadFile(url: string, destinationUri?: string): Promise<FileOperationResult> {
|
|
194
|
-
try {
|
|
195
|
-
const destination = destinationUri || FileSystemService.generateFilePath('download');
|
|
196
|
-
const result = await FileSystem.downloadAsync(url, destination);
|
|
197
|
-
return { success: true, uri: result.uri };
|
|
198
|
-
} catch (error) {
|
|
199
|
-
return { success: false, error: error instanceof Error ? error.message : 'Download failed' };
|
|
200
|
-
}
|
|
103
|
+
static async exists(uri: string): Promise<boolean> {
|
|
104
|
+
return fileExists(uri);
|
|
201
105
|
}
|
|
202
106
|
|
|
203
|
-
/**
|
|
204
|
-
* Get file size
|
|
205
|
-
*/
|
|
206
107
|
static async getFileSize(uri: string): Promise<number> {
|
|
207
|
-
|
|
208
|
-
return info?.size || 0;
|
|
108
|
+
return getFileSize(uri);
|
|
209
109
|
}
|
|
210
110
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if (!cacheDir) return false;
|
|
218
|
-
|
|
219
|
-
const files = await FileSystemService.listDirectory(cacheDir);
|
|
220
|
-
await Promise.all(
|
|
221
|
-
files.map(file => FileSystemService.deleteFile(FileUtils.joinPaths(cacheDir, file)))
|
|
222
|
-
);
|
|
223
|
-
return true;
|
|
224
|
-
} catch (error) {
|
|
225
|
-
return false;
|
|
226
|
-
}
|
|
111
|
+
// Downloads
|
|
112
|
+
static async downloadFile(
|
|
113
|
+
url: string,
|
|
114
|
+
destinationUri?: string,
|
|
115
|
+
): Promise<FileOperationResult> {
|
|
116
|
+
return downloadFile(url, destinationUri);
|
|
227
117
|
}
|
|
228
118
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
static async getDirectorySize(uri: string): Promise<number> {
|
|
233
|
-
try {
|
|
234
|
-
const files = await FileSystemService.listDirectory(uri);
|
|
235
|
-
const sizes = await Promise.all(
|
|
236
|
-
files.map(async file => {
|
|
237
|
-
const filePath = FileUtils.joinPaths(uri, file);
|
|
238
|
-
const info = await FileSystemService.getFileInfo(filePath);
|
|
239
|
-
return info?.size || 0;
|
|
240
|
-
})
|
|
241
|
-
);
|
|
242
|
-
return sizes.reduce((total, size) => total + size, 0);
|
|
243
|
-
} catch (error) {
|
|
244
|
-
return 0;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Copy file to cache directory
|
|
250
|
-
*/
|
|
251
|
-
static async copyToCache(sourceUri: string, filename?: string): Promise<FileOperationResult> {
|
|
252
|
-
const name = filename || sourceUri.split('/').pop() || 'file';
|
|
253
|
-
const destinationUri = FileSystemService.generateFilePath(name, 'cacheDirectory');
|
|
254
|
-
return FileSystemService.copyFile(sourceUri, destinationUri);
|
|
119
|
+
// Cache Management
|
|
120
|
+
static async clearCache(): Promise<boolean> {
|
|
121
|
+
return clearCache();
|
|
255
122
|
}
|
|
256
123
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
124
|
+
static async getDirectorySize(uri: string): Promise<number> {
|
|
125
|
+
return getDirectorySize(uri);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// File Path Generation
|
|
129
|
+
static generateFilePath(
|
|
130
|
+
filename: string,
|
|
131
|
+
directory: DirectoryType = "documentDirectory",
|
|
132
|
+
): string {
|
|
133
|
+
return generateFilePath(filename, directory);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Convenience methods (delegates to other services)
|
|
137
|
+
static async copyToCache(
|
|
138
|
+
sourceUri: string,
|
|
139
|
+
filename?: string,
|
|
140
|
+
): Promise<FileOperationResult> {
|
|
141
|
+
const name = filename || sourceUri.split("/").pop() || "file";
|
|
142
|
+
const destinationUri = FileUtils.joinPaths(
|
|
143
|
+
getDirectoryPath("cacheDirectory"),
|
|
144
|
+
FileUtils.generateUniqueFilename(name),
|
|
145
|
+
);
|
|
146
|
+
return copyFileOp(sourceUri, destinationUri);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
static async copyToDocuments(
|
|
150
|
+
sourceUri: string,
|
|
151
|
+
filename?: string,
|
|
152
|
+
): Promise<FileOperationResult> {
|
|
153
|
+
const name = filename || sourceUri.split("/").pop() || "file";
|
|
154
|
+
const destinationUri = FileUtils.joinPaths(
|
|
155
|
+
getDirectoryPath("documentDirectory"),
|
|
156
|
+
FileUtils.generateUniqueFilename(name),
|
|
157
|
+
);
|
|
158
|
+
return copyFileOp(sourceUri, destinationUri);
|
|
264
159
|
}
|
|
265
160
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache Service
|
|
3
|
+
* Single Responsibility: Manage cache directory operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { getCacheDirectory, listDirectory } from "./directory.service";
|
|
7
|
+
import { deleteFile } from "./file-manager.service";
|
|
8
|
+
import { getFileInfo } from "./file-info.service";
|
|
9
|
+
import { FileUtils } from "../../domain/entities/File";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Clear cache directory
|
|
13
|
+
*/
|
|
14
|
+
export async function clearCache(): Promise<boolean> {
|
|
15
|
+
try {
|
|
16
|
+
const cacheDir = getCacheDirectory();
|
|
17
|
+
if (!cacheDir) return false;
|
|
18
|
+
|
|
19
|
+
const files = await listDirectory(cacheDir);
|
|
20
|
+
await Promise.all(
|
|
21
|
+
files.map((file) => deleteFile(FileUtils.joinPaths(cacheDir, file))),
|
|
22
|
+
);
|
|
23
|
+
return true;
|
|
24
|
+
} catch (error) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get total size of directory
|
|
31
|
+
*/
|
|
32
|
+
export async function getDirectorySize(uri: string): Promise<number> {
|
|
33
|
+
try {
|
|
34
|
+
const files = await listDirectory(uri);
|
|
35
|
+
const sizes = await Promise.all(
|
|
36
|
+
files.map(async (file) => {
|
|
37
|
+
const filePath = FileUtils.joinPaths(uri, file);
|
|
38
|
+
const info = await getFileInfo(filePath);
|
|
39
|
+
return info?.size || 0;
|
|
40
|
+
}),
|
|
41
|
+
);
|
|
42
|
+
return sizes.reduce((total, size) => total + size, 0);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Directory Service
|
|
3
|
+
* Single Responsibility: Manage directory operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as FileSystem from "expo-file-system";
|
|
7
|
+
import type { DirectoryType } from "../../domain/entities/File";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Create directory
|
|
11
|
+
*/
|
|
12
|
+
export async function createDirectory(uri: string): Promise<boolean> {
|
|
13
|
+
try {
|
|
14
|
+
await FileSystem.makeDirectoryAsync(uri, { intermediates: true });
|
|
15
|
+
return true;
|
|
16
|
+
} catch (error) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* List directory contents
|
|
23
|
+
*/
|
|
24
|
+
export async function listDirectory(uri: string): Promise<string[]> {
|
|
25
|
+
try {
|
|
26
|
+
const files = await FileSystem.readDirectoryAsync(uri);
|
|
27
|
+
return files;
|
|
28
|
+
} catch (error) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get directory path by type
|
|
35
|
+
*/
|
|
36
|
+
export function getDirectoryPath(type: DirectoryType): string {
|
|
37
|
+
switch (type) {
|
|
38
|
+
case "documentDirectory":
|
|
39
|
+
return FileSystem.documentDirectory || "";
|
|
40
|
+
case "cacheDirectory":
|
|
41
|
+
return FileSystem.cacheDirectory || "";
|
|
42
|
+
default:
|
|
43
|
+
return "";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get document directory path
|
|
49
|
+
*/
|
|
50
|
+
export function getDocumentDirectory(): string {
|
|
51
|
+
return getDirectoryPath("documentDirectory");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Get cache directory path
|
|
56
|
+
*/
|
|
57
|
+
export function getCacheDirectory(): string {
|
|
58
|
+
return getDirectoryPath("cacheDirectory");
|
|
59
|
+
}
|
|
60
|
+
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Download Service
|
|
3
|
+
* Single Responsibility: Download files from URLs
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as FileSystem from "expo-file-system";
|
|
7
|
+
import type { FileOperationResult } from "../../domain/entities/File";
|
|
8
|
+
import { getDirectoryPath } from "./directory.service";
|
|
9
|
+
import { FileUtils } from "../../domain/entities/File";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Download file from URL
|
|
13
|
+
*/
|
|
14
|
+
export async function downloadFile(
|
|
15
|
+
url: string,
|
|
16
|
+
destinationUri?: string,
|
|
17
|
+
): Promise<FileOperationResult> {
|
|
18
|
+
try {
|
|
19
|
+
const destination =
|
|
20
|
+
destinationUri ||
|
|
21
|
+
FileUtils.joinPaths(
|
|
22
|
+
getDirectoryPath("documentDirectory"),
|
|
23
|
+
FileUtils.generateUniqueFilename("download"),
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const result = await FileSystem.downloadAsync(url, destination);
|
|
27
|
+
return { success: true, uri: result.uri };
|
|
28
|
+
} catch (error) {
|
|
29
|
+
return {
|
|
30
|
+
success: false,
|
|
31
|
+
error: error instanceof Error ? error.message : "Download failed",
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Info Service
|
|
3
|
+
* Single Responsibility: Get file information and metadata
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as FileSystem from "expo-file-system";
|
|
7
|
+
import type { FileInfo } from "../../domain/entities/File";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Get file information
|
|
11
|
+
*/
|
|
12
|
+
export async function getFileInfo(uri: string): Promise<FileInfo | null> {
|
|
13
|
+
try {
|
|
14
|
+
const info = await FileSystem.getInfoAsync(uri);
|
|
15
|
+
|
|
16
|
+
if (!info.exists) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
uri,
|
|
22
|
+
name: uri.split("/").pop() || "",
|
|
23
|
+
size: info.size || 0,
|
|
24
|
+
exists: info.exists,
|
|
25
|
+
isDirectory: info.isDirectory || false,
|
|
26
|
+
modificationTime: info.modificationTime || 0,
|
|
27
|
+
};
|
|
28
|
+
} catch (error) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Check if file exists
|
|
35
|
+
*/
|
|
36
|
+
export async function fileExists(uri: string): Promise<boolean> {
|
|
37
|
+
try {
|
|
38
|
+
const info = await FileSystem.getInfoAsync(uri);
|
|
39
|
+
return info.exists;
|
|
40
|
+
} catch (error) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get file size
|
|
47
|
+
*/
|
|
48
|
+
export async function getFileSize(uri: string): Promise<number> {
|
|
49
|
+
const info = await getFileInfo(uri);
|
|
50
|
+
return info?.size || 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Manager Service
|
|
3
|
+
* Single Responsibility: Manage file operations (delete, copy, move)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as FileSystem from "expo-file-system";
|
|
7
|
+
import type { FileOperationResult } from "../../domain/entities/File";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Delete file or directory
|
|
11
|
+
*/
|
|
12
|
+
export async function deleteFile(uri: string): Promise<boolean> {
|
|
13
|
+
try {
|
|
14
|
+
await FileSystem.deleteAsync(uri, { idempotent: true });
|
|
15
|
+
return true;
|
|
16
|
+
} catch (error) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Copy file
|
|
23
|
+
*/
|
|
24
|
+
export async function copyFile(
|
|
25
|
+
sourceUri: string,
|
|
26
|
+
destinationUri: string,
|
|
27
|
+
): Promise<FileOperationResult> {
|
|
28
|
+
try {
|
|
29
|
+
await FileSystem.copyAsync({
|
|
30
|
+
from: sourceUri,
|
|
31
|
+
to: destinationUri,
|
|
32
|
+
});
|
|
33
|
+
return { success: true, uri: destinationUri };
|
|
34
|
+
} catch (error) {
|
|
35
|
+
return {
|
|
36
|
+
success: false,
|
|
37
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Move file
|
|
44
|
+
*/
|
|
45
|
+
export async function moveFile(
|
|
46
|
+
sourceUri: string,
|
|
47
|
+
destinationUri: string,
|
|
48
|
+
): Promise<FileOperationResult> {
|
|
49
|
+
try {
|
|
50
|
+
await FileSystem.moveAsync({
|
|
51
|
+
from: sourceUri,
|
|
52
|
+
to: destinationUri,
|
|
53
|
+
});
|
|
54
|
+
return { success: true, uri: destinationUri };
|
|
55
|
+
} catch (error) {
|
|
56
|
+
return {
|
|
57
|
+
success: false,
|
|
58
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Path Service
|
|
3
|
+
* Single Responsibility: Generate and manage file paths
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { DirectoryType } from "../../domain/entities/File";
|
|
7
|
+
import { getDirectoryPath } from "./directory.service";
|
|
8
|
+
import { FileUtils } from "../../domain/entities/File";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generate unique file path in specified directory
|
|
12
|
+
*/
|
|
13
|
+
export function generateFilePath(
|
|
14
|
+
filename: string,
|
|
15
|
+
directory: DirectoryType = "documentDirectory",
|
|
16
|
+
): string {
|
|
17
|
+
const dirPath = getDirectoryPath(directory);
|
|
18
|
+
const uniqueFilename = FileUtils.generateUniqueFilename(filename);
|
|
19
|
+
return FileUtils.joinPaths(dirPath, uniqueFilename);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Reader Service
|
|
3
|
+
* Single Responsibility: Read files from device storage
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as FileSystem from "expo-file-system";
|
|
7
|
+
import type { FileEncoding } from "../../domain/entities/File";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Read file as string with encoding
|
|
11
|
+
*/
|
|
12
|
+
export async function readFile(
|
|
13
|
+
uri: string,
|
|
14
|
+
encoding: FileEncoding = "utf8",
|
|
15
|
+
): Promise<string | null> {
|
|
16
|
+
try {
|
|
17
|
+
const encodingType =
|
|
18
|
+
encoding === "base64"
|
|
19
|
+
? FileSystem.EncodingType.Base64
|
|
20
|
+
: FileSystem.EncodingType.UTF8;
|
|
21
|
+
|
|
22
|
+
const content = await FileSystem.readAsStringAsync(uri, {
|
|
23
|
+
encoding: encodingType,
|
|
24
|
+
});
|
|
25
|
+
return content;
|
|
26
|
+
} catch (error) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Read file as base64 string
|
|
33
|
+
*/
|
|
34
|
+
export async function readFileAsBase64(uri: string): Promise<string | null> {
|
|
35
|
+
return readFile(uri, "base64");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Writer Service
|
|
3
|
+
* Single Responsibility: Write files to device storage
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as FileSystem from "expo-file-system";
|
|
7
|
+
import type { FileEncoding, FileOperationResult } from "../../domain/entities/File";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Write string to file
|
|
11
|
+
*/
|
|
12
|
+
export async function writeFile(
|
|
13
|
+
uri: string,
|
|
14
|
+
content: string,
|
|
15
|
+
encoding: FileEncoding = "utf8",
|
|
16
|
+
): Promise<FileOperationResult> {
|
|
17
|
+
try {
|
|
18
|
+
const encodingType =
|
|
19
|
+
encoding === "base64"
|
|
20
|
+
? FileSystem.EncodingType.Base64
|
|
21
|
+
: FileSystem.EncodingType.UTF8;
|
|
22
|
+
|
|
23
|
+
await FileSystem.writeAsStringAsync(uri, content, { encoding: encodingType });
|
|
24
|
+
return { success: true, uri };
|
|
25
|
+
} catch (error) {
|
|
26
|
+
return {
|
|
27
|
+
success: false,
|
|
28
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|