♻️ Refactored the upload files according to new backend tasks
This commit is contained in:
@@ -6,7 +6,7 @@ part of 'messages_notifier.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$messagesNotifierHash() => r'6adefd9152cdd686c2a863964993f24c42d405b5';
|
||||
String _$messagesNotifierHash() => r'b1d5d583199941d55dfdc707e1a22eec9616b7f1';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
||||
317
lib/pods/upload_tasks.dart
Normal file
317
lib/pods/upload_tasks.dart
Normal file
@@ -0,0 +1,317 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
import 'package:cross_file/cross_file.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:island/models/file.dart';
|
||||
import 'package:island/models/upload_task.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/pods/websocket.dart';
|
||||
import 'package:island/services/file_uploader.dart';
|
||||
|
||||
final uploadTasksProvider =
|
||||
StateNotifierProvider<UploadTasksNotifier, List<UploadTask>>(
|
||||
(ref) => UploadTasksNotifier(ref),
|
||||
);
|
||||
|
||||
class UploadTasksNotifier extends StateNotifier<List<UploadTask>> {
|
||||
final Ref ref;
|
||||
StreamSubscription? _websocketSubscription;
|
||||
|
||||
UploadTasksNotifier(this.ref) : super([]) {
|
||||
_listenToWebSocket();
|
||||
}
|
||||
|
||||
void _listenToWebSocket() {
|
||||
final WebSocketService websocketService = ref.read(websocketProvider);
|
||||
_websocketSubscription = websocketService.dataStream.listen(
|
||||
_handleWebSocketPacket,
|
||||
);
|
||||
}
|
||||
|
||||
void _handleWebSocketPacket(dynamic packet) {
|
||||
if (packet.type.startsWith('upload.')) {
|
||||
final data = packet.data;
|
||||
if (data == null) return;
|
||||
|
||||
final taskId = data['task_id'] as String?;
|
||||
if (taskId == null) return;
|
||||
|
||||
switch (packet.type) {
|
||||
case 'upload.progress':
|
||||
_handleProgressUpdate(taskId, data);
|
||||
break;
|
||||
case 'upload.completed':
|
||||
_handleUploadCompleted(taskId, data);
|
||||
break;
|
||||
case 'upload.failed':
|
||||
_handleUploadFailed(taskId, data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _handleProgressUpdate(String taskId, Map<String, dynamic> data) {
|
||||
final uploadedChunks = data['chunksUploaded'] as int? ?? 0;
|
||||
final uploadedBytes =
|
||||
(data['progress'] as num? ?? 0.0) /
|
||||
100.0 *
|
||||
(data['fileSize'] as int? ?? 0);
|
||||
|
||||
state =
|
||||
state.map((task) {
|
||||
if (task.taskId == taskId) {
|
||||
return task.copyWith(
|
||||
uploadedChunks: uploadedChunks,
|
||||
uploadedBytes: uploadedBytes.toInt(),
|
||||
status: UploadTaskStatus.inProgress,
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
}
|
||||
return task;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
void _handleUploadCompleted(String taskId, Map<String, dynamic> data) {
|
||||
final fileData = data['file'];
|
||||
if (fileData != null) {
|
||||
// Assuming the file data comes in the expected format
|
||||
// You might need to adjust this based on the actual API response
|
||||
}
|
||||
|
||||
state =
|
||||
state.map((task) {
|
||||
if (task.taskId == taskId) {
|
||||
return task.copyWith(
|
||||
status: UploadTaskStatus.completed,
|
||||
uploadedChunks: task.totalChunks,
|
||||
uploadedBytes: task.fileSize,
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
}
|
||||
return task;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
void _handleUploadFailed(String taskId, Map<String, dynamic> data) {
|
||||
final errorMessage = data['error'] as String? ?? 'Upload failed';
|
||||
|
||||
state =
|
||||
state.map((task) {
|
||||
if (task.taskId == taskId) {
|
||||
return task.copyWith(
|
||||
status: UploadTaskStatus.failed,
|
||||
errorMessage: errorMessage,
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
}
|
||||
return task;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
void addUploadTask(UploadTask task) {
|
||||
state = [...state, task];
|
||||
}
|
||||
|
||||
void updateTaskStatus(
|
||||
String taskId,
|
||||
UploadTaskStatus status, {
|
||||
String? errorMessage,
|
||||
}) {
|
||||
state =
|
||||
state.map((task) {
|
||||
if (task.taskId == taskId) {
|
||||
return task.copyWith(
|
||||
status: status,
|
||||
errorMessage: errorMessage,
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
}
|
||||
return task;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
void removeTask(String taskId) {
|
||||
state = state.where((task) => task.taskId != taskId).toList();
|
||||
}
|
||||
|
||||
UploadTask? getTask(String taskId) {
|
||||
return state.where((task) => task.taskId == taskId).firstOrNull;
|
||||
}
|
||||
|
||||
List<UploadTask> getActiveTasks() {
|
||||
return state
|
||||
.where(
|
||||
(task) =>
|
||||
task.status == UploadTaskStatus.pending ||
|
||||
task.status == UploadTaskStatus.inProgress ||
|
||||
task.status == UploadTaskStatus.paused,
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_websocketSubscription?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// Provider for the enhanced FileUploader that integrates with upload tasks
|
||||
final enhancedFileUploaderProvider = Provider<EnhancedFileUploader>((ref) {
|
||||
final dio = ref.watch(apiClientProvider);
|
||||
return EnhancedFileUploader(dio, ref);
|
||||
});
|
||||
|
||||
class EnhancedFileUploader extends FileUploader {
|
||||
final Ref ref;
|
||||
|
||||
EnhancedFileUploader(super.client, this.ref);
|
||||
|
||||
/// Reads the next chunk from a stream subscription.
|
||||
Future<Uint8List> _readNextChunkFromStream(
|
||||
StreamSubscription<List<int>> subscription,
|
||||
int size,
|
||||
) async {
|
||||
final completer = Completer<Uint8List>();
|
||||
final buffer = <int>[];
|
||||
int remaining = size;
|
||||
|
||||
void onData(List<int> data) {
|
||||
buffer.addAll(data);
|
||||
remaining -= data.length;
|
||||
if (remaining <= 0) {
|
||||
subscription.pause();
|
||||
completer.complete(Uint8List.fromList(buffer.sublist(0, size)));
|
||||
}
|
||||
}
|
||||
|
||||
void onDone() {
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete(Uint8List.fromList(buffer));
|
||||
}
|
||||
}
|
||||
|
||||
subscription.onData(onData);
|
||||
subscription.onDone(onDone);
|
||||
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<SnCloudFile> uploadFile({
|
||||
required dynamic fileData,
|
||||
required String fileName,
|
||||
required String contentType,
|
||||
String? poolId,
|
||||
String? bundleId,
|
||||
String? encryptPassword,
|
||||
String? expiredAt,
|
||||
int? customChunkSize,
|
||||
Function(double? progress, Duration estimate)? onProgress,
|
||||
}) async {
|
||||
// Step 1: Create upload task
|
||||
onProgress?.call(null, Duration.zero);
|
||||
final createResponse = await createUploadTask(
|
||||
fileData: fileData,
|
||||
fileName: fileName,
|
||||
contentType: contentType,
|
||||
poolId: poolId,
|
||||
bundleId: bundleId,
|
||||
encryptPassword: encryptPassword,
|
||||
expiredAt: expiredAt,
|
||||
chunkSize: customChunkSize,
|
||||
);
|
||||
|
||||
if (createResponse['file_exists'] == true) {
|
||||
// File already exists, return the existing file
|
||||
return SnCloudFile.fromJson(createResponse['file']);
|
||||
}
|
||||
|
||||
final taskId = createResponse['task_id'] as String;
|
||||
final chunkSize = createResponse['chunk_size'] as int;
|
||||
final chunksCount = createResponse['chunks_count'] as int;
|
||||
int totalSize;
|
||||
if (fileData is XFile) {
|
||||
totalSize = await fileData.length();
|
||||
} else if (fileData is Uint8List) {
|
||||
totalSize = fileData.length;
|
||||
} else {
|
||||
throw ArgumentError('Invalid fileData type');
|
||||
}
|
||||
|
||||
// Create upload task and add to state
|
||||
final uploadTask = UploadTask(
|
||||
id: DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
taskId: taskId,
|
||||
fileName: fileName,
|
||||
contentType: contentType,
|
||||
fileSize: totalSize,
|
||||
uploadedBytes: 0,
|
||||
totalChunks: chunksCount,
|
||||
uploadedChunks: 0,
|
||||
status: UploadTaskStatus.pending,
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
poolId: poolId,
|
||||
bundleId: bundleId,
|
||||
encryptPassword: encryptPassword,
|
||||
expiredAt: expiredAt,
|
||||
);
|
||||
|
||||
ref.read(uploadTasksProvider.notifier).addUploadTask(uploadTask);
|
||||
|
||||
// Step 2: Upload chunks
|
||||
int bytesUploaded = 0;
|
||||
if (fileData is XFile) {
|
||||
// Use stream for XFile
|
||||
final subscription = fileData.openRead().listen(null);
|
||||
subscription.pause();
|
||||
for (int i = 0; i < chunksCount; i++) {
|
||||
subscription.resume();
|
||||
final chunkData = await _readNextChunkFromStream(
|
||||
subscription,
|
||||
chunkSize,
|
||||
);
|
||||
await uploadChunk(
|
||||
taskId: taskId,
|
||||
chunkIndex: i,
|
||||
chunkData: chunkData,
|
||||
onSendProgress: (sent, total) {
|
||||
final overallProgress = (bytesUploaded + sent) / totalSize;
|
||||
onProgress?.call(overallProgress, Duration.zero);
|
||||
},
|
||||
);
|
||||
bytesUploaded += chunkData.length;
|
||||
}
|
||||
subscription.cancel();
|
||||
} else if (fileData is Uint8List) {
|
||||
// Use old way for Uint8List
|
||||
final chunks = <Uint8List>[];
|
||||
for (int i = 0; i < fileData.length; i += chunkSize) {
|
||||
final end =
|
||||
i + chunkSize > fileData.length ? fileData.length : i + chunkSize;
|
||||
chunks.add(Uint8List.fromList(fileData.sublist(i, end)));
|
||||
}
|
||||
|
||||
// Upload each chunk
|
||||
for (int i = 0; i < chunks.length; i++) {
|
||||
await uploadChunk(
|
||||
taskId: taskId,
|
||||
chunkIndex: i,
|
||||
chunkData: chunks[i],
|
||||
onSendProgress: (sent, total) {
|
||||
final overallProgress = (bytesUploaded + sent) / totalSize;
|
||||
onProgress?.call(overallProgress, Duration.zero);
|
||||
},
|
||||
);
|
||||
bytesUploaded += chunks[i].length;
|
||||
}
|
||||
} else {
|
||||
throw ArgumentError('Invalid fileData type');
|
||||
}
|
||||
|
||||
// Step 3: Complete upload
|
||||
onProgress?.call(null, Duration.zero);
|
||||
return await completeUpload(taskId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user