@umituz/react-native-design-system 2.8.6 → 2.8.8
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 +5 -6
- package/src/device/infrastructure/repositories/LegacyDeviceIdRepository.ts +1 -1
- package/src/device/infrastructure/services/DeviceFeatureService.ts +1 -1
- package/src/exception/infrastructure/services/ExceptionLogger.ts +1 -1
- package/src/exception/infrastructure/storage/ExceptionStore.ts +1 -1
- package/src/exports/filesystem.ts +1 -0
- package/src/exports/storage.ts +1 -0
- package/src/filesystem/domain/constants/FileConstants.ts +20 -0
- package/src/filesystem/domain/entities/File.ts +20 -0
- package/src/filesystem/domain/types/FileTypes.ts +43 -0
- package/src/filesystem/domain/utils/FileUtils.ts +86 -0
- package/src/filesystem/index.ts +23 -0
- package/src/filesystem/infrastructure/services/FileSystemService.ts +45 -0
- package/src/filesystem/infrastructure/services/cache.service.ts +48 -0
- package/src/filesystem/infrastructure/services/directory.service.ts +66 -0
- package/src/filesystem/infrastructure/services/download.constants.ts +6 -0
- package/src/filesystem/infrastructure/services/download.service.ts +74 -0
- package/src/filesystem/infrastructure/services/download.types.ts +7 -0
- package/src/filesystem/infrastructure/services/encoding.service.ts +25 -0
- package/src/filesystem/infrastructure/services/file-info.service.ts +52 -0
- package/src/filesystem/infrastructure/services/file-manager.service.ts +81 -0
- package/src/filesystem/infrastructure/services/file-path.service.ts +22 -0
- package/src/filesystem/infrastructure/services/file-reader.service.ts +52 -0
- package/src/filesystem/infrastructure/services/file-writer.service.ts +32 -0
- package/src/filesystem/infrastructure/utils/blob.utils.ts +20 -0
- package/src/image/infrastructure/services/ImageBatchService.ts +1 -1
- package/src/image/infrastructure/services/ImageStorageService.ts +1 -1
- package/src/image/presentation/components/editor/TextEditorTabs.tsx +6 -1
- package/src/index.ts +9 -0
- package/src/molecules/alerts/AlertStore.ts +1 -1
- package/src/molecules/calendar/infrastructure/storage/EventActions.ts +1 -1
- package/src/molecules/calendar/infrastructure/stores/storageAdapter.ts +1 -1
- package/src/offline/infrastructure/storage/OfflineStore.ts +1 -1
- package/src/onboarding/infrastructure/storage/OnboardingStore.ts +2 -2
- package/src/onboarding/infrastructure/storage/__tests__/OnboardingStore.test.ts +1 -1
- package/src/onboarding/infrastructure/storage/actions/answerActions.ts +1 -1
- package/src/onboarding/infrastructure/storage/actions/storageHelpers.ts +1 -1
- package/src/storage/README.md +185 -0
- package/src/storage/__tests__/integration.test.ts +391 -0
- package/src/storage/__tests__/mocks/asyncStorage.mock.ts +52 -0
- package/src/storage/__tests__/performance.test.tsx +352 -0
- package/src/storage/__tests__/setup.ts +63 -0
- package/src/storage/application/README.md +158 -0
- package/src/storage/application/ports/IStorageRepository.ts +61 -0
- package/src/storage/application/ports/README.md +127 -0
- package/src/storage/cache/README.md +154 -0
- package/src/storage/cache/__tests__/PerformanceAndMemory.test.ts +387 -0
- package/src/storage/cache/__tests__/setup.ts +19 -0
- package/src/storage/cache/domain/Cache.ts +146 -0
- package/src/storage/cache/domain/CacheManager.md +83 -0
- package/src/storage/cache/domain/CacheManager.ts +48 -0
- package/src/storage/cache/domain/CacheStatsTracker.md +169 -0
- package/src/storage/cache/domain/CacheStatsTracker.ts +49 -0
- package/src/storage/cache/domain/CachedValue.md +97 -0
- package/src/storage/cache/domain/ErrorHandler.md +99 -0
- package/src/storage/cache/domain/ErrorHandler.ts +42 -0
- package/src/storage/cache/domain/PatternMatcher.md +122 -0
- package/src/storage/cache/domain/PatternMatcher.ts +30 -0
- package/src/storage/cache/domain/README.md +118 -0
- package/src/storage/cache/domain/__tests__/Cache.test.ts +293 -0
- package/src/storage/cache/domain/__tests__/CacheManager.test.ts +276 -0
- package/src/storage/cache/domain/__tests__/ErrorHandler.test.ts +303 -0
- package/src/storage/cache/domain/__tests__/PatternMatcher.test.ts +261 -0
- package/src/storage/cache/domain/strategies/EvictionStrategy.ts +9 -0
- package/src/storage/cache/domain/strategies/FIFOStrategy.ts +12 -0
- package/src/storage/cache/domain/strategies/LFUStrategy.ts +22 -0
- package/src/storage/cache/domain/strategies/LRUStrategy.ts +22 -0
- package/src/storage/cache/domain/strategies/README.md +117 -0
- package/src/storage/cache/domain/strategies/TTLStrategy.ts +23 -0
- package/src/storage/cache/domain/strategies/__tests__/EvictionStrategies.test.ts +293 -0
- package/src/storage/cache/domain/types/Cache.ts +28 -0
- package/src/storage/cache/domain/types/README.md +107 -0
- package/src/storage/cache/index.ts +28 -0
- package/src/storage/cache/infrastructure/README.md +126 -0
- package/src/storage/cache/infrastructure/TTLCache.ts +103 -0
- package/src/storage/cache/infrastructure/__tests__/TTLCache.test.ts +303 -0
- package/src/storage/cache/presentation/README.md +123 -0
- package/src/storage/cache/presentation/__tests__/ReactHooks.test.ts +514 -0
- package/src/storage/cache/presentation/useCache.ts +76 -0
- package/src/storage/cache/presentation/useCachedValue.ts +88 -0
- package/src/storage/cache/types.d.ts +3 -0
- package/src/storage/domain/README.md +128 -0
- package/src/storage/domain/constants/CacheDefaults.ts +64 -0
- package/src/storage/domain/constants/README.md +105 -0
- package/src/storage/domain/entities/CachedValue.ts +86 -0
- package/src/storage/domain/entities/README.md +109 -0
- package/src/storage/domain/entities/StorageResult.ts +75 -0
- package/src/storage/domain/entities/__tests__/CachedValue.test.ts +149 -0
- package/src/storage/domain/entities/__tests__/StorageResult.test.ts +122 -0
- package/src/storage/domain/errors/README.md +126 -0
- package/src/storage/domain/errors/StorageError.ts +81 -0
- package/src/storage/domain/errors/__tests__/StorageError.test.ts +127 -0
- package/src/storage/domain/factories/README.md +138 -0
- package/src/storage/domain/factories/StoreFactory.ts +59 -0
- package/src/storage/domain/types/README.md +522 -0
- package/src/storage/domain/types/Store.ts +44 -0
- package/src/storage/domain/utils/CacheKeyGenerator.ts +66 -0
- package/src/storage/domain/utils/README.md +127 -0
- package/src/storage/domain/utils/__tests__/devUtils.test.ts +97 -0
- package/src/storage/domain/utils/devUtils.ts +37 -0
- package/src/storage/domain/value-objects/README.md +120 -0
- package/src/storage/domain/value-objects/StorageKey.ts +60 -0
- package/src/storage/index.ts +175 -0
- package/src/storage/infrastructure/README.md +165 -0
- package/src/storage/infrastructure/adapters/README.md +175 -0
- package/src/storage/infrastructure/adapters/StorageService.md +103 -0
- package/src/storage/infrastructure/adapters/StorageService.ts +49 -0
- package/src/storage/infrastructure/repositories/AsyncStorageRepository.ts +98 -0
- package/src/storage/infrastructure/repositories/BaseStorageOperations.ts +100 -0
- package/src/storage/infrastructure/repositories/BatchStorageOperations.ts +42 -0
- package/src/storage/infrastructure/repositories/README.md +121 -0
- package/src/storage/infrastructure/repositories/StringStorageOperations.ts +44 -0
- package/src/storage/infrastructure/repositories/__tests__/AsyncStorageRepository.test.ts +170 -0
- package/src/storage/infrastructure/repositories/__tests__/BaseStorageOperations.test.ts +201 -0
- package/src/storage/presentation/README.md +181 -0
- package/src/storage/presentation/hooks/CacheStorageOperations.ts +94 -0
- package/src/storage/presentation/hooks/README.md +128 -0
- package/src/storage/presentation/hooks/__tests__/usePersistentCache.test.ts +405 -0
- package/src/storage/presentation/hooks/__tests__/useStorage.test.ts +247 -0
- package/src/storage/presentation/hooks/__tests__/useStorageState.test.ts +293 -0
- package/src/storage/presentation/hooks/useCacheState.ts +53 -0
- package/src/storage/presentation/hooks/usePersistentCache.ts +154 -0
- package/src/storage/presentation/hooks/useStorage.ts +102 -0
- package/src/storage/presentation/hooks/useStorageState.ts +71 -0
- package/src/storage/presentation/hooks/useStore.ts +15 -0
- package/src/storage/types/README.md +103 -0
- package/src/theme/infrastructure/globalThemeStore.ts +1 -1
- package/src/theme/infrastructure/storage/ThemeStorage.ts +1 -1
- package/src/theme/infrastructure/stores/themeStore.ts +1 -1
- package/src/utilities/sharing/infrastructure/services/SharingService.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-design-system",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.8",
|
|
4
4
|
"description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive, safe area, exception, infinite scroll, UUID, image, timezone, offline, and onboarding utilities",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
"./timezone": "./src/timezone/index.ts",
|
|
24
24
|
"./offline": "./src/offline/index.ts",
|
|
25
25
|
"./onboarding": "./src/onboarding/index.ts",
|
|
26
|
+
"./storage": "./src/storage/index.ts",
|
|
27
|
+
"./filesystem": "./src/filesystem/index.ts",
|
|
26
28
|
"./package.json": "./package.json"
|
|
27
29
|
},
|
|
28
30
|
"scripts": {
|
|
@@ -59,13 +61,12 @@
|
|
|
59
61
|
"@expo/vector-icons": ">=15.0.0",
|
|
60
62
|
"@react-native-community/datetimepicker": ">=8.0.0",
|
|
61
63
|
"@react-native-community/slider": ">=4.0.0",
|
|
64
|
+
"@react-native-async-storage/async-storage": ">=1.18.0",
|
|
62
65
|
"@react-navigation/bottom-tabs": ">=7.0.0",
|
|
63
66
|
"@react-navigation/native": ">=7.0.0",
|
|
64
67
|
"@react-navigation/stack": ">=7.0.0",
|
|
65
|
-
"@umituz/react-native-filesystem": "*",
|
|
66
68
|
"@umituz/react-native-haptics": "*",
|
|
67
69
|
"@umituz/react-native-localization": "*",
|
|
68
|
-
"@umituz/react-native-storage": "*",
|
|
69
70
|
"@umituz/react-native-uuid": "*",
|
|
70
71
|
"expo-application": ">=5.0.0",
|
|
71
72
|
"expo-clipboard": ">=8.0.0",
|
|
@@ -111,10 +112,8 @@
|
|
|
111
112
|
"@types/react-native": "^0.73.0",
|
|
112
113
|
"@typescript-eslint/eslint-plugin": "^8.50.1",
|
|
113
114
|
"@typescript-eslint/parser": "^8.50.1",
|
|
114
|
-
"@umituz/react-native-filesystem": "^2.1.7",
|
|
115
115
|
"@umituz/react-native-haptics": "^1.0.2",
|
|
116
116
|
"@umituz/react-native-localization": "^3.5.34",
|
|
117
|
-
"@umituz/react-native-storage": "^2.6.20",
|
|
118
117
|
"@umituz/react-native-uuid": "*",
|
|
119
118
|
"eslint": "^9.39.2",
|
|
120
119
|
"eslint-plugin-react": "^7.37.5",
|
|
@@ -125,7 +124,7 @@
|
|
|
125
124
|
"expo-crypto": "~14.0.0",
|
|
126
125
|
"expo-device": "~7.0.2",
|
|
127
126
|
"expo-file-system": "^19.0.21",
|
|
128
|
-
"expo-font": "~
|
|
127
|
+
"expo-font": "~14.0.0",
|
|
129
128
|
"expo-network": "~8.0.0",
|
|
130
129
|
"expo-video": "~3.0.0",
|
|
131
130
|
"expo-haptics": "~14.0.0",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import type { ExceptionEntity } from '../../domain/entities/ExceptionEntity';
|
|
12
12
|
import { ExceptionHandler } from './ExceptionHandler';
|
|
13
|
-
import { storageRepository } from '@
|
|
13
|
+
import { storageRepository } from '@storage';
|
|
14
14
|
|
|
15
15
|
export class ExceptionLogger {
|
|
16
16
|
private static readonly STORAGE_KEY = '@exceptions';
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Zustand store for exception state management
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { createStore } from '@
|
|
6
|
+
import { createStore } from '@storage';
|
|
7
7
|
import type { ExceptionEntity } from '../../domain/entities/ExceptionEntity';
|
|
8
8
|
|
|
9
9
|
interface ExceptionState {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../filesystem';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '../storage';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Constants
|
|
3
|
+
* File-related constants for the filesystem package
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { FileEncoding } from '../types/FileTypes';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* File-related constants
|
|
10
|
+
*/
|
|
11
|
+
export const FILE_CONSTANTS = {
|
|
12
|
+
MAX_FILE_SIZE: 100 * 1024 * 1024, // 100 MB
|
|
13
|
+
ALLOWED_EXTENSIONS: ['.jpg', '.jpeg', '.png', '.pdf', '.txt', '.json', '.mp4', '.mp3'] as const,
|
|
14
|
+
DEFAULT_ENCODING: 'utf8' as FileEncoding,
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Allowed file extensions type
|
|
19
|
+
*/
|
|
20
|
+
export type AllowedExtension = typeof FILE_CONSTANTS.ALLOWED_EXTENSIONS[number];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Entity - Module Index
|
|
3
|
+
* Domain layer exports for file operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Types
|
|
7
|
+
export type {
|
|
8
|
+
FileEncoding,
|
|
9
|
+
DirectoryType,
|
|
10
|
+
FileOperationResult,
|
|
11
|
+
FileInfo,
|
|
12
|
+
DownloadProgress,
|
|
13
|
+
} from '../types/FileTypes';
|
|
14
|
+
|
|
15
|
+
// Constants
|
|
16
|
+
export { FILE_CONSTANTS } from '../constants/FileConstants';
|
|
17
|
+
export type { AllowedExtension } from '../constants/FileConstants';
|
|
18
|
+
|
|
19
|
+
// Utils
|
|
20
|
+
export { FileUtils } from '../utils/FileUtils';
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Types
|
|
3
|
+
* Domain layer type definitions for file operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* File encoding types supported by expo-file-system
|
|
8
|
+
*/
|
|
9
|
+
export type FileEncoding = 'utf8' | 'base64';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Directory types available in expo-file-system
|
|
13
|
+
*/
|
|
14
|
+
export type DirectoryType = 'documentDirectory' | 'cacheDirectory';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Result of a file operation
|
|
18
|
+
*/
|
|
19
|
+
export interface FileOperationResult {
|
|
20
|
+
success: boolean;
|
|
21
|
+
uri?: string;
|
|
22
|
+
error?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* File information metadata
|
|
27
|
+
*/
|
|
28
|
+
export interface FileInfo {
|
|
29
|
+
uri: string;
|
|
30
|
+
name?: string;
|
|
31
|
+
size: number;
|
|
32
|
+
exists: boolean;
|
|
33
|
+
isDirectory: boolean;
|
|
34
|
+
modificationTime?: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Download progress information
|
|
39
|
+
*/
|
|
40
|
+
export interface DownloadProgress {
|
|
41
|
+
totalBytesWritten: number;
|
|
42
|
+
totalBytesExpectedToWrite: number;
|
|
43
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Utils
|
|
3
|
+
* File utility functions for common operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { FILE_CONSTANTS, type AllowedExtension } from '../constants/FileConstants';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* File utility functions
|
|
10
|
+
*/
|
|
11
|
+
export class FileUtils {
|
|
12
|
+
/**
|
|
13
|
+
* Format file size in bytes to human-readable format
|
|
14
|
+
*/
|
|
15
|
+
static formatFileSize(bytes: number): string {
|
|
16
|
+
if (bytes === 0) return '0 Bytes';
|
|
17
|
+
const k = 1024;
|
|
18
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
19
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
20
|
+
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Generate unique filename with timestamp
|
|
25
|
+
*/
|
|
26
|
+
static generateUniqueFilename(filename: string): string {
|
|
27
|
+
const timestamp = Date.now();
|
|
28
|
+
const extension = filename.includes('.') ? filename.substring(filename.lastIndexOf('.')) : '';
|
|
29
|
+
const nameWithoutExt = filename.includes('.')
|
|
30
|
+
? filename.substring(0, filename.lastIndexOf('.'))
|
|
31
|
+
: filename;
|
|
32
|
+
return `${nameWithoutExt}_${timestamp}${extension}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Sanitize filename by removing invalid characters
|
|
37
|
+
*/
|
|
38
|
+
static sanitizeFilename(filename: string): string {
|
|
39
|
+
return filename.replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Join path segments
|
|
44
|
+
*/
|
|
45
|
+
static joinPaths(...segments: string[]): string {
|
|
46
|
+
return segments
|
|
47
|
+
.map((segment, index) => {
|
|
48
|
+
if (index === 0) {
|
|
49
|
+
return segment.replace(/\/+$/, '');
|
|
50
|
+
}
|
|
51
|
+
return segment.replace(/^\/+/, '').replace(/\/+$/, '');
|
|
52
|
+
})
|
|
53
|
+
.filter(segment => segment.length > 0)
|
|
54
|
+
.join('/');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get file extension from filename
|
|
59
|
+
*/
|
|
60
|
+
static getFileExtension(filename: string): string {
|
|
61
|
+
const lastDot = filename.lastIndexOf('.');
|
|
62
|
+
return lastDot > 0 ? filename.substring(lastDot) : '';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Check if file extension is allowed
|
|
67
|
+
*/
|
|
68
|
+
static isAllowedExtension(filename: string): boolean {
|
|
69
|
+
const extension = this.getFileExtension(filename).toLowerCase();
|
|
70
|
+
return FILE_CONSTANTS.ALLOWED_EXTENSIONS.includes(extension as AllowedExtension);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get filename from URI
|
|
75
|
+
*/
|
|
76
|
+
static getFilenameFromUri(uri: string): string {
|
|
77
|
+
return uri.split('/').pop() || '';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Validate file size
|
|
82
|
+
*/
|
|
83
|
+
static isValidFileSize(size: number): boolean {
|
|
84
|
+
return size > 0 && size <= FILE_CONSTANTS.MAX_FILE_SIZE;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @filesystem
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Domain - Types and Utilities
|
|
6
|
+
export type { FileEncoding, DirectoryType, FileOperationResult, FileInfo, DownloadProgress } from "./domain/entities/File";
|
|
7
|
+
export { FILE_CONSTANTS, FileUtils } from "./domain/entities/File";
|
|
8
|
+
|
|
9
|
+
// Services - Download Operations
|
|
10
|
+
export type { DownloadProgressCallback, DownloadWithProgressResult } from "./infrastructure/services/download.types";
|
|
11
|
+
export { downloadFile, downloadFileWithProgress, isUrlCached, getCachedFileUri, deleteCachedFile } from "./infrastructure/services/download.service";
|
|
12
|
+
|
|
13
|
+
// Services - Directory Operations
|
|
14
|
+
export { getCacheDirectory, getDocumentDirectory, createDirectory } from "./infrastructure/services/directory.service";
|
|
15
|
+
|
|
16
|
+
// Services - File Operations
|
|
17
|
+
export { FileSystemService } from "./infrastructure/services/FileSystemService";
|
|
18
|
+
export { fileExists, getFileInfo } from "./infrastructure/services/file-info.service";
|
|
19
|
+
export { deleteFile } from "./infrastructure/services/file-manager.service";
|
|
20
|
+
export { clearCache } from "./infrastructure/services/cache.service";
|
|
21
|
+
|
|
22
|
+
// Services - File Reading
|
|
23
|
+
export { readFile, readFileAsBase64 } from "./infrastructure/services/file-reader.service";
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FileSystem Service - Facade
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFile, readFileAsBase64 } from "./file-reader.service";
|
|
6
|
+
import { writeFile } from "./file-writer.service";
|
|
7
|
+
import { deleteFile, copyFile, moveFile } from "./file-manager.service";
|
|
8
|
+
import { createDirectory, listDirectory, getDirectoryPath, getDocumentDirectory, getCacheDirectory } from "./directory.service";
|
|
9
|
+
import { getFileInfo, fileExists, getFileSize } from "./file-info.service";
|
|
10
|
+
import { downloadFile } from "./download.service";
|
|
11
|
+
import { clearCache, getDirectorySize } from "./cache.service";
|
|
12
|
+
import { generateFilePath } from "./file-path.service";
|
|
13
|
+
import { FileUtils } from "../../domain/entities/File";
|
|
14
|
+
import type { FileOperationResult } from "../../domain/entities/File";
|
|
15
|
+
|
|
16
|
+
export class FileSystemService {
|
|
17
|
+
static readFile = readFile;
|
|
18
|
+
static readFileAsBase64 = readFileAsBase64;
|
|
19
|
+
static writeFile = writeFile;
|
|
20
|
+
static deleteFile = deleteFile;
|
|
21
|
+
static copyFile = copyFile;
|
|
22
|
+
static moveFile = moveFile;
|
|
23
|
+
static createDirectory = createDirectory;
|
|
24
|
+
static listDirectory = listDirectory;
|
|
25
|
+
static getDirectoryPath = getDirectoryPath;
|
|
26
|
+
static getDocumentDirectory = getDocumentDirectory;
|
|
27
|
+
static getCacheDirectory = getCacheDirectory;
|
|
28
|
+
static getFileInfo = getFileInfo;
|
|
29
|
+
static exists = fileExists;
|
|
30
|
+
static getFileSize = getFileSize;
|
|
31
|
+
static downloadFile = downloadFile;
|
|
32
|
+
static clearCache = clearCache;
|
|
33
|
+
static getDirectorySize = getDirectorySize;
|
|
34
|
+
static generateFilePath = generateFilePath;
|
|
35
|
+
|
|
36
|
+
static async copyToCache(sourceUri: string, filename?: string): Promise<FileOperationResult> {
|
|
37
|
+
const dest = FileUtils.joinPaths(getCacheDirectory(), FileUtils.generateUniqueFilename(filename || sourceUri.split("/").pop() || "file"));
|
|
38
|
+
return copyFile(sourceUri, dest);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static async copyToDocuments(sourceUri: string, filename?: string): Promise<FileOperationResult> {
|
|
42
|
+
const dest = FileUtils.joinPaths(getDocumentDirectory(), FileUtils.generateUniqueFilename(filename || sourceUri.split("/").pop() || "file"));
|
|
43
|
+
return copyFile(sourceUri, dest);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
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 {
|
|
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 {
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Directory Service
|
|
3
|
+
* Single Responsibility: Manage directory operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Directory, Paths } 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
|
+
const dir = new Directory(uri);
|
|
15
|
+
dir.create({ intermediates: true, idempotent: true });
|
|
16
|
+
return true;
|
|
17
|
+
} catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* List directory contents
|
|
24
|
+
*/
|
|
25
|
+
export async function listDirectory(uri: string): Promise<string[]> {
|
|
26
|
+
try {
|
|
27
|
+
const dir = new Directory(uri);
|
|
28
|
+
const items = dir.list();
|
|
29
|
+
return items.map((item) => item.uri);
|
|
30
|
+
} catch {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get directory path by type
|
|
37
|
+
*/
|
|
38
|
+
export function getDirectoryPath(type: DirectoryType): string {
|
|
39
|
+
try {
|
|
40
|
+
switch (type) {
|
|
41
|
+
case "documentDirectory":
|
|
42
|
+
return Paths.document.uri;
|
|
43
|
+
case "cacheDirectory":
|
|
44
|
+
return Paths.cache.uri;
|
|
45
|
+
default:
|
|
46
|
+
return "";
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
return "";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get document directory path
|
|
55
|
+
*/
|
|
56
|
+
export function getDocumentDirectory(): string {
|
|
57
|
+
return getDirectoryPath("documentDirectory");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get cache directory path
|
|
62
|
+
*/
|
|
63
|
+
export function getCacheDirectory(): string {
|
|
64
|
+
return getDirectoryPath("cacheDirectory");
|
|
65
|
+
}
|
|
66
|
+
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Download Service
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { File, Paths, Directory } from "expo-file-system";
|
|
6
|
+
import type { FileOperationResult } from "../../domain/entities/File";
|
|
7
|
+
import { FileUtils } from "../../domain/entities/File";
|
|
8
|
+
import { SUPPORTED_DOWNLOAD_EXTENSIONS, DEFAULT_DOWNLOAD_EXTENSION } from "./download.constants";
|
|
9
|
+
import type { DownloadProgressCallback, DownloadWithProgressResult } from "./download.types";
|
|
10
|
+
|
|
11
|
+
const hashUrl = (url: string) => {
|
|
12
|
+
let hash = 0;
|
|
13
|
+
for (let i = 0; i < url.length; i++) hash = ((hash << 5) - hash + url.charCodeAt(i)) | 0;
|
|
14
|
+
return Math.abs(hash).toString(36);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const getExt = (url: string) => {
|
|
18
|
+
const parts = url.split("?")[0]?.split(".") || [];
|
|
19
|
+
const ext = parts.length > 1 ? parts.pop()?.toLowerCase() || DEFAULT_DOWNLOAD_EXTENSION : DEFAULT_DOWNLOAD_EXTENSION;
|
|
20
|
+
return (SUPPORTED_DOWNLOAD_EXTENSIONS as readonly string[]).includes(ext) ? ext : DEFAULT_DOWNLOAD_EXTENSION;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const getCacheUri = (url: string, dir: string) => FileUtils.joinPaths(dir, `cached_${hashUrl(url)}.${getExt(url)}`);
|
|
24
|
+
|
|
25
|
+
export async function downloadFile(url: string, dest?: string): Promise<FileOperationResult> {
|
|
26
|
+
try {
|
|
27
|
+
const destination = dest ? new File(dest) : new File(Paths.document, FileUtils.generateUniqueFilename("download"));
|
|
28
|
+
const res = await File.downloadFileAsync(url, destination, { idempotent: true });
|
|
29
|
+
return { success: true, uri: res.uri };
|
|
30
|
+
} catch (e: unknown) { return { success: false, error: e instanceof Error ? e.message : String(e) }; }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function downloadFileWithProgress(url: string, cacheDir: string, onProgress?: DownloadProgressCallback): Promise<DownloadWithProgressResult> {
|
|
34
|
+
try {
|
|
35
|
+
const dir = new Directory(cacheDir);
|
|
36
|
+
if (!dir.exists) dir.create({ intermediates: true, idempotent: true });
|
|
37
|
+
|
|
38
|
+
const destUri = getCacheUri(url, cacheDir);
|
|
39
|
+
if (new File(destUri).exists) return { success: true, uri: destUri, fromCache: true };
|
|
40
|
+
|
|
41
|
+
const response = await fetch(url);
|
|
42
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
43
|
+
|
|
44
|
+
const totalBytes = parseInt(response.headers.get("content-length") || "0", 10);
|
|
45
|
+
if (!response.body) return { ...(await downloadFile(url, destUri)), fromCache: false };
|
|
46
|
+
|
|
47
|
+
const reader = response.body.getReader();
|
|
48
|
+
const chunks: Uint8Array[] = [];
|
|
49
|
+
let received = 0;
|
|
50
|
+
|
|
51
|
+
while (true) {
|
|
52
|
+
const { done, value } = await reader.read();
|
|
53
|
+
if (done) break;
|
|
54
|
+
chunks.push(value);
|
|
55
|
+
received += value.length;
|
|
56
|
+
onProgress?.({ totalBytesWritten: received, totalBytesExpectedToWrite: totalBytes || received });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const all = new Uint8Array(received);
|
|
60
|
+
let pos = 0;
|
|
61
|
+
for (const c of chunks) { all.set(c, pos); pos += c.length; }
|
|
62
|
+
new File(destUri).write(all);
|
|
63
|
+
|
|
64
|
+
return { success: true, uri: destUri, fromCache: false };
|
|
65
|
+
} catch (e: unknown) { return { success: false, error: e instanceof Error ? e.message : String(e) }; }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const isUrlCached = (url: string, dir: string) => new File(getCacheUri(url, dir)).exists;
|
|
69
|
+
export const getCachedFileUri = (url: string, dir: string) => isUrlCached(url, dir) ? getCacheUri(url, dir) : null;
|
|
70
|
+
export const deleteCachedFile = (url: string, dir: string) => {
|
|
71
|
+
const f = new File(getCacheUri(url, dir));
|
|
72
|
+
if (f.exists) f.delete();
|
|
73
|
+
return true;
|
|
74
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { FileOperationResult, DownloadProgress } from "../../domain/entities/File";
|
|
2
|
+
|
|
3
|
+
export type DownloadProgressCallback = (progress: DownloadProgress) => void;
|
|
4
|
+
|
|
5
|
+
export interface DownloadWithProgressResult extends FileOperationResult {
|
|
6
|
+
fromCache?: boolean;
|
|
7
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encoding Service
|
|
3
|
+
* Single Responsibility: Handle file encoding/decoding operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { FileEncoding } from "../../domain/entities/File";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Encoding type for Expo FileSystem
|
|
10
|
+
*/
|
|
11
|
+
export type ExpoEncodingType = FileEncoding;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Convert FileEncoding to Expo FileSystem encoding type
|
|
15
|
+
*/
|
|
16
|
+
export function getEncodingType(encoding: FileEncoding): ExpoEncodingType {
|
|
17
|
+
return encoding;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Validate encoding type
|
|
22
|
+
*/
|
|
23
|
+
export function isValidEncoding(encoding: string): encoding is FileEncoding {
|
|
24
|
+
return encoding === "utf8" || encoding === "base64";
|
|
25
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Info Service
|
|
3
|
+
* Single Responsibility: Get file information and metadata
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { File } 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 file = new File(uri);
|
|
15
|
+
|
|
16
|
+
if (!file.exists) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
uri,
|
|
22
|
+
name: file.name,
|
|
23
|
+
size: file.size || 0,
|
|
24
|
+
exists: file.exists,
|
|
25
|
+
isDirectory: false,
|
|
26
|
+
modificationTime: file.modificationTime || 0,
|
|
27
|
+
};
|
|
28
|
+
} catch {
|
|
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 file = new File(uri);
|
|
39
|
+
return file.exists;
|
|
40
|
+
} catch {
|
|
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
|
+
|