feat: M1.1 完成 + M1.2 启动 — 全量更新
M1.1 收尾: - 24项 P0/P1/P2 bug 修复 (Rust 107 tests + Flutter 15 tests) - Flutter App v0.3: cupertino_icons 修复, 单元测试, 调试面板, APK 52.6MB - 示例插件完善: manifest.json + 请求/响应示范 + 7个测试 - API 文档重写 (以 routes.rs 为唯一权威) - MILESTONES.md 更新至 100% M1.2 启动: - P0: 插件管理 API 闭环 (handle_manager_message Custom 分支 + broadcast_plugin_states) - ServiceManager 集成测试 8/8 (tests/m1_2_service_manager.rs) - M1.2 测试计划 (docs/M1.2_TEST_PLAN.md, 18个E2E场景) - 动态插件系统: auto_rollback + version_manager GC + 路径穿越防护 总计: Rust 115/115 测试, Flutter 15/15 测试, 零 warning Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
@@ -11,9 +12,11 @@ import '../models/video_item.dart';
|
||||
import '../models/wifi_network.dart';
|
||||
import '../models/wifi_status.dart';
|
||||
|
||||
typedef UploadProgressCallback = void Function(double progress);
|
||||
|
||||
class HttpApiService {
|
||||
HttpApiService({required String baseUrl, http.Client? client})
|
||||
: _baseUrl = _normalizeBaseUrl(baseUrl),
|
||||
: _baseUrl = normalizeBaseUrl(baseUrl),
|
||||
_client = client ?? http.Client();
|
||||
|
||||
final http.Client _client;
|
||||
@@ -22,7 +25,7 @@ class HttpApiService {
|
||||
String get baseUrl => _baseUrl;
|
||||
|
||||
set baseUrl(String value) {
|
||||
_baseUrl = _normalizeBaseUrl(value);
|
||||
_baseUrl = normalizeBaseUrl(value);
|
||||
}
|
||||
|
||||
Uri _uri(String path, [Map<String, String>? queryParameters]) {
|
||||
@@ -229,6 +232,17 @@ class HttpApiService {
|
||||
return _uploadSingleFile('/api/videos/upload', file);
|
||||
}
|
||||
|
||||
Future<ApiResponse> uploadVideoWithProgress(
|
||||
File file, {
|
||||
UploadProgressCallback? onProgress,
|
||||
}) {
|
||||
return _uploadSingleFile(
|
||||
'/api/videos/upload',
|
||||
file,
|
||||
onProgress: onProgress,
|
||||
);
|
||||
}
|
||||
|
||||
Future<ApiResponse> uploadVideos(List<String> filePaths) async {
|
||||
if (filePaths.isEmpty) {
|
||||
throw const ApiException('未选择上传文件');
|
||||
@@ -377,17 +391,63 @@ class HttpApiService {
|
||||
String endpoint,
|
||||
File file, {
|
||||
String? directoryPath,
|
||||
UploadProgressCallback? onProgress,
|
||||
}) async {
|
||||
final request = http.MultipartRequest(
|
||||
'POST',
|
||||
_uri(endpoint, _pathQuery(directoryPath)),
|
||||
);
|
||||
request.files.add(await http.MultipartFile.fromPath('file', file.path));
|
||||
request.files.add(
|
||||
await _createMultipartFile(
|
||||
file,
|
||||
fieldName: 'file',
|
||||
onProgress: onProgress,
|
||||
),
|
||||
);
|
||||
final response = await http.Response.fromStream(await request.send());
|
||||
onProgress?.call(1);
|
||||
_ensureSuccess(response);
|
||||
return ApiResponse.fromJson(_decodeMap(response.body));
|
||||
}
|
||||
|
||||
Future<http.MultipartFile> _createMultipartFile(
|
||||
File file, {
|
||||
required String fieldName,
|
||||
UploadProgressCallback? onProgress,
|
||||
}) async {
|
||||
final length = await file.length();
|
||||
final filename = file.uri.pathSegments.isNotEmpty
|
||||
? file.uri.pathSegments.last
|
||||
: file.path;
|
||||
|
||||
if (onProgress == null) {
|
||||
return http.MultipartFile.fromPath(fieldName, file.path, filename: filename);
|
||||
}
|
||||
|
||||
var uploaded = 0;
|
||||
final stream = http.ByteStream(
|
||||
file.openRead().transform(
|
||||
StreamTransformer<List<int>, List<int>>.fromHandlers(
|
||||
handleData: (chunk, sink) {
|
||||
uploaded += chunk.length;
|
||||
if (length > 0) {
|
||||
final progress = (uploaded / length).clamp(0.0, 1.0);
|
||||
onProgress(progress);
|
||||
}
|
||||
sink.add(chunk);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return http.MultipartFile(
|
||||
fieldName,
|
||||
stream,
|
||||
length,
|
||||
filename: filename,
|
||||
);
|
||||
}
|
||||
|
||||
Future<ApiResponse> _postCommand(String path) async {
|
||||
final response = await _client.post(_uri(path));
|
||||
_ensureSuccess(response);
|
||||
@@ -477,7 +537,7 @@ class HttpApiService {
|
||||
_client.close();
|
||||
}
|
||||
|
||||
static String _normalizeBaseUrl(String raw) {
|
||||
static String normalizeBaseUrl(String raw) {
|
||||
final trimmed = raw.trim();
|
||||
if (trimmed.isEmpty) {
|
||||
throw const ApiException('baseUrl 不能为空');
|
||||
|
||||
Reference in New Issue
Block a user