|
| 1 | +// Copyright (c) 2025 Sendbird, Inc. All rights reserved. |
| 2 | + |
| 3 | +import 'dart:io'; |
| 4 | + |
| 5 | +import 'package:path_provider/path_provider.dart'; |
| 6 | +import 'package:sendbird_chat_sdk/sendbird_chat_sdk.dart'; |
| 7 | +import 'package:sendbird_chat_sdk/src/internal/main/chat/chat.dart'; |
| 8 | + |
| 9 | +import '../logger/sendbird_logger.dart'; |
| 10 | + |
| 11 | +class FileCacheManager { |
| 12 | + static const String _folderName = 'sendbird_chat_file_cache'; |
| 13 | + |
| 14 | + final Chat _chat; |
| 15 | + int retentionMinutes = 3 * 24 * 60; // 3 days |
| 16 | + |
| 17 | + FileCacheManager({required Chat chat}) : _chat = chat; |
| 18 | + |
| 19 | + bool _isForIOSCaching() { |
| 20 | + return Platform.isIOS && _chat.chatContext.options.useCollectionCaching; |
| 21 | + } |
| 22 | + |
| 23 | + Future<Directory> _getCacheDir() async { |
| 24 | + final appSupportDir = await getApplicationSupportDirectory(); |
| 25 | + final cacheDir = Directory('${appSupportDir.path}/$_folderName'); |
| 26 | + if (!cacheDir.existsSync()) { |
| 27 | + cacheDir.createSync(); |
| 28 | + } |
| 29 | + return cacheDir; |
| 30 | + } |
| 31 | + |
| 32 | + Future<String> _getCachedFilePath({ |
| 33 | + required String requestId, |
| 34 | + required File file, |
| 35 | + }) async { |
| 36 | + final cacheDir = await _getCacheDir(); |
| 37 | + String cachedFilePath = |
| 38 | + '${cacheDir.path}/${requestId}_${file.path.split('/').last}'; |
| 39 | + return cachedFilePath; |
| 40 | + } |
| 41 | + |
| 42 | + Future<void> copyFileForIOS({ |
| 43 | + required GroupChannel channel, |
| 44 | + required String? requestId, |
| 45 | + required File? originalFile, |
| 46 | + }) async { |
| 47 | + if (_isForIOSCaching() && requestId != null && originalFile != null) { |
| 48 | + try { |
| 49 | + final cachedFilePath = await _getCachedFilePath( |
| 50 | + requestId: requestId, |
| 51 | + file: originalFile, |
| 52 | + ); |
| 53 | + File cachedFile = File(cachedFilePath); |
| 54 | + if (cachedFile.existsSync()) { |
| 55 | + cachedFile.deleteSync(); |
| 56 | + } |
| 57 | + cachedFile = await originalFile.copy(cachedFilePath); |
| 58 | + |
| 59 | + final failedFileMessage = await _chat.dbManager.getFailedFileMessage( |
| 60 | + channelType: ChannelType.group, |
| 61 | + channelUrl: channel.channelUrl, |
| 62 | + requestId: requestId, |
| 63 | + ); |
| 64 | + |
| 65 | + if (failedFileMessage != null) { |
| 66 | + failedFileMessage.file = cachedFile; |
| 67 | + failedFileMessage.messageCreateParams?.fileInfo.file = cachedFile; |
| 68 | + |
| 69 | + await _chat.dbManager.upsertMessages([failedFileMessage]); |
| 70 | + } |
| 71 | + } catch (e) { |
| 72 | + sbLog.e(StackTrace.current, 'Failed to write cache file for iOS: $e'); |
| 73 | + } |
| 74 | + } |
| 75 | + } |
| 76 | + |
| 77 | + Future<void> removeCachedFileForIOS({ |
| 78 | + required String? requestId, |
| 79 | + required File? file, |
| 80 | + }) async { |
| 81 | + if (_isForIOSCaching() && requestId != null && file != null) { |
| 82 | + try { |
| 83 | + final cachedFilePath = await _getCachedFilePath( |
| 84 | + requestId: requestId, |
| 85 | + file: file, |
| 86 | + ); |
| 87 | + final cachedFile = File(cachedFilePath); |
| 88 | + if (cachedFile.existsSync()) { |
| 89 | + cachedFile.deleteSync(); |
| 90 | + } |
| 91 | + } catch (e) { |
| 92 | + sbLog.e(StackTrace.current, 'Failed to delete cache file for iOS: $e'); |
| 93 | + } |
| 94 | + } |
| 95 | + } |
| 96 | + |
| 97 | + Future<void> removeOldCachedFilesForIOS() async { |
| 98 | + if (_isForIOSCaching()) { |
| 99 | + try { |
| 100 | + final cacheDir = await _getCacheDir(); |
| 101 | + List<String> filePathList = []; |
| 102 | + final now = DateTime.now(); |
| 103 | + |
| 104 | + for (final fileEntity in cacheDir.listSync()) { |
| 105 | + if (fileEntity is File) { |
| 106 | + final stat = await fileEntity.stat(); |
| 107 | + if (now.difference(stat.accessed).inMinutes > retentionMinutes) { |
| 108 | + filePathList.add(fileEntity.path); |
| 109 | + } |
| 110 | + } |
| 111 | + } |
| 112 | + |
| 113 | + if (filePathList.isNotEmpty) { |
| 114 | + try { |
| 115 | + await _chat.dbManager.removeOldFailedMessages(filePathList); |
| 116 | + } catch (e) { |
| 117 | + sbLog.e(StackTrace.current, |
| 118 | + 'Failed to remove failed messages with old cache files: $e'); |
| 119 | + } |
| 120 | + } |
| 121 | + } catch (e) { |
| 122 | + sbLog.e(StackTrace.current, 'Failed to clean old cache files: $e'); |
| 123 | + } |
| 124 | + } |
| 125 | + } |
| 126 | + |
| 127 | + Future<void> clearCacheForIOS() async { |
| 128 | + if (_isForIOSCaching()) { |
| 129 | + try { |
| 130 | + final cacheDir = await _getCacheDir(); |
| 131 | + if (cacheDir.existsSync()) { |
| 132 | + cacheDir.deleteSync(recursive: true); |
| 133 | + } |
| 134 | + } catch (e) { |
| 135 | + sbLog.e(StackTrace.current, 'Failed to clear cache for iOS: $e'); |
| 136 | + } |
| 137 | + } |
| 138 | + } |
| 139 | +} |
0 commit comments