mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-05-11 12:37:46 +08:00
feat: parallel upload & download image (#556)
* feat: parallel upload file * feat: parallel download file
This commit is contained in:
committed by
GitHub
parent
25995b0ed6
commit
76d031e8d1
@@ -231,22 +231,24 @@ class MsgHttp {
|
|||||||
dynamic path,
|
dynamic path,
|
||||||
String? category,
|
String? category,
|
||||||
String? biz,
|
String? biz,
|
||||||
|
CancelToken? cancelToken,
|
||||||
}) async {
|
}) async {
|
||||||
String csrf = await Request.getCsrf();
|
final file = await MultipartFile.fromFile(path);
|
||||||
Map<String, dynamic> data = await WbiSign.makSign({
|
Map<String, dynamic> data = {
|
||||||
'file_up': await MultipartFile.fromFile(path),
|
'file_up': file,
|
||||||
if (category != null) 'category': category,
|
if (category != null) 'category': category,
|
||||||
if (biz != null) 'biz': biz,
|
if (biz != null) 'biz': biz,
|
||||||
'csrf': csrf,
|
'csrf': await Request.getCsrf(),
|
||||||
});
|
};
|
||||||
var res = await Request().post(
|
var res = await Request().post(
|
||||||
Api.uploadBfs,
|
Api.uploadBfs,
|
||||||
data: FormData.fromMap(data),
|
data: FormData.fromMap(data),
|
||||||
|
cancelToken: cancelToken,
|
||||||
);
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {
|
return {
|
||||||
'status': true,
|
'status': true,
|
||||||
'data': res.data['data'],
|
'data': res.data['data']..['img_size'] = file.length,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactivevie
|
|||||||
import 'package:PiliPlus/http/msg.dart';
|
import 'package:PiliPlus/http/msg.dart';
|
||||||
import 'package:PiliPlus/utils/extension.dart';
|
import 'package:PiliPlus/utils/extension.dart';
|
||||||
import 'package:chat_bottom_container/chat_bottom_container.dart';
|
import 'package:chat_bottom_container/chat_bottom_container.dart';
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
import 'package:easy_debounce/easy_throttle.dart';
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
@@ -161,32 +162,34 @@ abstract class CommonPublishPageState<T extends CommonPublishPage>
|
|||||||
|
|
||||||
Future onPublish() async {
|
Future onPublish() async {
|
||||||
feedBack();
|
feedBack();
|
||||||
List? pictures;
|
List<Map<String, dynamic>>? pictures;
|
||||||
if (pathList.isNotEmpty) {
|
if (pathList.isNotEmpty) {
|
||||||
pictures = [];
|
SmartDialog.showLoading(msg: '正在上传图片...');
|
||||||
for (int i = 0; i < pathList.length; i++) {
|
final cancelToken = CancelToken();
|
||||||
SmartDialog.showLoading(msg: '正在上传图片: ${i + 1}/${pathList.length}');
|
try {
|
||||||
dynamic result = await MsgHttp.uploadBfs(
|
pictures = await Future.wait<Map<String, dynamic>>(
|
||||||
path: pathList[i],
|
pathList.map((path) async {
|
||||||
category: 'daily',
|
Map result = await MsgHttp.uploadBfs(
|
||||||
biz: 'new_dyn',
|
path: path,
|
||||||
);
|
category: 'daily',
|
||||||
if (result['status']) {
|
biz: 'new_dyn',
|
||||||
int imageSize = await File(pathList[i]).length();
|
cancelToken: cancelToken,
|
||||||
pictures.add({
|
);
|
||||||
'img_width': result['data']['image_width'],
|
if (!result['status']) throw HttpException(result['msg']);
|
||||||
'img_height': result['data']['image_height'],
|
return {
|
||||||
'img_size': imageSize / 1024,
|
'img_width': result['data']['image_width'],
|
||||||
'img_src': result['data']['image_url'],
|
'img_height': result['data']['image_height'],
|
||||||
});
|
'img_size': result['data']['img_size'] / 1024,
|
||||||
} else {
|
'img_src': result['data']['image_url'],
|
||||||
SmartDialog.dismiss();
|
};
|
||||||
SmartDialog.showToast(result['msg']);
|
}).toList(),
|
||||||
return;
|
eagerError: true);
|
||||||
}
|
SmartDialog.dismiss();
|
||||||
if (i == pathList.length - 1) {
|
} on HttpException catch (e) {
|
||||||
SmartDialog.dismiss();
|
cancelToken.cancel();
|
||||||
}
|
SmartDialog.dismiss();
|
||||||
|
SmartDialog.showToast(e.message);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onCustomPublish(message: editController.text, pictures: pictures);
|
onCustomPublish(message: editController.text, pictures: pictures);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
|
||||||
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
|
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
|
||||||
import 'package:PiliPlus/http/msg.dart';
|
import 'package:PiliPlus/http/msg.dart';
|
||||||
import 'package:PiliPlus/models/msg/session.dart';
|
import 'package:PiliPlus/models/msg/session.dart';
|
||||||
@@ -273,7 +272,6 @@ class _WhisperDetailPageState
|
|||||||
biz: 'im',
|
biz: 'im',
|
||||||
);
|
);
|
||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
int imageSize = await File(pickedFile.path).length();
|
|
||||||
String mimeType = lookupMimeType(pickedFile.path)
|
String mimeType = lookupMimeType(pickedFile.path)
|
||||||
?.split('/')
|
?.split('/')
|
||||||
.getOrNull(1) ??
|
.getOrNull(1) ??
|
||||||
@@ -284,7 +282,7 @@ class _WhisperDetailPageState
|
|||||||
'width': result['data']['image_width'],
|
'width': result['data']['image_width'],
|
||||||
'imageType': mimeType,
|
'imageType': mimeType,
|
||||||
'original': 1,
|
'original': 1,
|
||||||
'size': imageSize / 1024,
|
'size': result['data']['img_size'] / 1024,
|
||||||
};
|
};
|
||||||
SmartDialog.showLoading(msg: '正在发送');
|
SmartDialog.showLoading(msg: '正在发送');
|
||||||
await _whisperDetailController.sendMsg(
|
await _whisperDetailController.sendMsg(
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ class AccountManager extends Interceptor {
|
|||||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||||
final path = options.path;
|
final path = options.path;
|
||||||
|
|
||||||
if (path.startsWith(GStorage.blockServer)) return handler.next(options);
|
if (_skipCookie(path)) return handler.next(options);
|
||||||
|
|
||||||
final Account account = options.extra['account'] ?? _findAccount(path);
|
final Account account = options.extra['account'] ?? _findAccount(path);
|
||||||
|
|
||||||
@@ -148,8 +148,7 @@ class AccountManager extends Interceptor {
|
|||||||
@override
|
@override
|
||||||
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
||||||
final path = response.requestOptions.path;
|
final path = response.requestOptions.path;
|
||||||
if (path.startsWith(HttpString.appBaseUrl) ||
|
if (path.startsWith(HttpString.appBaseUrl) || _skipCookie(path)) {
|
||||||
path.startsWith(GStorage.blockServer)) {
|
|
||||||
return handler.next(response);
|
return handler.next(response);
|
||||||
} else {
|
} else {
|
||||||
_saveCookies(response).then((_) => handler.next(response)).catchError(
|
_saveCookies(response).then((_) => handler.next(response)).catchError(
|
||||||
@@ -167,19 +166,25 @@ class AccountManager extends Interceptor {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void onError(DioException err, ErrorInterceptorHandler handler) {
|
void onError(DioException err, ErrorInterceptorHandler handler) {
|
||||||
|
const List<String> skipShow = [
|
||||||
|
'heartbeat',
|
||||||
|
'history/report',
|
||||||
|
'roomEntryAction',
|
||||||
|
'seg.so',
|
||||||
|
'online/total',
|
||||||
|
'github',
|
||||||
|
'hdslb.com',
|
||||||
|
'biliimg.com'
|
||||||
|
];
|
||||||
String url = err.requestOptions.uri.toString();
|
String url = err.requestOptions.uri.toString();
|
||||||
debugPrint('🌹🌹ApiInterceptor: $url');
|
debugPrint('🌹🌹ApiInterceptor: $url');
|
||||||
if (url.contains('heartbeat') ||
|
if (skipShow.any((i) => url.contains(i)) ||
|
||||||
url.contains('history/report') ||
|
|
||||||
url.contains('roomEntryAction') ||
|
|
||||||
url.contains('seg.so') ||
|
|
||||||
url.contains('online/total') ||
|
|
||||||
url.contains('github') ||
|
|
||||||
(url.contains('skipSegments') && err.requestOptions.method == 'GET')) {
|
(url.contains('skipSegments') && err.requestOptions.method == 'GET')) {
|
||||||
// skip
|
// skip
|
||||||
} else {
|
} else {
|
||||||
dioError(err).then((res) => SmartDialog.showToast(res + url));
|
dioError(err).then((res) => SmartDialog.showToast(res + url));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err.response != null &&
|
if (err.response != null &&
|
||||||
!err.response!.requestOptions.path.startsWith(HttpString.appBaseUrl)) {
|
!err.response!.requestOptions.path.startsWith(HttpString.appBaseUrl)) {
|
||||||
_saveCookies(err.response!).then((_) => handler.next(err)).catchError(
|
_saveCookies(err.response!).then((_) => handler.next(err)).catchError(
|
||||||
@@ -231,6 +236,12 @@ class AccountManager extends Interceptor {
|
|||||||
await account.onChange();
|
await account.onChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool _skipCookie(String path) {
|
||||||
|
return path.startsWith(GStorage.blockServer) ||
|
||||||
|
path.contains('hdslb.com') ||
|
||||||
|
path.contains('biliimg.com');
|
||||||
|
}
|
||||||
|
|
||||||
Account _findAccount(String path) => loginApi.contains(path)
|
Account _findAccount(String path) => loginApi.contains(path)
|
||||||
? AnonymousAccount()
|
? AnonymousAccount()
|
||||||
: Accounts.get(AccountType.values.firstWhere(
|
: Accounts.get(AccountType.values.firstWhere(
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:typed_data';
|
|||||||
|
|
||||||
import 'package:PiliPlus/http/init.dart';
|
import 'package:PiliPlus/http/init.dart';
|
||||||
import 'package:PiliPlus/utils/extension.dart';
|
import 'package:PiliPlus/utils/extension.dart';
|
||||||
|
import 'package:PiliPlus/utils/utils.dart';
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -161,55 +162,59 @@ class DownloadUtils {
|
|||||||
List<String> imgList, {
|
List<String> imgList, {
|
||||||
String imgType = 'cover',
|
String imgType = 'cover',
|
||||||
}) async {
|
}) async {
|
||||||
|
if (!await checkPermissionDependOnSdkInt(context)) return;
|
||||||
|
final cancelToken = CancelToken();
|
||||||
|
SmartDialog.showLoading(
|
||||||
|
msg: '正在下载原图',
|
||||||
|
clickMaskDismiss: true,
|
||||||
|
onDismiss: () {
|
||||||
|
cancelToken.cancel();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
final picName =
|
||||||
|
"${imgType}_${DateTime.now().toString().substring(0, 19).replaceAll(' ', '_').replaceAll(':', '-')}";
|
||||||
try {
|
try {
|
||||||
if (!await checkPermissionDependOnSdkInt(context)) {
|
await Future.wait(imgList.map((i) async {
|
||||||
return;
|
|
||||||
}
|
|
||||||
bool dispose = false;
|
|
||||||
for (int i = 0; i < imgList.length; i++) {
|
|
||||||
SmartDialog.showLoading(
|
|
||||||
msg:
|
|
||||||
'正在下载原图${imgList.length > 1 ? '${i + 1}/${imgList.length}' : ''}',
|
|
||||||
clickMaskDismiss: true,
|
|
||||||
onDismiss: () {
|
|
||||||
dispose = true;
|
|
||||||
if (i != imgList.length - 1) {
|
|
||||||
SmartDialog.showToast('已取消下载剩余图片');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
var response = await Request().get(
|
var response = await Request().get(
|
||||||
imgList[i].http2https,
|
i.http2https,
|
||||||
options: Options(responseType: ResponseType.bytes),
|
options: Options(responseType: ResponseType.bytes),
|
||||||
|
cancelToken: cancelToken,
|
||||||
);
|
);
|
||||||
String picName =
|
|
||||||
"${imgType}_${DateTime.now().toString().replaceAll(' ', '_').replaceAll(':', '-').split('.').first}";
|
if (response.data is Map) {
|
||||||
|
throw HttpException(response.data['message']);
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = '${picName}_${Utils.getFileName(i)}';
|
||||||
|
|
||||||
final SaveResult result = await SaverGallery.saveImage(
|
final SaveResult result = await SaverGallery.saveImage(
|
||||||
Uint8List.fromList(response.data),
|
response.data as Uint8List,
|
||||||
quality: 100,
|
quality: 100,
|
||||||
fileName: picName,
|
fileName: name,
|
||||||
// 保存到 PiliPlus文件夹
|
// 保存到 PiliPlus文件夹
|
||||||
androidRelativePath: "Pictures/PiliPlus",
|
androidRelativePath: "Pictures/PiliPlus",
|
||||||
skipIfExists: false,
|
skipIfExists: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
// SmartDialog.dismiss();
|
// SmartDialog.dismiss();
|
||||||
// SmartDialog.showLoading(msg: '正在保存图片至图库');
|
// SmartDialog.showLoading(msg: '正在保存图片至图库');
|
||||||
if (i == imgList.length - 1) {
|
|
||||||
SmartDialog.dismiss();
|
|
||||||
}
|
|
||||||
if (result.isSuccess) {
|
if (result.isSuccess) {
|
||||||
SmartDialog.showToast('「$picName」已保存 ');
|
// SmartDialog.showToast('「$name」已保存');
|
||||||
} else {
|
} else {
|
||||||
SmartDialog.showToast('保存失败,${result.errorMessage}');
|
throw Exception('保存失败,${result.errorMessage}');
|
||||||
}
|
}
|
||||||
if (dispose) {
|
}), eagerError: true);
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} catch (err) {
|
|
||||||
SmartDialog.dismiss();
|
SmartDialog.dismiss();
|
||||||
SmartDialog.showToast(err.toString());
|
SmartDialog.showToast('图片已保存');
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
SmartDialog.dismiss();
|
||||||
|
if (cancelToken.isCancelled) {
|
||||||
|
SmartDialog.showToast('已取消下载');
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast(e.toString());
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1670,4 +1670,8 @@ class Utils {
|
|||||||
List<int> randomBytes = generateRandomBytes(minLength, maxLength);
|
List<int> randomBytes = generateRandomBytes(minLength, maxLength);
|
||||||
return base64.encode(randomBytes);
|
return base64.encode(randomBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static String getFileName(String uri) {
|
||||||
|
return uri.substring(uri.lastIndexOf('/') + 1, uri.lastIndexOf('.'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
// import md5 from 'md5'
|
// import md5 from 'md5'
|
||||||
// import axios from 'axios'
|
// import axios from 'axios'
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'package:PiliPlus/utils/utils.dart';
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:synchronized/synchronized.dart';
|
import 'package:synchronized/synchronized.dart';
|
||||||
@@ -66,10 +67,6 @@ class WbiSign {
|
|||||||
md5.convert(utf8.encode(queryStr + mixinKey)).toString(); // 计算 w_rid
|
md5.convert(utf8.encode(queryStr + mixinKey)).toString(); // 计算 w_rid
|
||||||
}
|
}
|
||||||
|
|
||||||
static String getFileName(String uri) {
|
|
||||||
return uri.substring(uri.lastIndexOf('/') + 1, uri.lastIndexOf('.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取最新的 img_key 和 sub_key 可以从缓存中获取
|
// 获取最新的 img_key 和 sub_key 可以从缓存中获取
|
||||||
static Future<String> getWbiKeys() async {
|
static Future<String> getWbiKeys() async {
|
||||||
final DateTime nowDate = DateTime.now();
|
final DateTime nowDate = DateTime.now();
|
||||||
@@ -86,8 +83,8 @@ class WbiSign {
|
|||||||
if (resp.data['code'] == 0) {
|
if (resp.data['code'] == 0) {
|
||||||
final wbiUrls = resp.data['data']['wbi_img'];
|
final wbiUrls = resp.data['data']['wbi_img'];
|
||||||
|
|
||||||
mixinKey = getMixinKey(
|
mixinKey = getMixinKey(Utils.getFileName(wbiUrls['img_url']) +
|
||||||
getFileName(wbiUrls['img_url']) + getFileName(wbiUrls['sub_url']));
|
Utils.getFileName(wbiUrls['sub_url']));
|
||||||
|
|
||||||
localCache.put(LocalCacheKey.mixinKey, mixinKey);
|
localCache.put(LocalCacheKey.mixinKey, mixinKey);
|
||||||
localCache.put(LocalCacheKey.timeStamp, nowDate.millisecondsSinceEpoch);
|
localCache.put(LocalCacheKey.timeStamp, nowDate.millisecondsSinceEpoch);
|
||||||
|
|||||||
Reference in New Issue
Block a user