解决了语音上传到问题,接下来要解决下载播放问题

This commit is contained in:
joywayer
2025-06-15 12:36:47 +08:00
parent bba3ed1cb4
commit c11fc62bf1
513 changed files with 31197 additions and 2969 deletions

126
Pods/Qiniu/QiniuSDK/Storage/QNBaseUpload.h generated Normal file
View File

@@ -0,0 +1,126 @@
//
// QNBaseUpload.h
// QiniuSDK
//
// Created by WorkSpace_Sun on 2020/4/19.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "QNConfiguration.h"
#import "QNCrc32.h"
#import "QNRecorderDelegate.h"
#import "QNUpToken.h"
#import "QNUrlSafeBase64.h"
#import "QNAsyncRun.h"
#import "QNUploadManager.h"
#import "QNUploadOption.h"
#import "QNZone.h"
#import "QNUploadSource.h"
#import "QNUploadRequestMetrics.h"
extern NSString *const QNUploadUpTypeForm;
extern NSString *const QNUploadUpTypeResumableV1;
extern NSString *const QNUploadUpTypeResumableV2;
typedef void (^QNUpTaskCompletionHandler)(QNResponseInfo *info, NSString *key, QNUploadTaskMetrics *metrics, NSDictionary *resp);
@interface QNBaseUpload : NSObject
@property (nonatomic, copy, readonly) NSString *upType;
@property (nonatomic, copy, readonly) NSString *key;
@property (nonatomic, copy, readonly) NSString *fileName;
@property (nonatomic, strong, readonly) NSData *data;
@property (nonatomic, strong, readonly) id <QNUploadSource> uploadSource;
@property (nonatomic, strong, readonly) QNUpToken *token;
@property (nonatomic, strong, readonly) QNUploadOption *option;
@property (nonatomic, strong, readonly) QNConfiguration *config;
@property (nonatomic, strong, readonly) id <QNRecorderDelegate> recorder;
@property (nonatomic, copy, readonly) NSString *recorderKey;
@property (nonatomic, strong, readonly) QNUpTaskCompletionHandler completionHandler;
@property (nonatomic, strong, readonly) QNUploadRegionRequestMetrics *currentRegionRequestMetrics;
@property (nonatomic, strong, readonly) QNUploadTaskMetrics *metrics;
//MARK:-- 构造函数
/// file构造函数
/// @param uploadSource 文件源
/// @param key 上传key
/// @param token 上传token
/// @param option 上传option
/// @param config 上传config
/// @param recorder 断点续传记录信息
/// @param recorderKey 断电上传信息保存的key值需确保唯一性
/// @param completionHandler 上传完成回调
- (instancetype)initWithSource:(id<QNUploadSource>)uploadSource
key:(NSString *)key
token:(QNUpToken *)token
option:(QNUploadOption *)option
configuration:(QNConfiguration *)config
recorder:(id<QNRecorderDelegate>)recorder
recorderKey:(NSString *)recorderKey
completionHandler:(QNUpTaskCompletionHandler)completionHandler;
/// data 构造函数
/// @param data 上传data流
/// @param key 上传key
/// @param fileName 上传fileName
/// @param token 上传token
/// @param option 上传option
/// @param config 上传config
/// @param completionHandler 上传完成回调
- (instancetype)initWithData:(NSData *)data
key:(NSString *)key
fileName:(NSString *)fileName
token:(QNUpToken *)token
option:(QNUploadOption *)option
configuration:(QNConfiguration *)config
completionHandler:(QNUpTaskCompletionHandler)completionHandler;
/// 初始化数据
- (void)initData;
//MARK: -- 上传
/// 开始上传流程
- (void)run;
/// 准备上传
- (int)prepareToUpload;
/// 重新加载上传数据
- (BOOL)reloadUploadInfo;
/// 开始上传
- (void)startToUpload;
/// 切换区域
- (BOOL)switchRegionAndUpload;
// 根据错误信息进行切换region并上传return:是否切换region并上传
- (BOOL)switchRegionAndUploadIfNeededWithErrorResponse:(QNResponseInfo *)errorResponseInfo;
/// 上传结束调用回调方法,在上传结束时调用,该方法内部会调用回调,已通知上层上传结束
/// @param info 上传返回信息
/// @param response 上传字典信息
- (void)complete:(QNResponseInfo *)info
response:(NSDictionary *)response;
//MARK: -- 机房管理
/// 在区域列表头部插入一个区域
- (void)insertRegionAtFirst:(id <QNUploadRegion>)region;
/// 切换区域
- (BOOL)switchRegion;
/// 获取目标区域
- (id <QNUploadRegion>)getTargetRegion;
/// 获取当前区域
- (id <QNUploadRegion>)getCurrentRegion;
//MARK: -- upLog
// 一个上传流程可能会发起多个上传操作上传多个分片每个上传操作均是以一个Region的host做重试操作
- (void)addRegionRequestMetricsOfOneFlow:(QNUploadRegionRequestMetrics *)metrics;
@end

295
Pods/Qiniu/QiniuSDK/Storage/QNBaseUpload.m generated Normal file
View File

@@ -0,0 +1,295 @@
//
// QNBaseUpload.m
// QiniuSDK
//
// Created by WorkSpace_Sun on 2020/4/19.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNAutoZone.h"
#import "QNZoneInfo.h"
#import "QNResponseInfo.h"
#import "QNDefine.h"
#import "QNBaseUpload.h"
#import "QNUploadDomainRegion.h"
NSString *const QNUploadUpTypeForm = @"form";
NSString *const QNUploadUpTypeResumableV1 = @"resumable_v1";
NSString *const QNUploadUpTypeResumableV2 = @"resumable_v2";
@interface QNBaseUpload ()
@property (nonatomic, strong) QNBaseUpload *strongSelf;
@property (nonatomic, copy) NSString *key;
@property (nonatomic, copy) NSString *fileName;
@property (nonatomic, strong) NSData *data;
@property (nonatomic, strong) id <QNUploadSource> uploadSource;
@property (nonatomic, strong) QNUpToken *token;
@property (nonatomic, copy) NSString *identifier;
@property (nonatomic, strong) QNUploadOption *option;
@property (nonatomic, strong) QNConfiguration *config;
@property (nonatomic, strong) id <QNRecorderDelegate> recorder;
@property (nonatomic, copy) NSString *recorderKey;
@property (nonatomic, strong) QNUpTaskCompletionHandler completionHandler;
@property (nonatomic, assign)NSInteger currentRegionIndex;
@property (nonatomic, strong)NSMutableArray <id <QNUploadRegion> > *regions;
@property (nonatomic, strong)QNUploadRegionRequestMetrics *currentRegionRequestMetrics;
@property (nonatomic, strong) QNUploadTaskMetrics *metrics;
@end
@implementation QNBaseUpload
- (instancetype)initWithSource:(id<QNUploadSource>)uploadSource
key:(NSString *)key
token:(QNUpToken *)token
option:(QNUploadOption *)option
configuration:(QNConfiguration *)config
recorder:(id<QNRecorderDelegate>)recorder
recorderKey:(NSString *)recorderKey
completionHandler:(QNUpTaskCompletionHandler)completionHandler{
return [self initWithSource:uploadSource data:nil fileName:[uploadSource getFileName] key:key token:token option:option configuration:config recorder:recorder recorderKey:recorderKey completionHandler:completionHandler];
}
- (instancetype)initWithData:(NSData *)data
key:(NSString *)key
fileName:(NSString *)fileName
token:(QNUpToken *)token
option:(QNUploadOption *)option
configuration:(QNConfiguration *)config
completionHandler:(QNUpTaskCompletionHandler)completionHandler{
return [self initWithSource:nil data:data fileName:fileName key:key token:token option:option configuration:config recorder:nil recorderKey:nil completionHandler:completionHandler];
}
- (instancetype)initWithSource:(id<QNUploadSource>)uploadSource
data:(NSData *)data
fileName:(NSString *)fileName
key:(NSString *)key
token:(QNUpToken *)token
option:(QNUploadOption *)option
configuration:(QNConfiguration *)config
recorder:(id<QNRecorderDelegate>)recorder
recorderKey:(NSString *)recorderKey
completionHandler:(QNUpTaskCompletionHandler)completionHandler{
if (self = [super init]) {
_uploadSource = uploadSource;
_data = data;
_fileName = fileName ?: @"?";
_key = key;
_token = token;
_config = config;
_option = option ?: [QNUploadOption defaultOptions];
_recorder = recorder;
_recorderKey = recorderKey;
_completionHandler = completionHandler;
[self initData];
}
return self;
}
- (instancetype)init{
if (self = [super init]) {
[self initData];
}
return self;
}
- (void)initData{
_strongSelf = self;
_currentRegionIndex = 0;
}
- (void)run {
[self.metrics start];
kQNWeakSelf;
[_config.zone query:self.config token:self.token on:^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, QNZonesInfo * _Nullable zonesInfo) {
kQNStrongSelf;
self.metrics.ucQueryMetrics = metrics;
if (responseInfo != nil && responseInfo.isOK && zonesInfo) {
if (![self setupRegions:zonesInfo]) {
responseInfo = [QNResponseInfo responseInfoWithInvalidArgument:[NSString stringWithFormat:@"setup regions host fail, origin response:%@", responseInfo]];
[self complete:responseInfo response:responseInfo.responseDictionary];
return;
}
int prepareCode = [self prepareToUpload];
if (prepareCode == 0) {
[self startToUpload];
} else {
QNResponseInfo *responseInfoP = [QNResponseInfo errorResponseInfo:prepareCode errorDesc:nil];
[self complete:responseInfoP response:responseInfoP.responseDictionary];
}
} else {
if (responseInfo == nil) {
// responseInfo
responseInfo = [QNResponseInfo responseInfoWithSDKInteriorError:@"can't get regions"];
}
[self complete:responseInfo response:responseInfo.responseDictionary];
}
}];
}
- (BOOL)reloadUploadInfo {
return YES;
}
- (int)prepareToUpload{
return 0;
}
- (void)startToUpload{
self.currentRegionRequestMetrics = [[QNUploadRegionRequestMetrics alloc] initWithRegion:[self getCurrentRegion]];
[self.currentRegionRequestMetrics start];
}
//
- (BOOL)switchRegionAndUpload{
if (self.currentRegionRequestMetrics) {
[self.currentRegionRequestMetrics end];
[self.metrics addMetrics:self.currentRegionRequestMetrics];
self.currentRegionRequestMetrics = nil;
}
BOOL isSwitched = [self switchRegion];
if (isSwitched) {
[self startToUpload];
}
return isSwitched;
}
// regionreturn:region
- (BOOL)switchRegionAndUploadIfNeededWithErrorResponse:(QNResponseInfo *)errorResponseInfo {
if (errorResponseInfo.statusCode == 400 && [errorResponseInfo.message containsString:@"incorrect region"]) {
[QNAutoZone clearCache];
}
if (!errorResponseInfo || errorResponseInfo.isOK || // ||
![errorResponseInfo couldRetry] || ![self.config allowBackupHost]) { //
return false;
}
if (self.currentRegionRequestMetrics) {
[self.currentRegionRequestMetrics end];
[self.metrics addMetrics:self.currentRegionRequestMetrics];
self.currentRegionRequestMetrics = nil;
}
// & Resource index
if (![self reloadUploadInfo]) {
return false;
}
// context
if (!errorResponseInfo.isCtxExpiedError && ![self switchRegion]) {
// context region
return false;
}
[self startToUpload];
return true;
}
- (void)complete:(QNResponseInfo *)info
response:(NSDictionary *)response{
[self.metrics end];
[self.currentRegionRequestMetrics end];
if (self.currentRegionRequestMetrics) {
[self.metrics addMetrics:self.currentRegionRequestMetrics];
}
if (self.completionHandler) {
self.completionHandler(info, _key, _metrics, response);
}
self.strongSelf = nil;
}
//MARK:-- region
- (BOOL)setupRegions:(QNZonesInfo *)zonesInfo{
if (zonesInfo == nil || zonesInfo.zonesInfo == nil || zonesInfo.zonesInfo.count == 0) {
return NO;
}
NSMutableArray *defaultRegions = [NSMutableArray array];
NSArray *zoneInfos = zonesInfo.zonesInfo;
for (QNZoneInfo *zoneInfo in zoneInfos) {
QNUploadDomainRegion *region = [[QNUploadDomainRegion alloc] initWithConfig:self.config];
[region setupRegionData:zoneInfo];
if (region.isValid) {
[defaultRegions addObject:region];
}
}
self.regions = defaultRegions;
self.metrics.regions = defaultRegions;
return defaultRegions.count > 0;
}
- (void)insertRegionAtFirst:(id <QNUploadRegion>)region{
BOOL hasRegion = NO;
for (id <QNUploadRegion> regionP in self.regions) {
if ([regionP.zoneInfo.regionId isEqualToString:region.zoneInfo.regionId]) {
hasRegion = YES;
break;
}
}
if (!hasRegion) {
[self.regions insertObject:region atIndex:0];
}
}
- (BOOL)switchRegion{
BOOL ret = NO;
@synchronized (self) {
NSInteger regionIndex = _currentRegionIndex + 1;
if (regionIndex < self.regions.count) {
_currentRegionIndex = regionIndex;
ret = YES;
}
}
return ret;
}
- (id <QNUploadRegion>)getTargetRegion{
return self.regions.firstObject;
}
- (id <QNUploadRegion>)getCurrentRegion{
id <QNUploadRegion> region = nil;
@synchronized (self) {
if (self.currentRegionIndex < self.regions.count) {
region = self.regions[self.currentRegionIndex];
}
}
return region;
}
- (void)addRegionRequestMetricsOfOneFlow:(QNUploadRegionRequestMetrics *)metrics{
if (metrics == nil) {
return;
}
@synchronized (self) {
if (self.currentRegionRequestMetrics == nil) {
self.currentRegionRequestMetrics = metrics;
return;
}
}
[self.currentRegionRequestMetrics addMetrics:metrics];
}
- (QNUploadTaskMetrics *)metrics {
if (_metrics == nil) {
_metrics = [QNUploadTaskMetrics taskMetrics:self.upType];
}
return _metrics;
}
@end

View File

@@ -0,0 +1,14 @@
//
// QNConcurrentResumeUpload.h
// QiniuSDK
//
// Created by WorkSpace_Sun on 2019/7/15.
// Copyright © 2019 Qiniu. All rights reserved.
//
/// 并发分片上传
#import "QNPartsUpload.h"
@interface QNConcurrentResumeUpload : QNPartsUpload
@end

View File

@@ -0,0 +1,43 @@
//
// QNConcurrentResumeUpload.m
// QiniuSDK
//
// Created by WorkSpace_Sun on 2019/7/15.
// Copyright © 2019 Qiniu. All rights reserved.
//
#import "QNLogUtil.h"
#import "QNConcurrentResumeUpload.h"
@interface QNConcurrentResumeUpload()
@property(nonatomic, strong) dispatch_group_t uploadGroup;
@property(nonatomic, strong) dispatch_queue_t uploadQueue;
@end
@implementation QNConcurrentResumeUpload
- (int)prepareToUpload{
self.uploadGroup = dispatch_group_create();
self.uploadQueue = dispatch_queue_create("com.qiniu.concurrentUpload", DISPATCH_QUEUE_SERIAL);
return [super prepareToUpload];
}
- (void)uploadRestData:(dispatch_block_t)completeHandler {
QNLogInfo(@"key:%@ 并发分片", self.key);
for (int i = 0; i < self.config.concurrentTaskCount; i++) {
dispatch_group_enter(self.uploadGroup);
dispatch_group_async(self.uploadGroup, self.uploadQueue, ^{
[super performUploadRestData:^{
dispatch_group_leave(self.uploadGroup);
}];
});
}
dispatch_group_notify(self.uploadGroup, self.uploadQueue, ^{
completeHandler();
});
}
@end

View File

@@ -0,0 +1,346 @@
//
// QNConfiguration.h
// QiniuSDK
//
// Created by bailong on 15/5/21.
// Copyright (c) 2015年 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "QNRecorderDelegate.h"
#import "QNDns.h"
/**
* 断点上传时的分块大小
*/
extern const UInt32 kQNBlockSize;
/**
* DNS默认缓存时间
*/
extern const UInt32 kQNDefaultDnsCacheTime;
/**
* 转换为用户需要的url
*
* @param url 上传url
*
* @return 根据上传url算出代理url
*/
typedef NSString * (^QNUrlConvert)(NSString *url);
typedef NS_ENUM(NSInteger, QNResumeUploadVersion){
QNResumeUploadVersionV1, // 分片v1
QNResumeUploadVersionV2 // 分片v2
};
@class QNConfigurationBuilder;
@class QNZone;
@class QNReportConfig;
/**
* Builder block
*
* @param builder builder实例
*/
typedef void (^QNConfigurationBuilderBlock)(QNConfigurationBuilder *builder);
@interface QNConfiguration : NSObject
/**
* 存储区域
*/
@property (copy, nonatomic, readonly) QNZone *zone;
/**
* 断点上传时的分片大小
*/
@property (readonly) UInt32 chunkSize;
/**
* 如果大于此值就使用断点上传否则使用form上传
*/
@property (readonly) UInt32 putThreshold;
/**
* 上传失败时每个上传域名的重试次数默认重试1次
*/
@property (readonly) UInt32 retryMax;
/**
* 重试前等待时长默认0.5s
*/
@property (readonly) NSTimeInterval retryInterval;
/**
* 单个请求超时时间 单位 秒
* 注:每个文件上传肯能存在多个操作,当每个操作失败时,可能存在多个请求重试。
*/
@property (readonly) UInt32 timeoutInterval;
/**
* 是否使用 https默认为 YES
*/
@property (nonatomic, assign, readonly) BOOL useHttps;
/**
* 单个文件是否开启并发分片上传默认为NO
* 单个文件大小大于4M时会采用分片上传每个分片会已单独的请求进行上传操作多个上传操作可以使用并发
* 也可以采用串行,采用并发时,可以设置并发的个数(对concurrentTaskCount进行设置)。
*/
@property (nonatomic, assign, readonly) BOOL useConcurrentResumeUpload;
/**
* 分片上传版本
*/
@property (nonatomic, assign, readonly) QNResumeUploadVersion resumeUploadVersion;
/**
* 并发分片上传的并发任务个数在concurrentResumeUpload为YES时有效默认为3个
*/
@property (nonatomic, assign, readonly) UInt32 concurrentTaskCount;
/**
* 重试时是否允许使用备用上传域名默认为YES
*/
@property (nonatomic, assign) BOOL allowBackupHost;
/**
* 是否允许使用加速域名,默认为 false
*/
@property (nonatomic, assign, readonly) BOOL accelerateUploading;
/**
* 持久化记录接口,可以实现将记录持久化到文件,数据库等
*/
@property (nonatomic, readonly) id<QNRecorderDelegate> recorder;
/**
* 为持久化上传记录根据上传的key以及文件名 生成持久化的记录key
*/
@property (nonatomic, readonly) QNRecorderKeyGenerator recorderKeyGen;
/**
* 上传请求代理配置信息
*/
@property (nonatomic, readonly) NSDictionary *proxy;
/**
* 上传URL转换使url转换为用户需要的url
*/
@property (nonatomic, readonly) QNUrlConvert converter;
/**
* 默认配置
*/
+ (instancetype)defaultConfiguration;
/**
* 使用 QNConfigurationBuilder 进行配置
* @param block 配置block
*/
+ (instancetype)build:(QNConfigurationBuilderBlock)block;
@end
#define kQNGlobalConfiguration [QNGlobalConfiguration shared]
@interface QNGlobalConfiguration : NSObject
/**
* 是否开启dns预解析 默认开启
*/
@property(nonatomic, assign)BOOL isDnsOpen;
/**
* dns 预取失败后 会进行重新预取 dnsRepreHostNum为最多尝试次数
*/
@property(nonatomic, assign)UInt32 dnsRepreHostNum;
/**
* dns 预取超时,单位:秒 默认2
*/
@property(nonatomic, assign)int dnsResolveTimeout;
/**
* dns 预取, ip 默认有效时间 单位:秒 默认120
* 只有在 dns 预取未返回 ttl 时使用
*/
@property(nonatomic, assign)UInt32 dnsCacheTime;
/**
* dns预取缓存最大有效时间 单位:秒 默认 1800
* 当 dns 缓存 ip 过期并未刷新时,只要在 dnsCacheMaxTTL 时间内仍有效。
*/
@property(nonatomic, assign)UInt32 dnsCacheMaxTTL;
/**
* 自定义DNS解析客户端host
*/
@property(nonatomic, strong) id <QNDnsDelegate> dns;
/**
* dns解析结果本地缓存路径
*/
@property(nonatomic, copy, readonly)NSString *dnsCacheDir;
/**
* 是否使用 udp 方式进行 Dns 预取,默认开启
*/
@property(nonatomic, assign)BOOL udpDnsEnable;
/**
* 使用 udp 进行 Dns 预取时的 server ipv4 数组;当对某个 Host 使用 udp 进行 Dns 预取时,会使用 udpDnsIps 进行并发预取
* 当 udpDnsEnable 开启时,使用 udp 进行 Dns 预取方式才会生效
* 默认
*/
@property(nonatomic, copy) NSArray <NSString *> *udpDnsIpv4Servers;
/**
* 使用 udp 进行 Dns 预取时的 server ipv6 数组;当对某个 Host 使用 udp 进行 Dns 预取时,会使用 udpDnsIps 进行并发预取
* 当 udpDnsEnable 开启时,使用 udp 进行 Dns 预取方式才会生效
* 默认nil
*/
@property(nonatomic, copy) NSArray <NSString *> *udpDnsIpv6Servers;
/**
* 是否使用 doh 预取,默认开启
*/
@property(nonatomic, assign)BOOL dohEnable;
/**
* 使用 doh 预取时的 server 数组;当对某个 Host 使用 Doh 预取时,会使用 dohServers 进行并发预取
* 当 dohEnable 开启时doh 预取才会生效
* 注意:如果使用 ip需保证服务证书与 IP 绑定,避免 sni 问题
*/
@property(nonatomic, copy) NSArray <NSString *> *dohIpv4Servers;
/**
* 使用 doh 预取时的 server 数组;当对某个 Host 使用 Doh 预取时,会使用 dohServers 进行并发预取
* 当 dohEnable 开启时doh 预取才会生效
* 默认nil
* 注意:如果使用 ip需保证服务证书与 IP 绑定,避免 sni 问题
*/
@property(nonatomic, copy) NSArray <NSString *> *dohIpv6Servers;
/**
* Host全局冻结时间 单位:秒 默认60 推荐范围:[30 ~ 120]
* 当某个Host的上传失败后并且可能短时间无法恢复会冻结该Host
*/
@property(nonatomic, assign)UInt32 globalHostFrozenTime;
/**
* Host局部冻结时间只会影响当前上传操作 单位:秒 默认5*60 推荐范围:[60 ~ 10*60]
* 当某个Host的上传失败后并且短时间可能会恢复会局部冻结该Host
*/
@property(nonatomic, assign)UInt32 partialHostFrozenTime;
/**
* 网络连接状态检测使用的connectCheckURLStrings网络链接状态检测可能会影响重试机制启动网络连接状态检测有助于提高上传可用性。
* 当请求的 Response 为网络异常时,并发对 connectCheckURLStrings 中 URLString 进行 HEAD 请求,以此检测当前网络状态的链接状态,其中任意一个 URLString 链接成功则认为当前网络状态链接良好;
* 当 connectCheckURLStrings 为 nil 或者 空数组时则弃用检测功能。
*/
@property(nonatomic, copy)NSArray <NSString *> *connectCheckURLStrings;
/**
* 是否开启网络连接状态检测,默认:开启
*/
@property(nonatomic, assign)BOOL connectCheckEnable;
/**
* 网络连接状态检测HEAD请求超时默认2s
*/
@property(nonatomic, assign)NSTimeInterval connectCheckTimeout;
+ (instancetype)shared;
@end
@interface QNConfigurationBuilder : NSObject
/**
* 默认上传服务器地址
*/
@property (nonatomic, strong) QNZone *zone;
/**
* 断点上传时的分片大小
* 分片 v1 最小为 1024即 1K建议用户配置 >= 512K
* 分片 v2 最小为 1024 * 1024即 1M
*/
@property (assign) UInt32 chunkSize;
/**
* 如果大于此值就使用断点上传否则使用form上传
*/
@property (assign) UInt32 putThreshold;
/**
* 上传失败时每个上传域名的重试次数默认重试1次
*/
@property (assign) UInt32 retryMax;
/**
* 重试前等待时长默认0.5s
*/
@property (assign) NSTimeInterval retryInterval;
/**
* 超时时间 单位 秒
*/
@property (assign) UInt32 timeoutInterval;
/**
* 是否使用 https默认为 YES
*/
@property (nonatomic, assign) BOOL useHttps;
/**
* 重试时是否允许使用备用上传域名默认为YES
*/
@property (nonatomic, assign) BOOL allowBackupHost;
/**
* 是否允许使用加速域名,默认为 false
*/
@property (nonatomic, assign) BOOL accelerateUploading;
/**
* 是否开启并发分片上传默认为NO
*/
@property (nonatomic, assign) BOOL useConcurrentResumeUpload;
/**
* 分片上传版本
*/
@property (nonatomic, assign) QNResumeUploadVersion resumeUploadVersion;
/**
* 并发分片上传的并发任务个数在concurrentResumeUpload为YES时有效默认为3个
*/
@property (nonatomic, assign) UInt32 concurrentTaskCount;
/**
* 持久化记录接口,可以实现将记录持久化到文件,数据库等
*/
@property (nonatomic, strong) id<QNRecorderDelegate> recorder;
/**
* 为持久化上传记录根据上传的key以及文件名 生成持久化的记录key
*/
@property (nonatomic, strong) QNRecorderKeyGenerator recorderKeyGen;
/**
* 上传请求代理配置信息
*/
@property (nonatomic, strong) NSDictionary *proxy;
/**
* 上传URL转换使url转换为用户需要的url
*/
@property (nonatomic, strong) QNUrlConvert converter;
@end

View File

@@ -0,0 +1,293 @@
//
// QNConfiguration.m
// QiniuSDK
//
// Created by bailong on 15/5/21.
// Copyright (c) 2015 Qiniu. All rights reserved.
//
#import "QNConfiguration.h"
#import "QNResponseInfo.h"
#import "QNUpToken.h"
#import "QNReportConfig.h"
#import "QNAutoZone.h"
#import "QN_GTM_Base64.h"
const UInt32 kQNBlockSize = 4 * 1024 * 1024;
const UInt32 kQNDefaultDnsCacheTime = 2 * 60;
@implementation QNConfiguration
+ (instancetype)defaultConfiguration{
QNConfigurationBuilder *builder = [[QNConfigurationBuilder alloc] init];
return [[QNConfiguration alloc] initWithBuilder:builder];
}
+ (instancetype)build:(QNConfigurationBuilderBlock)block {
QNConfigurationBuilder *builder = [[QNConfigurationBuilder alloc] init];
block(builder);
return [[QNConfiguration alloc] initWithBuilder:builder];
}
- (instancetype)initWithBuilder:(QNConfigurationBuilder *)builder {
if (self = [super init]) {
_useConcurrentResumeUpload = builder.useConcurrentResumeUpload;
_resumeUploadVersion = builder.resumeUploadVersion;
_concurrentTaskCount = builder.concurrentTaskCount;
_chunkSize = builder.chunkSize;
if (builder.resumeUploadVersion == QNResumeUploadVersionV1) {
if (_chunkSize < 1024) {
_chunkSize = 1024;
}
} else if (builder.resumeUploadVersion == QNResumeUploadVersionV2) {
if (_chunkSize < 1024 * 1024) {
_chunkSize = 1024 * 1024;
}
}
_putThreshold = builder.putThreshold;
_retryMax = builder.retryMax;
_retryInterval = builder.retryInterval;
_timeoutInterval = builder.timeoutInterval;
_recorder = builder.recorder;
_recorderKeyGen = builder.recorderKeyGen;
_proxy = builder.proxy;
_converter = builder.converter;
_zone = builder.zone;
_useHttps = builder.useHttps;
_allowBackupHost = builder.allowBackupHost;
_accelerateUploading = builder.accelerateUploading;
}
return self;
}
@end
@interface QNGlobalConfiguration(){
NSArray *_defaultDohIpv4Servers;
NSArray *_defaultDohIpv6Servers;
NSArray *_defaultUdpDnsIpv4Servers;
NSArray *_defaultUdpDnsIpv6Servers;
NSArray *_defaultConnectCheckUrls;
}
@property(nonatomic, strong)NSArray *defaultDohIpv4Servers;
@property(nonatomic, strong)NSArray *defaultDohIpv6Servers;
@property(nonatomic, strong)NSArray *defaultUdpDnsIpv4Servers;
@property(nonatomic, strong)NSArray *defaultUdpDnsIpv6Servers;
@property(nonatomic, strong)NSArray *defaultConnectCheckUrls;
@end
@implementation QNGlobalConfiguration
+ (instancetype)shared{
static QNGlobalConfiguration *config = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
config = [[QNGlobalConfiguration alloc] init];
[config setupData];
});
return config;
}
- (void)setupData{
_isDnsOpen = YES;
_dnsResolveTimeout = 2;
_dnsCacheDir = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/Caches/Dns"];
_dnsRepreHostNum = 2;
_dnsCacheTime = kQNDefaultDnsCacheTime;
_dnsCacheMaxTTL = 10*60;
_dohEnable = true;
_defaultDohIpv4Servers = [self parseBase64Array:@"WyJodHRwczovLzIyMy42LjYuNi9kbnMtcXVlcnkiLCAiaHR0cHM6Ly84LjguOC44L2Rucy1xdWVyeSJd"];
_udpDnsEnable = true;
_defaultUdpDnsIpv4Servers = [self parseBase64Array:@"WyIyMjMuNS41LjUiLCAiMTE0LjExNC4xMTQuMTE0IiwgIjEuMS4xLjEiLCAiOC44LjguOCJd"];
_globalHostFrozenTime = 60;
_partialHostFrozenTime = 5*60;
_connectCheckEnable = YES;
_connectCheckTimeout = 2;
_defaultConnectCheckUrls = [self parseBase64Array:@"WyJodHRwczovL3d3dy5xaW5pdS5jb20iLCAiaHR0cHM6Ly93d3cuYmFpZHUuY29tIiwgImh0dHBzOi8vd3d3Lmdvb2dsZS5jb20iXQ=="];
_connectCheckURLStrings = nil;
}
- (NSArray *)parseBase64Array:(NSString *)data {
NSData *jsonData = [QN_GTM_Base64 decodeData:[data dataUsingEncoding:NSUTF8StringEncoding]];
NSArray *ret = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableLeaves error:nil];
if (ret && [ret isKindOfClass:[NSArray class]]) {
return ret;
}
return nil;
}
- (BOOL)isDohEnable {
return _dohEnable && (self.dohIpv4Servers.count > 0 || self.dohIpv6Servers.count > 0) ;
}
- (NSArray<NSString *> *)dohIpv4Servers {
if (_dohIpv4Servers) {
return _dohIpv4Servers;
} else {
return self.defaultDohIpv4Servers;
}
}
- (NSArray<NSString *> *)dohIpv6Servers {
if (_dohIpv6Servers) {
return _dohIpv6Servers;
} else {
return self.defaultDohIpv6Servers;
}
}
- (NSArray<NSString *> *)udpDnsIpv4Servers {
if (_udpDnsIpv4Servers) {
return _udpDnsIpv4Servers;
} else {
return self.defaultUdpDnsIpv4Servers;
}
}
- (NSArray<NSString *> *)udpDnsIpv6Servers {
if (_udpDnsIpv6Servers) {
return _udpDnsIpv6Servers;
} else {
return self.defaultUdpDnsIpv6Servers;
}
}
- (BOOL)isUdpDnsEnable {
return _udpDnsEnable && (self.udpDnsIpv4Servers.count > 0 || self.udpDnsIpv6Servers.count > 0) ;
}
- (NSArray<NSString *> *)connectCheckURLStrings {
if (_connectCheckURLStrings) {
return _connectCheckURLStrings;
} else {
return self.defaultConnectCheckUrls;
}
}
- (NSArray *)defaultDohIpv4Servers {
NSArray *arr = nil;
@synchronized (self) {
if (_defaultDohIpv4Servers) {
arr = [_defaultDohIpv4Servers copy];
}
}
return arr;
}
- (void)setDefaultDohIpv4Servers:(NSArray *)defaultDohIpv4Servers {
@synchronized (self) {
_defaultDohIpv4Servers = defaultDohIpv4Servers;
}
}
- (NSArray *)defaultDohIpv6Servers {
NSArray *arr = nil;
@synchronized (self) {
if (_defaultDohIpv6Servers) {
arr = [_defaultDohIpv6Servers copy];
}
}
return arr;
}
- (void)setDefaultDohIpv6Servers:(NSArray *)defaultDohIpv6Servers {
@synchronized (self) {
_defaultDohIpv6Servers = defaultDohIpv6Servers;
}
}
- (NSArray *)defaultUdpDnsIpv4Servers {
NSArray *arr = nil;
@synchronized (self) {
if (_defaultUdpDnsIpv4Servers) {
arr = [_defaultUdpDnsIpv4Servers copy];
}
}
return arr;
}
- (void)setDefaultUdpDnsIpv4Servers:(NSArray *)defaultUdpDnsIpv4Servers {
@synchronized (self) {
_defaultUdpDnsIpv4Servers = defaultUdpDnsIpv4Servers;
}
}
- (NSArray *)defaultUdpDnsIpv6Servers {
NSArray *arr = nil;
@synchronized (self) {
if (_defaultUdpDnsIpv6Servers) {
arr = [_defaultUdpDnsIpv6Servers copy];
}
}
return arr;
}
- (void)setDefaultUdpDnsIpv6Servers:(NSArray *)defaultUdpDnsIpv6Servers {
@synchronized (self) {
_defaultUdpDnsIpv6Servers = defaultUdpDnsIpv6Servers;
}
}
- (NSArray *)defaultConnectCheckUrls {
NSArray *arr = nil;
@synchronized (self) {
if (_defaultConnectCheckUrls) {
arr = [_defaultConnectCheckUrls copy];
}
}
return arr;
}
- (void)setDefaultConnectCheckUrls:(NSArray *)defaultConnectCheckUrls {
@synchronized (self) {
_defaultConnectCheckUrls = defaultConnectCheckUrls;
}
}
@end
@implementation QNConfigurationBuilder
- (instancetype)init {
if (self = [super init]) {
_zone = [[QNAutoZone alloc] init];
_chunkSize = 2 * 1024 * 1024;
_putThreshold = 4 * 1024 * 1024;
_retryMax = 1;
_timeoutInterval = 90;
_retryInterval = 0.5;
_recorder = nil;
_recorderKeyGen = nil;
_proxy = nil;
_converter = nil;
_useHttps = YES;
_allowBackupHost = YES;
_accelerateUploading = NO;
_useConcurrentResumeUpload = NO;
_resumeUploadVersion = QNResumeUploadVersionV1;
_concurrentTaskCount = 3;
}
return self;
}
@end

13
Pods/Qiniu/QiniuSDK/Storage/QNFormUpload.h generated Executable file
View File

@@ -0,0 +1,13 @@
//
// QNFormUpload.h
// QiniuSDK
//
// Created by bailong on 15/1/4.
// Copyright (c) 2015年 Qiniu. All rights reserved.
//
#import "QNBaseUpload.h"
@interface QNFormUpload : QNBaseUpload
@end

View File

@@ -0,0 +1,73 @@
//
// QNFormUpload.m
// QiniuSDK
//
// Created by bailong on 15/1/4.
// Copyright (c) 2015 Qiniu. All rights reserved.
//
#import "QNDefine.h"
#import "QNLogUtil.h"
#import "QNFormUpload.h"
#import "QNResponseInfo.h"
#import "QNUpProgress.h"
#import "QNRequestTransaction.h"
@interface QNFormUpload ()
@property(nonatomic, strong)QNUpProgress *progress;
@property(nonatomic, strong)QNRequestTransaction *uploadTransaction;
@end
@implementation QNFormUpload
- (void)startToUpload {
[super startToUpload];
QNLogInfo(@"key:%@ form上传", self.key);
self.uploadTransaction = [[QNRequestTransaction alloc] initWithConfig:self.config
uploadOption:self.option
targetRegion:[self getTargetRegion]
currentRegion:[self getCurrentRegion]
key:self.key
token:self.token];
kQNWeakSelf;
void(^progressHandler)(long long totalBytesWritten, long long totalBytesExpectedToWrite) = ^(long long totalBytesWritten, long long totalBytesExpectedToWrite){
kQNStrongSelf;
[self.progress progress:self.key uploadBytes:totalBytesWritten totalBytes:totalBytesExpectedToWrite];
};
[self.uploadTransaction uploadFormData:self.data
fileName:self.fileName
progress:progressHandler
complete:^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response) {
kQNStrongSelf;
[self addRegionRequestMetricsOfOneFlow:metrics];
if (!responseInfo.isOK) {
if (![self switchRegionAndUploadIfNeededWithErrorResponse:responseInfo]) {
[self complete:responseInfo response:response];
}
return;
}
[self.progress notifyDone:self.key totalBytes:self.data.length];
[self complete:responseInfo response:response];
}];
}
- (QNUpProgress *)progress {
if (_progress == nil) {
_progress = [QNUpProgress progress:self.option.progressHandler byteProgress:self.option.byteProgressHandler];
}
return _progress;
}
- (NSString *)upType {
return QNUploadUpTypeForm;
}
@end

View File

@@ -0,0 +1,24 @@
//
// QNPartsUpload.h
// QiniuSDK_Mac
//
// Created by yangsen on 2020/5/7.
// Copyright © 2020 Qiniu. All rights reserved.
//
/// 分片上传,默认为串行
#import "QNBaseUpload.h"
#import "QNUploadInfo.h"
NS_ASSUME_NONNULL_BEGIN
@class QNRequestTransaction;
@interface QNPartsUpload : QNBaseUpload
/// 上传剩余的数据此方法整合上传流程上传操作为performUploadRestData默认串行上传
- (void)uploadRestData:(dispatch_block_t)completeHandler;
- (void)performUploadRestData:(dispatch_block_t)completeHandler;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,324 @@
//
// QNPartsUpload.m
// QiniuSDK_Mac
//
// Created by yangsen on 2020/5/7.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNDefine.h"
#import "QNUtils.h"
#import "QNLogUtil.h"
#import "QNPartsUpload.h"
#import "QNZoneInfo.h"
#import "QNReportItem.h"
#import "QNRequestTransaction.h"
#import "QNPartsUploadPerformerV1.h"
#import "QNPartsUploadPerformerV2.h"
#define kQNRecordFileInfoKey @"recordFileInfo"
#define kQNRecordZoneInfoKey @"recordZoneInfo"
@interface QNPartsUpload()
@property(nonatomic, strong)QNPartsUploadPerformer *uploadPerformer;
@property( atomic, strong)QNResponseInfo *uploadDataErrorResponseInfo;
@property( atomic, strong)NSDictionary *uploadDataErrorResponse;
@end
@implementation QNPartsUpload
- (void)initData {
[super initData];
//
if (self.config.resumeUploadVersion == QNResumeUploadVersionV1) {
QNLogInfo(@"key:%@ 分片V1", self.key);
self.uploadPerformer = [[QNPartsUploadPerformerV1 alloc] initWithSource:self.uploadSource
fileName:self.fileName
key:self.key
token:self.token
option:self.option
configuration:self.config
recorderKey:self.recorderKey];
} else {
QNLogInfo(@"key:%@ 分片V2", self.key);
self.uploadPerformer = [[QNPartsUploadPerformerV2 alloc] initWithSource:self.uploadSource
fileName:self.fileName
key:self.key
token:self.token
option:self.option
configuration:self.config
recorderKey:self.recorderKey];
}
}
- (BOOL)isAllUploaded {
return [self.uploadPerformer.uploadInfo isAllUploaded];
}
- (void)setErrorResponseInfo:(QNResponseInfo *)responseInfo errorResponse:(NSDictionary *)response{
if (!responseInfo) {
return;
}
if (!self.uploadDataErrorResponseInfo || responseInfo.statusCode != kQNSDKInteriorError) {
self.uploadDataErrorResponseInfo = responseInfo;
self.uploadDataErrorResponse = response ?: responseInfo.responseDictionary;
}
}
- (int)prepareToUpload{
int code = [super prepareToUpload];
if (code != 0) {
return code;
}
// region
if (self.uploadPerformer.currentRegion && self.uploadPerformer.currentRegion.isValid) {
// currentRegionregionregionList
[self insertRegionAtFirst:self.uploadPerformer.currentRegion];
QNLogInfo(@"key:%@ 使用缓存region", self.key);
} else {
// currentRegion region
[self.uploadPerformer switchRegion:[self getCurrentRegion]];
}
QNLogInfo(@"key:%@ region:%@", self.key, self.uploadPerformer.currentRegion.zoneInfo.regionId);
if (self.uploadSource == nil) {
code = kQNLocalIOError;
}
return code;
}
- (BOOL)switchRegion{
BOOL isSuccess = [super switchRegion];
if (isSuccess) {
[self.uploadPerformer switchRegion:self.getCurrentRegion];
QNLogInfo(@"key:%@ 切换region%@", self.key , self.uploadPerformer.currentRegion.zoneInfo.regionId);
}
return isSuccess;
}
- (BOOL)switchRegionAndUploadIfNeededWithErrorResponse:(QNResponseInfo *)errorResponseInfo {
[self reportBlock];
return [super switchRegionAndUploadIfNeededWithErrorResponse:errorResponseInfo];
}
- (BOOL)reloadUploadInfo {
if (![super reloadUploadInfo]) {
return NO;
}
//
return [self.uploadPerformer couldReloadInfo] && [self.uploadPerformer reloadInfo];
}
- (void)startToUpload{
[super startToUpload];
//
self.uploadDataErrorResponseInfo = nil;
self.uploadDataErrorResponse = nil;
QNLogInfo(@"key:%@ serverInit", self.key);
// 1. upload
kQNWeakSelf;
[self serverInit:^(QNResponseInfo * _Nullable responseInfo, NSDictionary * _Nullable response) {
kQNStrongSelf;
if (!responseInfo.isOK) {
if (![self switchRegionAndUploadIfNeededWithErrorResponse:responseInfo]) {
[self complete:responseInfo response:response];
}
return;
}
QNLogInfo(@"key:%@ uploadRestData", self.key);
// 2.
kQNWeakSelf;
[self uploadRestData:^{
kQNStrongSelf;
if (![self isAllUploaded]) {
if (![self switchRegionAndUploadIfNeededWithErrorResponse:self.uploadDataErrorResponseInfo]) {
[self complete:self.uploadDataErrorResponseInfo response:self.uploadDataErrorResponse];
}
return;
}
//
if ([self.uploadPerformer.uploadInfo getSourceSize] == 0) {
QNResponseInfo *responseInfo = [QNResponseInfo responseInfoOfZeroData:@"file is empty"];
[self complete:responseInfo response:responseInfo.responseDictionary];
return;
}
QNLogInfo(@"key:%@ completeUpload errorResponseInfo:%@", self.key, self.uploadDataErrorResponseInfo);
// 3.
kQNWeakSelf;
[self completeUpload:^(QNResponseInfo * _Nullable responseInfo, NSDictionary * _Nullable response) {
kQNStrongSelf;
if (!responseInfo.isOK) {
if (![self switchRegionAndUploadIfNeededWithErrorResponse:responseInfo]) {
[self complete:responseInfo response:response];
}
return;
}
[self complete:responseInfo response:response];
}];
}];
}];
}
- (void)uploadRestData:(dispatch_block_t)completeHandler {
QNLogInfo(@"key:%@ 串行分片", self.key);
[self performUploadRestData:completeHandler];
}
- (void)performUploadRestData:(dispatch_block_t)completeHandler {
if ([self isAllUploaded]) {
completeHandler();
return;
}
kQNWeakSelf;
[self uploadNextData:^(BOOL stop, QNResponseInfo * _Nullable responseInfo, NSDictionary * _Nullable response) {
kQNStrongSelf;
if (stop || !responseInfo.isOK) {
completeHandler();
} else {
[self performUploadRestData:completeHandler];
}
}];
}
//MARK:-- concurrent upload model API
- (void)serverInit:(void(^)(QNResponseInfo * _Nullable responseInfo, NSDictionary * _Nullable response))completeHandler {
kQNWeakSelf;
void(^completeHandlerP)(QNResponseInfo *, QNUploadRegionRequestMetrics *, NSDictionary *) = ^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response){
kQNStrongSelf;
if (!responseInfo.isOK) {
[self setErrorResponseInfo:responseInfo errorResponse:response];
}
[self addRegionRequestMetricsOfOneFlow:metrics];
completeHandler(responseInfo, response);
};
[self.uploadPerformer serverInit:completeHandlerP];
}
- (void)uploadNextData:(void(^)(BOOL stop, QNResponseInfo * _Nullable responseInfo, NSDictionary * _Nullable response))completeHandler {
kQNWeakSelf;
void(^completeHandlerP)(BOOL, QNResponseInfo *, QNUploadRegionRequestMetrics *, NSDictionary *) = ^(BOOL stop, QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response){
kQNStrongSelf;
if (!responseInfo.isOK) {
[self setErrorResponseInfo:responseInfo errorResponse:response];
}
[self addRegionRequestMetricsOfOneFlow:metrics];
completeHandler(stop, responseInfo, response);
};
[self.uploadPerformer uploadNextData:completeHandlerP];
}
- (void)completeUpload:(void(^)(QNResponseInfo * _Nullable responseInfo, NSDictionary * _Nullable response))completeHandler {
kQNWeakSelf;
void(^completeHandlerP)(QNResponseInfo *, QNUploadRegionRequestMetrics *, NSDictionary *) = ^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response){
kQNStrongSelf;
if (!responseInfo.isOK) {
[self setErrorResponseInfo:responseInfo errorResponse:response];
}
[self addRegionRequestMetricsOfOneFlow:metrics];
completeHandler(responseInfo, response);
};
[self.uploadPerformer completeUpload:completeHandlerP];
}
- (void)complete:(QNResponseInfo *)info response:(NSDictionary *)response{
[self.uploadSource close];
if ([self shouldRemoveUploadInfoRecord:info]) {
[self.uploadPerformer removeUploadInfoRecord];
}
[super complete:info response:response];
[self reportBlock];
}
- (BOOL)shouldRemoveUploadInfoRecord:(QNResponseInfo *)info {
return info.isOK || info.statusCode == 612 || info.statusCode == 614 || info.statusCode == 701;
}
//MARK:-- block
- (void)reportBlock{
QNUploadRegionRequestMetrics *metrics = self.currentRegionRequestMetrics ?: [QNUploadRegionRequestMetrics emptyMetrics];
QNReportItem *item = [QNReportItem item];
[item setReportValue:QNReportLogTypeBlock forKey:QNReportBlockKeyLogType];
[item setReportValue:@([[NSDate date] timeIntervalSince1970]) forKey:QNReportBlockKeyUpTime];
[item setReportValue:self.token.bucket forKey:QNReportBlockKeyTargetBucket];
[item setReportValue:self.key forKey:QNReportBlockKeyTargetKey];
[item setReportValue:[self getTargetRegion].zoneInfo.regionId forKey:QNReportBlockKeyTargetRegionId];
[item setReportValue:[self getCurrentRegion].zoneInfo.regionId forKey:QNReportBlockKeyCurrentRegionId];
[item setReportValue:metrics.totalElapsedTime forKey:QNReportBlockKeyTotalElapsedTime];
[item setReportValue:metrics.bytesSend forKey:QNReportBlockKeyBytesSent];
[item setReportValue:self.uploadPerformer.recoveredFrom forKey:QNReportBlockKeyRecoveredFrom];
[item setReportValue:@([self.uploadSource getSize]) forKey:QNReportBlockKeyFileSize];
[item setReportValue:@([QNUtils getCurrentProcessID]) forKey:QNReportBlockKeyPid];
[item setReportValue:@([QNUtils getCurrentThreadID]) forKey:QNReportBlockKeyTid];
[item setReportValue:metrics.metricsList.lastObject.hijacked forKey:QNReportBlockKeyHijacking];
// region /
if (self.uploadDataErrorResponseInfo == nil && [self.uploadSource getSize] > 0 && [metrics totalElapsedTime] > 0) {
NSNumber *speed = [QNUtils calculateSpeed:[self.uploadSource getSize] totalTime:[metrics totalElapsedTime].longLongValue];
[item setReportValue:speed forKey:QNReportBlockKeyPerceptiveSpeed];
}
if (self.config.resumeUploadVersion == QNResumeUploadVersionV1) {
[item setReportValue:@(1) forKey:QNReportBlockKeyUpApiVersion];
} else {
[item setReportValue:@(2) forKey:QNReportBlockKeyUpApiVersion];
}
[item setReportValue:[QNUtils getCurrentNetworkType] forKey:QNReportBlockKeyClientTime];
[item setReportValue:[QNUtils systemName] forKey:QNReportBlockKeyOsName];
[item setReportValue:[QNUtils systemVersion] forKey:QNReportBlockKeyOsVersion];
[item setReportValue:[QNUtils sdkLanguage] forKey:QNReportBlockKeySDKName];
[item setReportValue:[QNUtils sdkVersion] forKey:QNReportBlockKeySDKVersion];
[kQNReporter reportItem:item token:self.token.token];
}
- (NSString *)upType {
if (self.config == nil) {
return nil;
}
NSString *sourceType = @"";
if ([self.uploadSource respondsToSelector:@selector(sourceType)]) {
sourceType = [self.uploadSource sourceType];
}
if (self.config.resumeUploadVersion == QNResumeUploadVersionV1) {
return [NSString stringWithFormat:@"%@<%@>",QNUploadUpTypeResumableV1, sourceType];
} else {
return [NSString stringWithFormat:@"%@<%@>",QNUploadUpTypeResumableV2, sourceType];
}
}
@end

View File

@@ -0,0 +1,87 @@
//
// QNPartsUploadPerformer.h
// QiniuSDK
//
// Created by yangsen on 2020/12/1.
// Copyright © 2020 Qiniu. All rights reserved.
//
/// 抽象类,不可以直接使用,需要使用子类
#import "QNFileDelegate.h"
#import "QNUploadSource.h"
#import "QNResponseInfo.h"
#import "QNUploadOption.h"
#import "QNConfiguration.h"
#import "QNUpToken.h"
NS_ASSUME_NONNULL_BEGIN
@protocol QNUploadRegion;
@class QNUploadInfo, QNRequestTransaction, QNUploadRegionRequestMetrics;
@interface QNPartsUploadPerformer : NSObject
@property (nonatomic, copy, readonly) NSString *key;
@property (nonatomic, copy, readonly) NSString *fileName;
@property (nonatomic, strong, readonly) id <QNUploadSource> uploadSource;
@property (nonatomic, strong, readonly) QNUpToken *token;
@property (nonatomic, strong, readonly) QNUploadOption *option;
@property (nonatomic, strong, readonly) QNConfiguration *config;
@property (nonatomic, strong, readonly) id <QNRecorderDelegate> recorder;
@property (nonatomic, copy, readonly) NSString *recorderKey;
/// 断点续传时,起始上传偏移
@property(nonatomic, strong, readonly)NSNumber *recoveredFrom;
@property(nonatomic, strong, readonly)id <QNUploadRegion> currentRegion;
@property(nonatomic, strong, readonly)QNUploadInfo *uploadInfo;
- (instancetype)initWithSource:(id<QNUploadSource>)uploadSource
fileName:(NSString *)fileName
key:(NSString *)key
token:(QNUpToken *)token
option:(QNUploadOption *)option
configuration:(QNConfiguration *)config
recorderKey:(NSString *)recorderKey;
// 是否可以重新加载资源
- (BOOL)couldReloadInfo;
// 重新加载资源
- (BOOL)reloadInfo;
- (void)switchRegion:(id <QNUploadRegion>)region;
/// 通知回调当前进度
- (void)notifyProgress:(BOOL)isCompleted;
/// 分片信息保存本地
- (void)recordUploadInfo;
/// 分片信息从本地移除
- (void)removeUploadInfoRecord;
/// 根据字典构造分片信息 【子类实现】
- (QNUploadInfo *)getFileInfoWithDictionary:(NSDictionary * _Nonnull)fileInfoDictionary;
/// 根据配置构造分片信息 【子类实现】
- (QNUploadInfo *)getDefaultUploadInfo;
- (QNRequestTransaction *)createUploadRequestTransaction;
- (void)destroyUploadRequestTransaction:(QNRequestTransaction *)transaction;
/// 上传前,服务端配置工作 【子类实现】
- (void)serverInit:(void(^)(QNResponseInfo * _Nullable responseInfo,
QNUploadRegionRequestMetrics * _Nullable metrics,
NSDictionary * _Nullable response))completeHandler;
/// 上传文件分片 【子类实现】
- (void)uploadNextData:(void(^)(BOOL stop,
QNResponseInfo * _Nullable responseInfo,
QNUploadRegionRequestMetrics * _Nullable metrics,
NSDictionary * _Nullable response))completeHandler;
/// 完成上传,服务端组织文件信息 【子类实现】
- (void)completeUpload:(void(^)(QNResponseInfo * _Nullable responseInfo,
QNUploadRegionRequestMetrics * _Nullable metrics,
NSDictionary * _Nullable response))completeHandler;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,237 @@
//
// QNPartsUploadPerformer.m
// QiniuSDK
//
// Created by yangsen on 2020/12/1.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNLogUtil.h"
#import "QNAsyncRun.h"
#import "QNUpToken.h"
#import "QNZoneInfo.h"
#import "QNUploadOption.h"
#import "QNConfiguration.h"
#import "QNUploadInfo.h"
#import "QNUploadRegionInfo.h"
#import "QNRecorderDelegate.h"
#import "QNUploadDomainRegion.h"
#import "QNPartsUploadPerformer.h"
#import "QNUpProgress.h"
#import "QNRequestTransaction.h"
#define kQNRecordFileInfoKey @"recordFileInfo"
#define kQNRecordZoneInfoKey @"recordZoneInfo"
@interface QNPartsUploadPerformer()
@property (nonatomic, copy) NSString *key;
@property (nonatomic, copy) NSString *fileName;
@property (nonatomic, strong) id <QNUploadSource> uploadSource;
@property (nonatomic, strong) QNUpToken *token;
@property (nonatomic, strong) QNUploadOption *option;
@property (nonatomic, strong) QNConfiguration *config;
@property (nonatomic, strong) id <QNRecorderDelegate> recorder;
@property (nonatomic, copy) NSString *recorderKey;
@property (nonatomic, strong) NSNumber *recoveredFrom;
@property (nonatomic, strong) id <QNUploadRegion> targetRegion;
@property (nonatomic, strong) id <QNUploadRegion> currentRegion;
@property (nonatomic, strong) QNUploadInfo *uploadInfo;
@property(nonatomic, strong) NSLock *progressLocker;
@property(nonatomic, strong) QNUpProgress *progress;
@property(nonatomic, strong) NSMutableArray <QNRequestTransaction *> *uploadTransactions;
@end
@implementation QNPartsUploadPerformer
- (instancetype)initWithSource:(id<QNUploadSource>)uploadSource
fileName:(NSString *)fileName
key:(NSString *)key
token:(QNUpToken *)token
option:(QNUploadOption *)option
configuration:(QNConfiguration *)config
recorderKey:(NSString *)recorderKey {
if (self = [super init]) {
_uploadSource = uploadSource;
_fileName = fileName;
_key = key;
_token = token;
_option = option;
_config = config;
_recorder = config.recorder;
_recorderKey = recorderKey;
[self initData];
}
return self;
}
- (void)initData {
self.uploadTransactions = [NSMutableArray array];
if (!self.uploadInfo) {
self.uploadInfo = [self getDefaultUploadInfo];
}
[self recoverUploadInfoFromRecord];
}
- (BOOL)couldReloadInfo {
return [self.uploadInfo couldReloadSource];
}
- (BOOL)reloadInfo {
self.recoveredFrom = nil;
[self.uploadInfo clearUploadState];
return [self.uploadInfo reloadSource];
}
- (void)switchRegion:(id <QNUploadRegion>)region {
self.currentRegion = region;
if (!self.targetRegion) {
self.targetRegion = region;
}
}
- (void)notifyProgress:(BOOL)isCompleted {
if (self.uploadInfo == nil) {
return;
}
if (isCompleted) {
[self.progress notifyDone:self.key totalBytes:[self.uploadInfo getSourceSize]];
} else {
[self.progress progress:self.key uploadBytes:[self.uploadInfo uploadSize] totalBytes:[self.uploadInfo getSourceSize]];
}
}
- (void)recordUploadInfo {
NSString *key = self.recorderKey;
if (self.recorder == nil || key == nil || key.length == 0) {
return;
}
@synchronized (self) {
NSDictionary *zoneInfo = [self.currentRegion zoneInfo].detailInfo;
NSDictionary *uploadInfo = [self.uploadInfo toDictionary];
if (zoneInfo && uploadInfo) {
NSDictionary *info = @{kQNRecordZoneInfoKey : zoneInfo,
kQNRecordFileInfoKey : uploadInfo};
NSData *data = [NSJSONSerialization dataWithJSONObject:info options:NSJSONWritingPrettyPrinted error:nil];
if (data) {
[self.recorder set:key data:data];
}
}
}
QNLogInfo(@"key:%@ recorderKey:%@ recordUploadInfo", self.key, self.recorderKey);
}
- (void)removeUploadInfoRecord {
self.recoveredFrom = nil;
[self.uploadInfo clearUploadState];
[self.recorder del:self.recorderKey];
QNLogInfo(@"key:%@ recorderKey:%@ removeUploadInfoRecord", self.key, self.recorderKey);
}
- (void)recoverUploadInfoFromRecord {
QNLogInfo(@"key:%@ recorderKey:%@ recorder:%@ recoverUploadInfoFromRecord", self.key, self.recorderKey, self.recorder);
NSString *key = self.recorderKey;
if (self.recorder == nil || key == nil || [key isEqualToString:@""]) {
return;
}
NSData *data = [self.recorder get:key];
if (data == nil) {
QNLogInfo(@"key:%@ recorderKey:%@ recoverUploadInfoFromRecord data:nil", self.key, self.recorderKey);
return;
}
NSError *error = nil;
NSDictionary *info = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&error];
if (error != nil || ![info isKindOfClass:[NSDictionary class]]) {
QNLogInfo(@"key:%@ recorderKey:%@ recoverUploadInfoFromRecord json error", self.key, self.recorderKey);
[self.recorder del:self.key];
return;
}
QNZoneInfo *zoneInfo = [QNZoneInfo zoneInfoFromDictionary:info[kQNRecordZoneInfoKey]];
QNUploadInfo *recoverUploadInfo = [self getFileInfoWithDictionary:info[kQNRecordFileInfoKey]];
if (zoneInfo && self.uploadInfo && [recoverUploadInfo isValid]
&& [self.uploadInfo isSameUploadInfo:recoverUploadInfo]) {
QNLogInfo(@"key:%@ recorderKey:%@ recoverUploadInfoFromRecord valid", self.key, self.recorderKey);
[recoverUploadInfo checkInfoStateAndUpdate];
self.uploadInfo = recoverUploadInfo;
QNUploadDomainRegion *region = [[QNUploadDomainRegion alloc] init];
[region setupRegionData:zoneInfo];
self.currentRegion = region;
self.targetRegion = region;
self.recoveredFrom = @([recoverUploadInfo uploadSize]);
} else {
QNLogInfo(@"key:%@ recorderKey:%@ recoverUploadInfoFromRecord invalid", self.key, self.recorderKey);
[self.recorder del:self.key];
self.currentRegion = nil;
self.targetRegion = nil;
self.recoveredFrom = nil;
}
}
- (QNRequestTransaction *)createUploadRequestTransaction {
QNRequestTransaction *transaction = [[QNRequestTransaction alloc] initWithConfig:self.config
uploadOption:self.option
targetRegion:self.targetRegion
currentRegion:self.currentRegion
key:self.key
token:self.token];
@synchronized (self) {
[self.uploadTransactions addObject:transaction];
}
return transaction;
}
- (void)destroyUploadRequestTransaction:(QNRequestTransaction *)transaction {
if (transaction) {
@synchronized (self) {
[self.uploadTransactions removeObject:transaction];
}
}
}
- (QNUploadInfo *)getFileInfoWithDictionary:(NSDictionary *)fileInfoDictionary {
return nil;
}
- (QNUploadInfo *)getDefaultUploadInfo {
return nil;
}
- (void)serverInit:(void (^)(QNResponseInfo * _Nullable,
QNUploadRegionRequestMetrics * _Nullable,
NSDictionary * _Nullable))completeHandler {}
- (void)uploadNextData:(void (^)(BOOL stop,
QNResponseInfo * _Nullable,
QNUploadRegionRequestMetrics * _Nullable,
NSDictionary * _Nullable))completeHandler {}
- (void)completeUpload:(void (^)(QNResponseInfo * _Nullable,
QNUploadRegionRequestMetrics * _Nullable,
NSDictionary * _Nullable))completeHandler {}
- (QNUpProgress *)progress {
[self.progressLocker lock];
if (_progress == nil) {
_progress = [QNUpProgress progress:self.option.progressHandler byteProgress:self.option.byteProgressHandler];
}
[self.progressLocker unlock];
return _progress;
}
@end

View File

@@ -0,0 +1,19 @@
//
// QNPartsUploadApiV1.h
// QiniuSDK
//
// Created by yangsen on 2020/11/30.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNPartsUploadPerformer.h"
NS_ASSUME_NONNULL_BEGIN
@interface QNPartsUploadPerformerV1 : QNPartsUploadPerformer
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,188 @@
//
// QNPartsUploadApiV1.m
// QiniuSDK
//
// Created by yangsen on 2020/11/30.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNLogUtil.h"
#import "QNDefine.h"
#import "QNRequestTransaction.h"
#import "QNUploadInfoV1.h"
#import "QNPartsUploadPerformerV1.h"
@interface QNPartsUploadPerformerV1()
@end
@implementation QNPartsUploadPerformerV1
+ (long long)blockSize{
return 4 * 1024 * 1024;
}
- (QNUploadInfo *)getFileInfoWithDictionary:(NSDictionary *)fileInfoDictionary {
return [QNUploadInfoV1 info:self.uploadSource dictionary:fileInfoDictionary];
}
- (QNUploadInfo *)getDefaultUploadInfo {
return [QNUploadInfoV1 info:self.uploadSource configuration:self.config];
}
- (void)serverInit:(void(^)(QNResponseInfo * _Nullable responseInfo,
QNUploadRegionRequestMetrics * _Nullable metrics,
NSDictionary * _Nullable response))completeHandler {
QNResponseInfo *responseInfo = [QNResponseInfo successResponse];
completeHandler(responseInfo, nil, nil);
}
- (void)uploadNextData:(void(^)(BOOL stop,
QNResponseInfo * _Nullable responseInfo,
QNUploadRegionRequestMetrics * _Nullable metrics,
NSDictionary * _Nullable response))completeHandler {
QNUploadInfoV1 *uploadInfo = (QNUploadInfoV1 *)self.uploadInfo;
NSError *error;
QNUploadBlock *block = nil;
QNUploadData *chunk = nil;
@synchronized (self) {
block = [uploadInfo nextUploadBlock:&error];
chunk = [uploadInfo nextUploadData:block];
chunk.state = QNUploadStateUploading;
}
if (error) {
QNResponseInfo *responseInfo = [QNResponseInfo responseInfoWithLocalIOError:[NSString stringWithFormat:@"%@", error]];
completeHandler(YES, responseInfo, nil, nil);
return;
}
if (block == nil || chunk == nil) {
QNLogInfo(@"key:%@ no chunk left", self.key);
QNResponseInfo *responseInfo = nil;
if (uploadInfo.getSourceSize == 0) {
responseInfo = [QNResponseInfo responseInfoOfZeroData:@"file is empty"];
} else {
responseInfo = [QNResponseInfo responseInfoWithSDKInteriorError:@"no chunk left"];
}
completeHandler(YES, responseInfo, nil, nil);
return;
}
if (chunk.data == nil) {
QNLogInfo(@"key:%@ chunk data is nil", self.key);
QNResponseInfo *responseInfo = [QNResponseInfo responseInfoOfZeroData:@"chunk data is nil"];;
completeHandler(YES, responseInfo, nil, nil);
return;
}
kQNWeakSelf;
void (^progress)(long long, long long) = ^(long long totalBytesWritten, long long totalBytesExpectedToWrite){
kQNStrongSelf;
chunk.uploadSize = totalBytesWritten;
[self notifyProgress:false];
};
void (^completeHandlerP)(QNResponseInfo *, QNUploadRegionRequestMetrics *, NSDictionary *) = ^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response) {
kQNStrongSelf;
NSString *blockContext = response[@"ctx"];
NSNumber *expiredAt = response[@"expired_at"];
if (responseInfo.isOK && blockContext && expiredAt) {
block.context = blockContext;
block.expiredAt = expiredAt;
chunk.state = QNUploadStateComplete;
[self recordUploadInfo];
[self notifyProgress:false];
} else {
chunk.state = QNUploadStateWaitToUpload;
}
completeHandler(NO, responseInfo, metrics, response);
};
if ([uploadInfo isFirstData:chunk]) {
QNLogInfo(@"key:%@ makeBlock", self.key);
[self makeBlock:block firstChunk:chunk chunkData:chunk.data progress:progress completeHandler:completeHandlerP];
} else {
QNLogInfo(@"key:%@ uploadChunk", self.key);
[self uploadChunk:block chunk:chunk chunkData:chunk.data progress:progress completeHandler:completeHandlerP];
}
}
- (void)completeUpload:(void(^)(QNResponseInfo * _Nullable responseInfo,
QNUploadRegionRequestMetrics * _Nullable metrics,
NSDictionary * _Nullable response))completeHandler {
QNUploadInfoV1 *uploadInfo = (QNUploadInfoV1 *)self.uploadInfo;
QNRequestTransaction *transaction = [self createUploadRequestTransaction];
kQNWeakSelf;
kQNWeakObj(transaction);
[transaction makeFile:[uploadInfo getSourceSize]
fileName:self.fileName
blockContexts:[uploadInfo allBlocksContexts]
complete:^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response) {
kQNStrongSelf;
kQNStrongObj(transaction);
if (responseInfo.isOK) {
[self notifyProgress:true];
}
completeHandler(responseInfo, metrics, response);
[self destroyUploadRequestTransaction:transaction];
}];
}
- (void)makeBlock:(QNUploadBlock *)block
firstChunk:(QNUploadData *)chunk
chunkData:(NSData *)chunkData
progress:(void(^)(long long totalBytesWritten,
long long totalBytesExpectedToWrite))progress
completeHandler:(void(^)(QNResponseInfo * _Nullable responseInfo,
QNUploadRegionRequestMetrics * _Nullable metrics,
NSDictionary * _Nullable response))completeHandler {
QNRequestTransaction *transaction = [self createUploadRequestTransaction];
kQNWeakSelf;
kQNWeakObj(transaction);
[transaction makeBlock:block.offset
blockSize:block.size
firstChunkData:chunkData
progress:progress
complete:^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response) {
kQNStrongSelf;
kQNStrongObj(transaction);
completeHandler(responseInfo, metrics, response);
[self destroyUploadRequestTransaction:transaction];
}];
}
- (void)uploadChunk:(QNUploadBlock *)block
chunk:(QNUploadData *)chunk
chunkData:(NSData *)chunkData
progress:(void(^)(long long totalBytesWritten,
long long totalBytesExpectedToWrite))progress
completeHandler:(void(^)(QNResponseInfo * _Nullable responseInfo,
QNUploadRegionRequestMetrics * _Nullable metrics,
NSDictionary * _Nullable response))completeHandler {
QNRequestTransaction *transaction = [self createUploadRequestTransaction];
kQNWeakSelf;
kQNWeakObj(transaction);
[transaction uploadChunk:block.context
blockOffset:block.offset
chunkData:chunkData
chunkOffset:chunk.offset
progress:progress
complete:^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response) {
kQNStrongSelf;
kQNStrongObj(transaction);
completeHandler(responseInfo, metrics, response);
[self destroyUploadRequestTransaction:transaction];
}];
}
@end

View File

@@ -0,0 +1,17 @@
//
// QNPartsUploadApiV2.h
// QiniuSDK
//
// Created by yangsen on 2020/11/30.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNPartsUploadPerformer.h"
NS_ASSUME_NONNULL_BEGIN
@interface QNPartsUploadPerformerV2 : QNPartsUploadPerformer
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,148 @@
//
// QNPartsUploadApiV2.m
// QiniuSDK
//
// Created by yangsen on 2020/11/30.
// Copyright © 2020 Qiniu. All rights reserved.
//
#import "QNLogUtil.h"
#import "QNDefine.h"
#import "QNRequestTransaction.h"
#import "QNUploadInfoV2.h"
#import "QNPartsUploadPerformerV2.h"
@interface QNPartsUploadPerformerV2()
@end
@implementation QNPartsUploadPerformerV2
- (QNUploadInfo *)getFileInfoWithDictionary:(NSDictionary *)fileInfoDictionary {
return [QNUploadInfoV2 info:self.uploadSource dictionary:fileInfoDictionary];
}
- (QNUploadInfo *)getDefaultUploadInfo {
return [QNUploadInfoV2 info:self.uploadSource configuration:self.config];
}
- (void)serverInit:(void(^)(QNResponseInfo * _Nullable responseInfo,
QNUploadRegionRequestMetrics * _Nullable metrics,
NSDictionary * _Nullable response))completeHandler {
QNUploadInfoV2 *uploadInfo = (QNUploadInfoV2 *)self.uploadInfo;
if (uploadInfo && [uploadInfo isValid]) {
QNLogInfo(@"key:%@ serverInit success", self.key);
QNResponseInfo *responseInfo = [QNResponseInfo successResponse];
completeHandler(responseInfo, nil, nil);
return;
}
QNRequestTransaction *transaction = [self createUploadRequestTransaction];
kQNWeakSelf;
kQNWeakObj(transaction);
[transaction initPart:^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response) {
kQNStrongSelf;
kQNStrongObj(transaction);
NSString *uploadId = response[@"uploadId"];
NSNumber *expireAt = response[@"expireAt"];
if (responseInfo.isOK && uploadId && expireAt) {
uploadInfo.uploadId = uploadId;
uploadInfo.expireAt = expireAt;
[self recordUploadInfo];
}
completeHandler(responseInfo, metrics, response);
[self destroyUploadRequestTransaction:transaction];
}];
}
- (void)uploadNextData:(void(^)(BOOL stop,
QNResponseInfo * _Nullable responseInfo,
QNUploadRegionRequestMetrics * _Nullable metrics,
NSDictionary * _Nullable response))completeHandler {
QNUploadInfoV2 *uploadInfo = (QNUploadInfoV2 *)self.uploadInfo;
NSError *error = nil;
QNUploadData *data = nil;
@synchronized (self) {
data = [uploadInfo nextUploadData:&error];
data.state = QNUploadStateUploading;
}
if (error) {
QNResponseInfo *responseInfo = [QNResponseInfo responseInfoWithLocalIOError:[NSString stringWithFormat:@"%@", error]];
completeHandler(YES, responseInfo, nil, nil);
return;
}
//
if (data == nil) {
QNLogInfo(@"key:%@ no data left", self.key);
QNResponseInfo *responseInfo = nil;
if (uploadInfo.getSourceSize == 0) {
responseInfo = [QNResponseInfo responseInfoOfZeroData:@"file is empty"];
} else {
responseInfo = [QNResponseInfo responseInfoWithSDKInteriorError:@"no chunk left"];
}
completeHandler(YES, responseInfo, nil, nil);
return;
}
kQNWeakSelf;
void (^progress)(long long, long long) = ^(long long totalBytesWritten, long long totalBytesExpectedToWrite){
kQNStrongSelf;
data.uploadSize = totalBytesWritten;
[self notifyProgress:false];
};
QNRequestTransaction *transaction = [self createUploadRequestTransaction];
kQNWeakObj(transaction);
[transaction uploadPart:uploadInfo.uploadId
partIndex:[uploadInfo getPartIndexOfData:data]
partData:data.data
progress:progress
complete:^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response) {
kQNStrongSelf;
kQNStrongObj(transaction);
NSString *etag = response[@"etag"];
NSString *md5 = response[@"md5"];
if (responseInfo.isOK && etag && md5) {
data.etag = etag;
data.state = QNUploadStateComplete;
[self recordUploadInfo];
[self notifyProgress:false];
} else {
data.state = QNUploadStateWaitToUpload;
}
completeHandler(NO, responseInfo, metrics, response);
[self destroyUploadRequestTransaction:transaction];
}];
}
- (void)completeUpload:(void(^)(QNResponseInfo * _Nullable responseInfo,
QNUploadRegionRequestMetrics * _Nullable metrics,
NSDictionary * _Nullable response))completeHandler {
QNUploadInfoV2 *uploadInfo = (QNUploadInfoV2 *)self.uploadInfo;
NSArray *partInfoArray = [uploadInfo getPartInfoArray];
QNRequestTransaction *transaction = [self createUploadRequestTransaction];
kQNWeakSelf;
kQNWeakObj(transaction);
[transaction completeParts:self.fileName uploadId:uploadInfo.uploadId partInfoArray:partInfoArray complete:^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response) {
kQNStrongSelf;
kQNStrongObj(transaction);
if (responseInfo.isOK) {
[self notifyProgress:true];
}
completeHandler(responseInfo, metrics, response);
[self destroyUploadRequestTransaction:transaction];
}];
}
@end

View File

@@ -0,0 +1,23 @@
//
// QNUpProgress.h
// QiniuSDK
//
// Created by yangsen on 2021/5/21.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "QNUploadOption.h"
NS_ASSUME_NONNULL_BEGIN
@interface QNUpProgress : NSObject
+ (instancetype)progress:(QNUpProgressHandler)progress byteProgress:(QNUpByteProgressHandler)byteProgress;
- (void)progress:(NSString *)key uploadBytes:(long long)uploadBytes totalBytes:(long long)totalBytes;
- (void)notifyDone:(NSString *)key totalBytes:(long long)totalBytes;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,87 @@
//
// QNUpProgress.m
// QiniuSDK
//
// Created by yangsen on 2021/5/21.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "QNAsyncRun.h"
#import "QNUpProgress.h"
@interface QNUpProgress()
@property(nonatomic, assign)long long maxProgressUploadBytes;
@property(nonatomic, assign)long long previousUploadBytes;
@property(nonatomic, copy)QNUpProgressHandler progress;
@property(nonatomic, copy)QNUpByteProgressHandler byteProgress;
@end
@implementation QNUpProgress
+ (instancetype)progress:(QNUpProgressHandler)progress byteProgress:(QNUpByteProgressHandler)byteProgress {
QNUpProgress *upProgress = [[QNUpProgress alloc] init];
upProgress.maxProgressUploadBytes = -1;
upProgress.previousUploadBytes = 0;
upProgress.progress = progress;
upProgress.byteProgress = byteProgress;
return upProgress;
}
- (void)progress:(NSString *)key uploadBytes:(long long)uploadBytes totalBytes:(long long)totalBytes {
if ((self.progress == nil && self.byteProgress == nil) || uploadBytes < 0 || (totalBytes > 0 && uploadBytes > totalBytes)) {
return;
}
if (totalBytes > 0) {
@synchronized (self) {
if (self.maxProgressUploadBytes < 0) {
self.maxProgressUploadBytes = totalBytes * 0.95;
}
}
if (uploadBytes > self.maxProgressUploadBytes) {
return;
}
}
@synchronized (self) {
if (uploadBytes > self.previousUploadBytes) {
self.previousUploadBytes = uploadBytes;
} else {
return;
}
}
[self notify:key uploadBytes:uploadBytes totalBytes:totalBytes];
}
- (void)notifyDone:(NSString *)key totalBytes:(long long)totalBytes {
[self notify:key uploadBytes:totalBytes totalBytes:totalBytes];
}
- (void)notify:(NSString *)key uploadBytes:(long long)uploadBytes totalBytes:(long long)totalBytes {
if (self.progress == nil && self.byteProgress == nil) {
return;
}
if (self.byteProgress) {
QNAsyncRunInMain(^{
self.byteProgress(key, uploadBytes, totalBytes);
});
return;
}
if (totalBytes <= 0) {
return;
}
if (self.progress) {
QNAsyncRunInMain(^{
double notifyPercent = (double) uploadBytes / (double) totalBytes;
self.progress(key, notifyPercent);
});
}
}
@end

33
Pods/Qiniu/QiniuSDK/Storage/QNUpToken.h generated Normal file
View File

@@ -0,0 +1,33 @@
//
// QNUpToken.h
// QiniuSDK
//
// Created by bailong on 15/6/7.
// Copyright (c) 2015年 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface QNUpToken : NSObject
+ (instancetype)parse:(NSString *)token;
@property (assign, nonatomic, readonly) long deadline;
@property (copy , nonatomic, readonly) NSString *access;
@property (copy , nonatomic, readonly) NSString *bucket;
@property (copy , nonatomic, readonly) NSString *token;
@property (readonly) BOOL isValid;
@property (readonly) BOOL hasReturnUrl;
+ (instancetype)getInvalidToken;
- (NSString *)index;
/// 是否在未来 duration 分钟内有效
- (BOOL)isValidForDuration:(long)duration;
/// 在是否在 date 之前有效
- (BOOL)isValidBeforeDate:(NSDate *)date;
@end

103
Pods/Qiniu/QiniuSDK/Storage/QNUpToken.m generated Normal file
View File

@@ -0,0 +1,103 @@
//
// QNUpToken.m
// QiniuSDK
//
// Created by bailong on 15/6/7.
// Copyright (c) 2015 Qiniu. All rights reserved.
//
#import "QNUrlSafeBase64.h"
#import "QNUpToken.h"
#define kQNPolicyKeyScope @"scope"
#define kQNPolicyKeyDeadline @"deadline"
#define kQNPolicyKeyReturnUrl @"returnUrl"
@interface QNUpToken ()
- (instancetype)init:(NSDictionary *)policy token:(NSString *)token;
@end
@implementation QNUpToken
+ (instancetype)getInvalidToken {
QNUpToken *token = [[QNUpToken alloc] init];
token->_deadline = -1;
return token;
}
- (instancetype)init:(NSDictionary *)policy token:(NSString *)token {
if (self = [super init]) {
_token = token;
_access = [self getAccess];
_bucket = [self getBucket:policy];
_deadline = [policy[kQNPolicyKeyDeadline] longValue];
_hasReturnUrl = (policy[kQNPolicyKeyReturnUrl] != nil);
}
return self;
}
- (NSString *)getAccess {
NSRange range = [_token rangeOfString:@":" options:NSCaseInsensitiveSearch];
return [_token substringToIndex:range.location];
}
- (NSString *)getBucket:(NSDictionary *)info {
NSString *scope = [info objectForKey:kQNPolicyKeyScope];
if (!scope || [scope isKindOfClass:[NSNull class]]) {
return @"";
}
NSRange range = [scope rangeOfString:@":"];
if (range.location == NSNotFound) {
return scope;
}
return [scope substringToIndex:range.location];
}
+ (instancetype)parse:(NSString *)token {
if (token == nil) {
return nil;
}
NSArray *array = [token componentsSeparatedByString:@":"];
if (array == nil || array.count != 3) {
return nil;
}
NSData *data = [QNUrlSafeBase64 decodeString:array[2]];
if (!data) {
return nil;
}
NSError *tmp = nil;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&tmp];
if (tmp != nil || dict[kQNPolicyKeyScope] == nil || dict[kQNPolicyKeyDeadline] == nil) {
return nil;
}
return [[QNUpToken alloc] init:dict token:token];
}
- (NSString *)index {
return [NSString stringWithFormat:@"%@:%@", _access, _bucket];
}
- (BOOL)isValid {
return _access && _access.length > 0 && _bucket && _bucket.length > 0;
}
- (BOOL)isValidForDuration:(long)duration {
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:duration];
return [self isValidBeforeDate:date];
}
- (BOOL)isValidBeforeDate:(NSDate *)date {
if (date == nil) {
return NO;
}
return [date timeIntervalSince1970] < self.deadline;
}
@end

View File

@@ -0,0 +1,57 @@
//
// QNUploadBlock.h
// QiniuSDK
//
// Created by yangsen on 2021/5/10.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "QNUploadData.h"
NS_ASSUME_NONNULL_BEGIN
@interface QNUploadBlock : NSObject
// block下标
@property(nonatomic, assign, readonly)NSInteger index;
// 当前data偏移量
@property(nonatomic, assign, readonly)long long offset;
// 块大小
@property(nonatomic, assign, readonly)NSInteger size;
// 需要上传的片数据
@property(nonatomic, strong, readonly)NSArray <QNUploadData *> *uploadDataList;
// block上传上下文信息
@property(nonatomic, copy, nullable)NSString *context;
// block md5
@property(nonatomic, copy, nullable)NSString *md5;
// 是否已完成上传【不进行离线缓存】
@property(nonatomic, assign, readonly)BOOL isCompleted;
// 上传大小 【不进行离线缓存】
@property(nonatomic, assign, readonly)NSInteger uploadSize;
// ctx 过期时间
@property(nonatomic, strong, nullable)NSNumber *expiredAt;
//MARK:-- 构造
+ (instancetype)blockFromDictionary:(NSDictionary *)dictionary;
- (instancetype)initWithOffset:(long long)offset
blockSize:(NSInteger)blockSize
dataSize:(NSInteger)dataSize
index:(NSInteger)index;
// 检测 block 是否有效
- (BOOL)isValid;
/// 获取下一个需要上传的块
- (QNUploadData *)nextUploadDataWithoutCheckData;
/// 检测 data 状态,处理出于上传状态的无 data 数据的情况,无 data 数据则状态调整为监测数据状态
- (void)checkInfoStateAndUpdate;
/// 清理上传状态
- (void)clearUploadState;
/// 转化字典
- (NSDictionary *)toDictionary;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,175 @@
//
// QNUploadBlock.m
// QiniuSDK
//
// Created by yangsen on 2021/5/10.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "QNUploadBlock.h"
@interface QNUploadBlock()
@property(nonatomic, assign)long long offset;
@property(nonatomic, assign)NSInteger size;
@property(nonatomic, assign)NSInteger index;
@property(nonatomic, strong)NSArray <QNUploadData *> *uploadDataList;
@end
@implementation QNUploadBlock
+ (instancetype)blockFromDictionary:(NSDictionary *)dictionary{
if (![dictionary isKindOfClass:[NSDictionary class]]) {
return nil;
}
QNUploadBlock *block = [[QNUploadBlock alloc] init];
block.offset = [dictionary[@"offset"] longLongValue];
block.size = [dictionary[@"size"] integerValue];
block.index = [dictionary[@"index"] integerValue];
block.expiredAt = dictionary[@"expired_at"];
block.md5 = dictionary[@"md5"];
block.context = dictionary[@"context"];
NSArray *uploadDataInfos = dictionary[@"uploadDataList"];
if ([uploadDataInfos isKindOfClass:[NSArray class]]) {
NSMutableArray *uploadDataList = [NSMutableArray array];
for (NSDictionary *uploadDataInfo in uploadDataInfos) {
QNUploadData *data = [QNUploadData dataFromDictionary:uploadDataInfo];
if (!data) {
return nil;
}
[uploadDataList addObject:data];
}
block.uploadDataList = [uploadDataList copy];
}
return block;
}
- (instancetype)initWithOffset:(long long)offset
blockSize:(NSInteger)blockSize
dataSize:(NSInteger)dataSize
index:(NSInteger)index {
if (self = [super init]) {
_offset = offset;
_size = blockSize;
_index = index;
_uploadDataList = [self createDataList:dataSize];
}
return self;
}
- (BOOL)isValid {
if (!self.expiredAt || self.expiredAt.integerValue <= 0) {
// block:
return true;
}
//
return (self.expiredAt.doubleValue - 12*3600) > [[NSDate date] timeIntervalSince1970];
}
- (BOOL)isCompleted{
if (self.uploadDataList == nil) {
return true;
}
BOOL isCompleted = true;
for (QNUploadData *data in self.uploadDataList) {
if (data.isUploaded == false) {
isCompleted = false;
break;
}
}
return isCompleted;
}
- (NSInteger)uploadSize {
if (self.uploadDataList == nil) {
return 0;
}
NSInteger uploadSize = 0;
for (QNUploadData *data in self.uploadDataList) {
uploadSize += data.uploadSize;
}
return uploadSize;
}
- (NSArray *)createDataList:(long long)dataSize{
long long offSize = 0;
NSInteger dataIndex = 0;
NSMutableArray *datas = [NSMutableArray array];
while (offSize < self.size) {
long long lastSize = self.size - offSize;
long long dataSizeP = MIN(lastSize, dataSize);
QNUploadData *data = [[QNUploadData alloc] initWithOffset:offSize
dataSize:dataSizeP
index:dataIndex];
[datas addObject:data];
offSize += dataSizeP;
dataIndex += 1;
}
return [datas copy];
}
- (QNUploadData *)nextUploadDataWithoutCheckData {
if (!self.uploadDataList || self.uploadDataList.count == 0) {
return nil;
}
QNUploadData *data = nil;
for (QNUploadData *dataP in self.uploadDataList) {
if ([dataP needToUpload]) {
data = dataP;
break;
}
}
return data;
}
- (void)clearUploadState{
self.md5 = nil;
self.context = nil;
for (QNUploadData *data in self.uploadDataList) {
[data clearUploadState];
}
}
- (void)checkInfoStateAndUpdate {
for (QNUploadData *data in self.uploadDataList) {
[data checkStateAndUpdate];
}
}
- (NSDictionary *)toDictionary{
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
dictionary[@"offset"] = @(self.offset);
dictionary[@"size"] = @(self.size);
dictionary[@"index"] = @(self.index);
dictionary[@"expired_at"] = self.expiredAt ?: @(0);
dictionary[@"md5"] = self.md5 ?: @"";
if (self.context) {
dictionary[@"context"] = self.context;
}
if (self.uploadDataList) {
NSMutableArray *uploadDataInfos = [NSMutableArray array];
for (QNUploadData *data in self.uploadDataList) {
NSDictionary *uploadDataInfo = [data toDictionary];
if (uploadDataInfo) {
[uploadDataInfos addObject:uploadDataInfo];
}
}
dictionary[@"uploadDataList"] = [uploadDataInfos copy];
}
return [dictionary copy];
}
@end

View File

@@ -0,0 +1,60 @@
//
// QNUploadData.h
// QiniuSDK
//
// Created by yangsen on 2021/5/10.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, QNUploadState) {
QNUploadStateNeedToCheck, // 需要检测数据
QNUploadStateWaitToUpload, // 等待上传
QNUploadStateUploading, // 正在上传
QNUploadStateComplete, // 上传结束
};
@interface QNUploadData : NSObject
/// 当前data偏移量
@property(nonatomic, assign, readonly)long long offset;
/// 当前data大小
@property(nonatomic, assign, readonly)long long size;
/// data下标
@property(nonatomic, assign, readonly)NSInteger index;
/// data etag
@property(nonatomic, copy, nullable)NSString *etag;
/// data md5
@property(nonatomic, copy, nullable)NSString *md5;
/// 上传状态
@property(nonatomic, assign)QNUploadState state;
/// 上传进度 【不进行离线缓存】
@property(nonatomic, assign)long long uploadSize;
/// 上传数据 【不进行离线缓存】
@property(nonatomic, strong, nullable)NSData *data;
//MARK:-- 构造
+ (instancetype)dataFromDictionary:(NSDictionary *)dictionary;
- (instancetype)initWithOffset:(long long)offset
dataSize:(long long)dataSize
index:(NSInteger)index;
//MARK:-- logic
/// 是否需要上传
- (BOOL)needToUpload;
/// 是否已经上传
- (BOOL)isUploaded;
/// 检测 data 状态,处理出于上传状态的无 data 数据的情况,无 data 数据则状态调整为监测数据状态
- (void)checkStateAndUpdate;
/// 转化字典
- (NSDictionary *)toDictionary;
/// 清除状态
- (void)clearUploadState;
@end
NS_ASSUME_NONNULL_END

112
Pods/Qiniu/QiniuSDK/Storage/QNUploadData.m generated Normal file
View File

@@ -0,0 +1,112 @@
//
// QNUploadData.m
// QiniuSDK
//
// Created by yangsen on 2021/5/10.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "QNUploadData.h"
@interface QNUploadData()
@property(nonatomic, assign)long long offset;
@property(nonatomic, assign)long long size;
@property(nonatomic, assign)NSInteger index;
@end
@implementation QNUploadData
+ (instancetype)dataFromDictionary:(NSDictionary *)dictionary{
if (![dictionary isKindOfClass:[NSDictionary class]]) {
return nil;
}
QNUploadData *data = [[QNUploadData alloc] init];
data.offset = [dictionary[@"offset"] longLongValue];
data.size = [dictionary[@"size"] longLongValue];
data.index = [dictionary[@"index"] integerValue];
data.etag = dictionary[@"etag"];
data.md5 = dictionary[@"md5"];
data.state = [dictionary[@"state"] intValue];
return data;
}
- (instancetype)initWithOffset:(long long)offset
dataSize:(long long)dataSize
index:(NSInteger)index {
if (self = [super init]) {
_offset = offset;
_size = dataSize;
_index = index;
_etag = @"";
_md5 = @"";
_state = QNUploadStateNeedToCheck;
}
return self;
}
- (BOOL)needToUpload {
BOOL needToUpload = false;
switch (self.state) {
case QNUploadStateNeedToCheck:
case QNUploadStateWaitToUpload:
needToUpload = true;
break;
default:
break;
}
return needToUpload;
}
- (BOOL)isUploaded {
return self.state == QNUploadStateComplete;
}
- (void)setState:(QNUploadState)state {
switch (state) {
case QNUploadStateNeedToCheck:
case QNUploadStateWaitToUpload:
case QNUploadStateUploading:
self.uploadSize = 0;
self.etag = @"";
break;
default:
self.data = nil;
break;
}
_state = state;
}
- (long long)uploadSize {
if (self.state == QNUploadStateComplete) {
return _size;
} else {
return _uploadSize;
}
}
- (void)clearUploadState{
self.state = QNUploadStateNeedToCheck;
self.etag = nil;
self.md5 = nil;
}
- (void)checkStateAndUpdate {
if ((self.state == QNUploadStateWaitToUpload || self.state == QNUploadStateUploading) && self.data == nil) {
self.state = QNUploadStateNeedToCheck;
}
}
- (NSDictionary *)toDictionary{
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
dictionary[@"offset"] = @(self.offset);
dictionary[@"size"] = @(self.size);
dictionary[@"index"] = @(self.index);
dictionary[@"etag"] = self.etag ?: @"";
dictionary[@"md5"] = self.md5 ?: @"";
dictionary[@"state"] = @(self.state);
return [dictionary copy];
}
@end

View File

@@ -0,0 +1,75 @@
//
// QNUploadInfo.h
// QiniuSDK
//
// Created by yangsen on 2021/5/10.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "QNUploadSource.h"
NS_ASSUME_NONNULL_BEGIN
@interface QNUploadInfo : NSObject
/// 构造函数
/// @param source 上传数据源
+ (instancetype)info:(id <QNUploadSource>)source;
/// 通过字典信息进行配置
/// @param dictionary 配置信息
- (void)setInfoFromDictionary:(NSDictionary *)dictionary;
/// 信息转化为字典
- (NSDictionary *)toDictionary;
/// 数据源是否有效,为空则无效
- (BOOL)hasValidResource;
/// 是否有效,数据源是否有效 & 上传信息有效比如断点续传时UploadId是否有效
- (BOOL)isValid;
/// 是否可以重新
- (BOOL)couldReloadSource;
/// 重新加载数据
- (BOOL)reloadSource;
/// 数据源ID
- (NSString *)getSourceId;
/// 数据源大小,未知为:-1
- (long long)getSourceSize;
/// 是否为同一个 UploadInfo
/// 同一个source 相同,上传方式相同
/// @param info 上传信息
- (BOOL)isSameUploadInfo:(QNUploadInfo *)info;
/// 已上传大小
- (long long)uploadSize;
/// 资源是否已全部上传
/// 子类重写
- (BOOL)isAllUploaded;
/// 清除上传状态信息
/// 子类重写
- (void)clearUploadState;
/// 检查文件状态, 主要处理没有 data 但处于上传状态
- (void)checkInfoStateAndUpdate;
/// 读取数据
/// @param dataSize 读取数据大小
/// @param dataOffset 数据偏移量
/// @param error 读取时的错误信息
- (NSData *)readData:(NSInteger)dataSize dataOffset:(long long)dataOffset error:(NSError **)error;
/// 关闭流
- (void)close;
@end
#define kQNUploadInfoTypeKey @"infoType"
NS_ASSUME_NONNULL_END

115
Pods/Qiniu/QiniuSDK/Storage/QNUploadInfo.m generated Normal file
View File

@@ -0,0 +1,115 @@
//
// QNUploadInfo.m
// QiniuSDK
//
// Created by yangsen on 2021/5/10.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "QNErrorCode.h"
#import "QNUploadInfo.h"
@interface QNUploadInfo()
@property(nonatomic, copy)NSString *sourceId;
@property(nonatomic, assign)long long sourceSize;
@property(nonatomic, copy)NSString *fileName;
@property(nonatomic, strong)id <QNUploadSource> source;
@end
@implementation QNUploadInfo
+ (instancetype)info:(id <QNUploadSource>)source {
QNUploadInfo *info = [[self alloc] init];
info.source = source;
info.sourceSize = [source getSize];
info.fileName = [source getFileName];
return info;
}
- (void)setInfoFromDictionary:(NSDictionary *)dictionary {
self.sourceSize = [dictionary[@"sourceSize"] longValue];
self.sourceId = dictionary[@"sourceId"];
}
- (NSDictionary *)toDictionary {
return @{@"sourceSize" : @([self getSourceSize]),
@"sourceId" : self.sourceId ?: @""};
}
- (BOOL)hasValidResource {
return self.source != nil;
}
- (BOOL)isValid {
return [self hasValidResource];
}
- (BOOL)couldReloadSource {
return [self.source couldReloadSource];
}
- (BOOL)reloadSource {
return [self.source reloadSource];
}
- (NSString *)getSourceId {
return [self.source getId];
}
- (long long)getSourceSize {
return [self.source getSize];
}
- (BOOL)isSameUploadInfo:(QNUploadInfo *)info {
if (info == nil || ((self.sourceId.length > 0 || info.sourceId.length > 0) && ![self.sourceId isEqualToString:info.sourceId])) {
return false;
}
//
if (info.sourceSize > kQNUnknownSourceSize &&
self.sourceSize > kQNUnknownSourceSize &&
info.sourceSize != self.sourceSize) {
return false;
}
return true;
}
- (long long)uploadSize {
return 0;
}
- (BOOL)isAllUploaded {
return true;
}
- (void)clearUploadState {
}
- (void)checkInfoStateAndUpdate {
}
- (NSData *)readData:(NSInteger)dataSize dataOffset:(long long)dataOffset error:(NSError **)error {
if (!self.source) {
*error = [NSError errorWithDomain:NSStreamSOCKSErrorDomain code:kQNLocalIOError userInfo:@{NSLocalizedDescriptionKey : @"file is not exist"}];
return nil;
}
NSData *data = nil;
@synchronized (self.source) {
data = [self.source readData:dataSize dataOffset:dataOffset error:error];
}
if (*error == nil && data != nil && (data.length == 0 || data.length != dataSize)) {
self.sourceSize = data.length + dataOffset;
}
return data;
}
- (void)close {
[self.source close];
}
@end

View File

@@ -0,0 +1,35 @@
//
// QNUploadInfoV1.h
// QiniuSDK
//
// Created by yangsen on 2021/5/10.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "QNConfiguration.h"
#import "QNUploadData.h"
#import "QNUploadBlock.h"
#import "QNUploadInfo.h"
NS_ASSUME_NONNULL_BEGIN
@interface QNUploadInfoV1 : QNUploadInfo
+ (instancetype)info:(id <QNUploadSource>)source
configuration:(QNConfiguration *)configuration;
+ (instancetype)info:(id <QNUploadSource>)source
dictionary:(NSDictionary *)dictionary;
- (BOOL)isFirstData:(QNUploadData *)data;
- (QNUploadBlock *)nextUploadBlock:(NSError **)error;
- (QNUploadData *)nextUploadData:(QNUploadBlock *)block;
- (NSArray <NSString *> *)allBlocksContexts;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,327 @@
//
// QNUploadInfoV1.m
// QiniuSDK
//
// Created by yangsen on 2021/5/10.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "NSData+QNMD5.h"
#import "QNMutableArray.h"
#import "QNUploadInfoV1.h"
#define kTypeValue @"UploadInfoV1"
#define kBlockSize (4 * 1024 * 1024)
@interface QNUploadInfoV1()
@property(nonatomic, assign)int dataSize;
@property(nonatomic, strong)QNMutableArray *blockList;
@property(nonatomic, assign)BOOL isEOF;
@property(nonatomic, strong, nullable)NSError *readError;
@end
@implementation QNUploadInfoV1
+ (instancetype)info:(id<QNUploadSource>)source
configuration:(nonnull QNConfiguration *)configuration {
QNUploadInfoV1 *info = [QNUploadInfoV1 info:source];
if (configuration.useConcurrentResumeUpload || configuration.chunkSize > kBlockSize) {
info.dataSize = kBlockSize;
} else {
info.dataSize = configuration.chunkSize;
}
info.blockList = [QNMutableArray array];
return info;
}
+ (instancetype)info:(id <QNUploadSource>)source
dictionary:(NSDictionary *)dictionary {
if (dictionary == nil) {
return nil;
}
int dataSize = [dictionary[@"dataSize"] intValue];
NSString *type = dictionary[kQNUploadInfoTypeKey];
NSArray *blockInfoList = dictionary[@"blockList"];
QNMutableArray *blockList = [QNMutableArray array];
if ([blockInfoList isKindOfClass:[NSArray class]]) {
for (int i = 0; i < blockInfoList.count; i++) {
NSDictionary *blockInfo = blockInfoList[i];
if ([blockInfo isKindOfClass:[NSDictionary class]]) {
QNUploadBlock *block = [QNUploadBlock blockFromDictionary:blockInfo];
if (block == nil) {
return nil;
}
[blockList addObject:block];
}
}
}
QNUploadInfoV1 *info = [QNUploadInfoV1 info:source];
[info setInfoFromDictionary:dictionary];
info.dataSize = dataSize;
info.blockList = blockList;
if (![type isEqualToString:kTypeValue] || ![[source getId] isEqualToString:[info getSourceId]]) {
return nil;
} else {
return info;
}
}
- (BOOL)isFirstData:(QNUploadData *)data {
return data.index == 0;
}
- (BOOL)isValid {
if (![super isValid]) {
return false;
}
__block BOOL valid = true;
[self.blockList enumerateObjectsUsingBlock:^(QNUploadBlock *block, NSUInteger idx, BOOL * _Nonnull stop) {
valid = [block isValid];
if (!valid) {
*stop = true;
}
}];
return valid;
}
- (BOOL)reloadSource {
self.isEOF = false;
self.readError = nil;
return [super reloadSource];
}
- (BOOL)isSameUploadInfo:(QNUploadInfo *)info {
if (![super isSameUploadInfo:info]) {
return false;
}
if (![info isKindOfClass:[QNUploadInfoV1 class]]) {
return false;
}
return self.dataSize == [(QNUploadInfoV1 *)info dataSize];
}
- (void)clearUploadState {
if (self.blockList == nil || self.blockList.count == 0) {
return;
}
[self.blockList enumerateObjectsUsingBlock:^(QNUploadBlock *block, NSUInteger idx, BOOL * _Nonnull stop) {
[block clearUploadState];
}];
}
- (void)checkInfoStateAndUpdate {
[self.blockList enumerateObjectsUsingBlock:^(QNUploadBlock *block, NSUInteger idx, BOOL * _Nonnull stop) {
[block checkInfoStateAndUpdate];
}];
}
- (long long)uploadSize {
if (self.blockList == nil || self.blockList.count == 0) {
return 0;
}
__block long long uploadSize = 0;
[self.blockList enumerateObjectsUsingBlock:^(QNUploadBlock *block, NSUInteger idx, BOOL * _Nonnull stop) {
uploadSize += [block uploadSize];
}];
return uploadSize;
}
- (BOOL)isAllUploaded {
if (!_isEOF) {
return false;
}
if (self.blockList == nil || self.blockList.count == 0) {
return true;
}
__block BOOL isAllUploaded = true;
[self.blockList enumerateObjectsUsingBlock:^(QNUploadBlock *block, NSUInteger idx, BOOL * _Nonnull stop) {
if (!block.isCompleted) {
isAllUploaded = false;
*stop = true;
}
}];
return isAllUploaded;
}
- (NSDictionary *)toDictionary {
NSMutableDictionary *dictionary = [[super toDictionary] mutableCopy];
if (dictionary == nil) {
dictionary = [NSMutableDictionary dictionary];
}
[dictionary setObject:kTypeValue forKey:kQNUploadInfoTypeKey];
[dictionary setObject:@(self.dataSize) forKey:@"dataSize"];
if (self.blockList != nil && self.blockList.count != 0) {
NSMutableArray *blockInfoList = [NSMutableArray array];
[self.blockList enumerateObjectsUsingBlock:^(QNUploadBlock *block, NSUInteger idx, BOOL * _Nonnull stop) {
[blockInfoList addObject:[block toDictionary]];
}];
[dictionary setObject:[blockInfoList copy] forKey:@"blockList"];
}
return [dictionary copy];
}
- (QNUploadBlock *)nextUploadBlock:(NSError **)error {
// blockList block
QNUploadBlock *block = [self nextUploadBlockFormBlockList];
// blockList block
if (block == nil) {
if (self.isEOF) {
return nil;
} else if (self.readError) {
*error = self.readError;
return nil;
}
// block
long long blockOffset = 0;
if (self.blockList.count > 0) {
QNUploadBlock *lastBlock = self.blockList[self.blockList.count - 1];
blockOffset = lastBlock.offset + (long long)(lastBlock.size);
}
block = [[QNUploadBlock alloc] initWithOffset:blockOffset blockSize:kBlockSize dataSize:self.dataSize index:self.blockList.count];
}
QNUploadBlock *loadBlock = [self loadBlockData:block error:error];
if (*error != nil) {
self.readError = *error;
return nil;
}
if (loadBlock == nil) {
// 没有加在到 block, 也即数据源读取结束
self.isEOF = true;
// 有多余的 block 则移除,移除中包含 block
if (self.blockList.count > block.index) {
self.blockList = [[self.blockList subarrayWithRange:NSMakeRange(0, block.index)] mutableCopy];
}
} else {
// 加在到 block
if (loadBlock.index == self.blockList.count) {
// 新块block index 等于 blockList size 则为新创建 block需要加入 blockList
[self.blockList addObject:loadBlock];
} else if (loadBlock != block) {
// 更换块:重新加在了 block 更换信息
[self.blockList replaceObjectAtIndex:loadBlock.index withObject:loadBlock];
}
// 数据源读取结束,块读取大小小于预期,读取结束
if (loadBlock.size < kBlockSize) {
self.isEOF = true;
// 有多余的 block 则移除,移除中不包含 block
if (self.blockList.count > block.index + 1) {
self.blockList = [[self.blockList subarrayWithRange:NSMakeRange(0, block.index + 1)] mutableCopy];
}
}
}
return loadBlock;
}
- (QNUploadBlock *)nextUploadBlockFormBlockList {
if (self.blockList == nil || self.blockList.count == 0) {
return nil;
}
__block QNUploadBlock *block = nil;
[self.blockList enumerateObjectsUsingBlock:^(QNUploadBlock *blockP, NSUInteger idx, BOOL * _Nonnull stop) {
QNUploadData *data = [blockP nextUploadDataWithoutCheckData];
if (data != nil) {
block = blockP;
*stop = true;
}
}];
return block;
}
//
// 1.
// 2.
// 2.1 EOF null
// 2.2
// 2.2.1
// 2.2.2
- (QNUploadBlock *)loadBlockData:(QNUploadBlock *)block error:(NSError **)error {
if (block == nil) {
return nil;
}
// block
//
QNUploadData *nextUploadData = [block nextUploadDataWithoutCheckData];
if (nextUploadData.state == QNUploadStateWaitToUpload && nextUploadData.data != nil) {
return block;
}
// block
// block blockBytes
NSData *blockBytes = nil;
blockBytes = [self readData:block.size dataOffset:block.offset error:error];
if (*error != nil) {
return nil;
}
// 没有数据不需要上传
if (blockBytes == nil || blockBytes.length == 0) {
return nil;
}
NSString *md5 = [blockBytes qn_md5];
// 判断当前 block 的数据是否和实际数据吻合,不吻合则之前 block 被抛弃,重新创建 block
if (blockBytes.length != block.size || block.md5 == nil || ![block.md5 isEqualToString:md5]) {
block = [[QNUploadBlock alloc] initWithOffset:block.offset blockSize:blockBytes.length dataSize:self.dataSize index:block.index];
block.md5 = md5;
}
for (QNUploadData *data in block.uploadDataList) {
if (data.state != QNUploadStateComplete) {
// 还未上传的
data.data = [blockBytes subdataWithRange:NSMakeRange((NSUInteger)data.offset, (NSUInteger)data.size)];
data.state = QNUploadStateWaitToUpload;
} else {
// 已经上传的
data.state = QNUploadStateComplete;
}
}
return block;
}
- (QNUploadData *)nextUploadData:(QNUploadBlock *)block {
if (block == nil) {
return nil;
}
return [block nextUploadDataWithoutCheckData];
}
- (NSArray <NSString *> *)allBlocksContexts {
if (self.blockList == nil || self.blockList.count == 0) {
return nil;
}
NSMutableArray *contexts = [NSMutableArray array];
[self.blockList enumerateObjectsUsingBlock:^(QNUploadBlock *block, NSUInteger idx, BOOL * _Nonnull stop) {
if (block.context && block.context.length > 0) {
[contexts addObject:block.context];
}
}];
return [contexts copy];
}
@end

View File

@@ -0,0 +1,35 @@
//
// QNUploadInfoV2.h
// QiniuSDK
//
// Created by yangsen on 2021/5/13.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "QNConfiguration.h"
#import "QNUploadData.h"
#import "QNUploadInfo.h"
NS_ASSUME_NONNULL_BEGIN
@interface QNUploadInfoV2 : QNUploadInfo
@property(nonatomic, copy, nullable)NSString *uploadId;
@property(nonatomic, strong, nullable)NSNumber *expireAt;
+ (instancetype)info:(id <QNUploadSource>)source
configuration:(QNConfiguration *)configuration;
+ (instancetype)info:(id <QNUploadSource>)source
dictionary:(NSDictionary *)dictionary;
- (QNUploadData *)nextUploadData:(NSError **)error;
- (NSInteger)getPartIndexOfData:(QNUploadData *)data;
- (NSArray <NSDictionary <NSString *, NSObject *> *> *)getPartInfoArray;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,322 @@
//
// QNUploadInfoV2.m
// QiniuSDK
//
// Created by yangsen on 2021/5/13.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "NSData+QNMD5.h"
#import "QNMutableArray.h"
#import "QNUploadInfoV2.h"
#define kTypeValue @"UploadInfoV2"
#define kMaxDataSize (1024 * 1024 * 1024)
@interface QNUploadInfoV2()
@property(nonatomic, assign)int dataSize;
@property(nonatomic, strong)QNMutableArray *dataList;
@property(nonatomic, assign)BOOL isEOF;
@property(nonatomic, strong, nullable)NSError *readError;
@end
@implementation QNUploadInfoV2
+ (instancetype)info:(id<QNUploadSource>)source
configuration:(nonnull QNConfiguration *)configuration {
QNUploadInfoV2 *info = [QNUploadInfoV2 info:source];
info.dataSize = MIN(configuration.chunkSize, kMaxDataSize);
info.dataList = [QNMutableArray array];
return info;
}
+ (instancetype)info:(id <QNUploadSource>)source
dictionary:(NSDictionary *)dictionary {
if (dictionary == nil) {
return nil;
}
int dataSize = [dictionary[@"dataSize"] intValue];
NSNumber *expireAt = dictionary[@"expireAt"];
NSString *uploadId = dictionary[@"uploadId"];
NSString *type = dictionary[kQNUploadInfoTypeKey];
if (expireAt == nil || ![expireAt isKindOfClass:[NSNumber class]] ||
uploadId == nil || ![uploadId isKindOfClass:[NSString class]] || uploadId.length == 0) {
return nil;
}
NSArray *dataInfoList = dictionary[@"dataList"];
QNMutableArray *dataList = [QNMutableArray array];
if ([dataInfoList isKindOfClass:[NSArray class]]) {
for (int i = 0; i < dataInfoList.count; i++) {
NSDictionary *dataInfo = dataInfoList[i];
if ([dataInfo isKindOfClass:[NSDictionary class]]) {
QNUploadData *data = [QNUploadData dataFromDictionary:dataInfo];
if (data == nil) {
return nil;
}
[dataList addObject:data];
}
}
}
QNUploadInfoV2 *info = [QNUploadInfoV2 info:source];
[info setInfoFromDictionary:dictionary];
info.expireAt = expireAt;
info.uploadId = uploadId;
info.dataSize = dataSize;
info.dataList = dataList;
if (![type isEqualToString:kTypeValue] || ![[source getId] isEqualToString:[info getSourceId]]) {
return nil;
} else {
return info;
}
}
- (BOOL)isValid {
if (![super isValid]) {
return false;
}
if (!self.expireAt || !self.uploadId || self.uploadId.length == 0) {
return false;
}
return (self.expireAt.doubleValue - 2*3600) > [[NSDate date] timeIntervalSince1970];
}
- (BOOL)reloadSource {
self.isEOF = false;
self.readError = nil;
return [super reloadSource];
}
- (BOOL)isSameUploadInfo:(QNUploadInfo *)info {
if (![super isSameUploadInfo:info]) {
return false;
}
if (![info isKindOfClass:[QNUploadInfoV2 class]]) {
return false;
}
return self.dataSize == [(QNUploadInfoV2 *)info dataSize];
}
- (void)clearUploadState {
self.expireAt = nil;
self.uploadId = nil;
if (self.dataList == nil || self.dataList.count == 0) {
return;
}
[self.dataList enumerateObjectsUsingBlock:^(QNUploadData *data, NSUInteger idx, BOOL * _Nonnull stop) {
[data clearUploadState];
}];
}
- (void)checkInfoStateAndUpdate {
[self.dataList enumerateObjectsUsingBlock:^(QNUploadData *data, NSUInteger idx, BOOL * _Nonnull stop) {
[data checkStateAndUpdate];
}];
}
- (long long)uploadSize {
if (self.dataList == nil || self.dataList.count == 0) {
return 0;
}
__block long long uploadSize = 0;
[self.dataList enumerateObjectsUsingBlock:^(QNUploadData *data, NSUInteger idx, BOOL * _Nonnull stop) {
uploadSize += [data uploadSize];
}];
return uploadSize;
}
- (BOOL)isAllUploaded {
if (!_isEOF) {
return false;
}
if (self.dataList == nil || self.dataList.count == 0) {
return true;
}
__block BOOL isAllUploaded = true;
[self.dataList enumerateObjectsUsingBlock:^(QNUploadData *data, NSUInteger idx, BOOL * _Nonnull stop) {
if (!data.isUploaded) {
isAllUploaded = false;
*stop = true;
}
}];
return isAllUploaded;
}
- (NSDictionary *)toDictionary {
NSMutableDictionary *dictionary = [[super toDictionary] mutableCopy];
if (dictionary == nil) {
dictionary = [NSMutableDictionary dictionary];
}
[dictionary setObject:kTypeValue forKey:kQNUploadInfoTypeKey];
[dictionary setObject:@(self.dataSize) forKey:@"dataSize"];
[dictionary setObject:self.expireAt ?: 0 forKey:@"expireAt"];
[dictionary setObject:self.uploadId ?: @"" forKey:@"uploadId"];
if (self.dataList != nil && self.dataList.count != 0) {
NSMutableArray *blockInfoList = [NSMutableArray array];
[self.dataList enumerateObjectsUsingBlock:^(QNUploadData *data, NSUInteger idx, BOOL * _Nonnull stop) {
[blockInfoList addObject:[data toDictionary]];
}];
[dictionary setObject:[blockInfoList copy] forKey:@"dataList"];
}
return [dictionary copy];
}
- (NSInteger)getPartIndexOfData:(QNUploadData *)data {
return data.index + 1;
}
- (QNUploadData *)nextUploadData:(NSError **)error {
// dataList data
QNUploadData *data = [self nextUploadDataFormDataList];
// dataList data
if (data == nil) {
if (self.isEOF) {
return nil;
} else if (self.readError) {
*error = self.readError;
return nil;
}
// block
long long dataOffset = 0;
if (self.dataList.count > 0) {
QNUploadData *lastData = self.dataList[self.dataList.count - 1];
dataOffset = lastData.offset + lastData.size;
}
data = [[QNUploadData alloc] initWithOffset:dataOffset dataSize:self.dataSize index:self.dataList.count];
}
QNUploadData*loadData = [self loadData:data error:error];
if (*error != nil) {
self.readError = *error;
return nil;
}
if (loadData == nil) {
// 没有加在到 data, 也即数据源读取结束
self.isEOF = true;
// 有多余的 data 则移除,移除中包含 data
if (self.dataList.count > data.index) {
self.dataList = [[self.dataList subarrayWithRange:NSMakeRange(0, data.index)] mutableCopy];
}
} else {
// 加在到 data
if (loadData.index == self.dataList.count) {
// 新块data index 等于 dataList size 则为新创建 block需要加入 dataList
[self.dataList addObject:loadData];
} else if (loadData != data) {
// 更换块:重新加在了 data 更换信息
[self.dataList replaceObjectAtIndex:loadData.index withObject:loadData];
}
// 数据源读取结束,块读取大小小于预期,读取结束
if (loadData.size < data.size) {
self.isEOF = true;
// 有多余的 data 则移除,移除中不包含 data
if (self.dataList.count > data.index + 1) {
self.dataList = [[self.dataList subarrayWithRange:NSMakeRange(0, data.index + 1)] mutableCopy];
}
}
}
return loadData;
}
- (QNUploadData *)nextUploadDataFormDataList {
if (self.dataList == nil || self.dataList.count == 0) {
return nil;
}
__block QNUploadData *data = nil;
[self.dataList enumerateObjectsUsingBlock:^(QNUploadData *dataP, NSUInteger idx, BOOL * _Nonnull stop) {
if ([dataP needToUpload]) {
data = dataP;
*stop = true;
}
}];
return data;
}
//
// 1.
// 2.
// 2.1 EOF null
// 2.2
// 2.2.1
// 2.2.2
- (QNUploadData *)loadData:(QNUploadData *)data error:(NSError **)error {
if (data == nil) {
return nil;
}
//
if (data.data != nil) {
return data;
}
// block
// data dataBytes
NSData *dataBytes = [self readData:(NSInteger)data.size dataOffset:data.offset error:error];
if (*error != nil) {
return nil;
}
// 没有数据不需要上传
if (dataBytes == nil || dataBytes.length == 0) {
return nil;
}
NSString *md5 = [dataBytes qn_md5];
// 判断当前 block 的数据是否和实际数据吻合,不吻合则之前 block 被抛弃,重新创建 block
if (dataBytes.length != data.size || data.md5 == nil || ![data.md5 isEqualToString:md5]) {
data = [[QNUploadData alloc] initWithOffset:data.offset dataSize:dataBytes.length index:data.index];
data.md5 = md5;
}
if (data.etag == nil || data.etag.length == 0) {
data.data = dataBytes;
data.state = QNUploadStateWaitToUpload;
} else {
data.state = QNUploadStateComplete;
}
return data;
}
- (NSArray <NSDictionary <NSString *, NSObject *> *> *)getPartInfoArray {
if (self.uploadId == nil || self.uploadId.length == 0) {
return nil;
}
NSMutableArray *infoArray = [NSMutableArray array];
[self.dataList enumerateObjectsUsingBlock:^(QNUploadData *data, NSUInteger idx, BOOL * _Nonnull stop) {
if (data.state == QNUploadStateComplete && data.etag != nil) {
[infoArray addObject:@{@"etag" : data.etag,
@"partNumber" : @([self getPartIndexOfData:data])}];
}
}];
return [infoArray copy];
}
@end

182
Pods/Qiniu/QiniuSDK/Storage/QNUploadManager.h generated Executable file
View File

@@ -0,0 +1,182 @@
//
// QNUploader.h
// QiniuSDK
//
// Created by bailong on 14-9-28.
// Copyright (c) 2014年 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "QNRecorderDelegate.h"
@class QNResponseInfo;
@class QNUploadOption;
@class QNConfiguration;
@class PHAsset;
@class PHAssetResource;
#if !TARGET_OS_MACCATALYST
@class ALAsset;
#endif
/**
* 上传完成后的回调函数
*
* @param info 上下文信息,包括状态码,错误值
* @param key 上传时指定的key原样返回
* @param resp 上传成功会返回文件信息失败为nil; 可以通过此值是否为nil 判断上传结果
*/
typedef void (^QNUpCompletionHandler)(QNResponseInfo *info, NSString *key, NSDictionary *resp);
/**
管理上传的类,可以生成一次,持续使用,不必反复创建。
*/
@interface QNUploadManager : NSObject
/**
* 默认构造方法,没有持久化记录
*
* @return 上传管理类实例
*/
- (instancetype)init;
/**
* 使用一个持久化的记录接口进行记录的构造方法
*
* @param recorder 持久化记录接口实现
*
* @return 上传管理类实例
*/
- (instancetype)initWithRecorder:(id<QNRecorderDelegate>)recorder;
/**
* 使用持久化记录接口以及持久化key生成函数的构造方法默认情况下使用上传存储的key, 如果key为nil或者有特殊字符比如/,建议使用自己的生成函数
*
* @param recorder 持久化记录接口实现
* @param recorderKeyGenerator 持久化记录key生成函数
*
* @return 上传管理类实例
*/
- (instancetype)initWithRecorder:(id<QNRecorderDelegate>)recorder
recorderKeyGenerator:(QNRecorderKeyGenerator)recorderKeyGenerator;
/**
* 使用配置信息生成上传实例
*
* @param config 配置信息
*
* @return 上传管理类实例
*/
- (instancetype)initWithConfiguration:(QNConfiguration *)config;
/**
* 方便使用的单例方法
*
* @param config 配置信息
*
* @return 上传管理类实例
*/
+ (instancetype)sharedInstanceWithConfiguration:(QNConfiguration *)config;
/**
* 直接上传数据
*
* @param data 待上传的数据
* @param key 上传到云存储的key为nil时表示是由七牛生成
* @param token 上传需要的token, 由服务器生成
* @param completionHandler 上传完成后的回调函数
* @param option 上传时传入的可选参数
*/
- (void)putData:(NSData *)data
key:(NSString *)key
token:(NSString *)token
complete:(QNUpCompletionHandler)completionHandler
option:(QNUploadOption *)option;
/**
* 上传数据流
*
* @param inputStream 数据流
* @param sourceId 数据 id; 用于断点续传的标识
* @param size 流大小,主要用来计算上传进度,不能预知大小传 -1
* @param fileName 文件名
* @param key 上传到云存储的key为nil时表示是由七牛生成
* @param token 上传需要的token, 由服务器生成
* @param completionHandler 上传完成后的回调函数
* @param option 上传时传入的可选参数
*/
- (void)putInputStream:(NSInputStream *)inputStream
sourceId:(NSString *)sourceId
size:(long long)size
fileName:(NSString *)fileName
key:(NSString *)key
token:(NSString *)token
complete:(QNUpCompletionHandler)completionHandler
option:(QNUploadOption *)option;
/**
* 上传文件
*
* @param filePath 文件路径
* @param key 上传到云存储的key为nil时表示是由七牛生成
* @param token 上传需要的token, 由服务器生成
* @param completionHandler 上传完成后的回调函数
* @param option 上传时传入的可选参数
*/
- (void)putFile:(NSString *)filePath
key:(NSString *)key
token:(NSString *)token
complete:(QNUpCompletionHandler)completionHandler
option:(QNUploadOption *)option;
#if !TARGET_OS_MACCATALYST
/**
* 上传ALAsset文件
*
* @param asset ALAsset文件
* @param key 上传到云存储的key为nil时表示是由七牛生成
* @param token 上传需要的token, 由服务器生成
* @param completionHandler 上传完成后的回调函数
* @param option 上传时传入的可选参数
*/
- (void)putALAsset:(ALAsset *)asset
key:(NSString *)key
token:(NSString *)token
complete:(QNUpCompletionHandler)completionHandler
option:(QNUploadOption *)option API_DEPRECATED("use putPHAsset instead", ios(4.0, 9.0)) API_UNAVAILABLE(macos, tvos);
#endif
/**
* 上传PHAsset文件(IOS8 andLater)
*
* @param asset PHAsset文件
* @param key 上传到云存储的key为nil时表示是由七牛生成
* @param token 上传需要的token, 由服务器生成
* @param completionHandler 上传完成后的回调函数
* @param option 上传时传入的可选参数
*/
- (void)putPHAsset:(PHAsset *)asset
key:(NSString *)key
token:(NSString *)token
complete:(QNUpCompletionHandler)completionHandler
option:(QNUploadOption *)option API_AVAILABLE(ios(9.1)) API_UNAVAILABLE(macos, tvos);
/**
* 上传PHAssetResource文件(IOS9.1 andLater)
*
* @param assetResource PHAssetResource文件
* @param key 上传到云存储的key为nil时表示是由七牛生成
* @param token 上传需要的token, 由服务器生成
* @param completionHandler 上传完成后的回调函数
* @param option 上传时传入的可选参数
*/
- (void)putPHAssetResource:(PHAssetResource *)assetResource
key:(NSString *)key
token:(NSString *)token
complete:(QNUpCompletionHandler)completionHandler
option:(QNUploadOption *)option API_AVAILABLE(ios(9)) API_UNAVAILABLE(macos, tvos);
@end

View File

@@ -0,0 +1,505 @@
//
// QNUploader.h
// QiniuSDK
//
// Created by bailong on 14-9-28.
// Copyright (c) 2014 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
#if __IPHONE_OS_VERSION_MIN_REQUIRED
#import <MobileCoreServices/MobileCoreServices.h>
#import <UIKit/UIKit.h>
#if !TARGET_OS_MACCATALYST
#import <AssetsLibrary/AssetsLibrary.h>
#import "QNALAssetFile.h"
#endif
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
#import "QNPHAssetFile.h"
#import <Photos/Photos.h>
#endif
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 90000
#import "QNPHAssetResource.h"
#endif
#else
#import <CoreServices/CoreServices.h>
#endif
#import "QNUploadManager.h"
#import "QNAsyncRun.h"
#import "QNConfiguration.h"
#import "QNCrc32.h"
#import "QNFile.h"
#import "QNUtils.h"
#import "QNResponseInfo.h"
#import "QNFormUpload.h"
#import "QNPartsUpload.h"
#import "QNConcurrentResumeUpload.h"
#import "QNUpToken.h"
#import "QNUploadOption.h"
#import "QNReportItem.h"
#import "QNServerConfigMonitor.h"
#import "QNDnsPrefetch.h"
#import "QNZone.h"
#import "QNUploadSourceFile.h"
#import "QNUploadSourceStream.h"
@interface QNUploadManager ()
@property (nonatomic) QNConfiguration *config;
@end
@implementation QNUploadManager
- (instancetype)init {
return [self initWithConfiguration:nil];
}
- (instancetype)initWithRecorder:(id<QNRecorderDelegate>)recorder {
return [self initWithRecorder:recorder recorderKeyGenerator:nil];
}
- (instancetype)initWithRecorder:(id<QNRecorderDelegate>)recorder
recorderKeyGenerator:(QNRecorderKeyGenerator)recorderKeyGenerator {
QNConfiguration *config = [QNConfiguration build:^(QNConfigurationBuilder *builder) {
builder.recorder = recorder;
builder.recorderKeyGen = recorderKeyGenerator;
}];
return [self initWithConfiguration:config];
}
- (instancetype)initWithConfiguration:(QNConfiguration *)config {
if (self = [super init]) {
if (config == nil) {
config = [QNConfiguration build:^(QNConfigurationBuilder *builder){
}];
}
_config = config;
[[QNTransactionManager shared] addDnsLocalLoadTransaction];
[QNServerConfigMonitor startMonitor];
}
return self;
}
+ (instancetype)sharedInstanceWithConfiguration:(QNConfiguration *)config {
static QNUploadManager *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] initWithConfiguration:config];
});
return sharedInstance;
}
- (void)putData:(NSData *)data
key:(NSString *)key
token:(NSString *)token
complete:(QNUpCompletionHandler)completionHandler
option:(QNUploadOption *)option {
[self putData:data fileName:nil key:key token:token complete:completionHandler option:option];
}
- (void)putData:(NSData *)data
fileName:(NSString *)fileName
key:(NSString *)key
token:(NSString *)token
complete:(QNUpCompletionHandler)completionHandler
option:(QNUploadOption *)option {
if ([QNUploadManager checkAndNotifyError:key token:token input:data complete:completionHandler]) {
return;
}
QNUpToken *t = [QNUpToken parse:token];
if (t == nil || ![t isValid]) {
QNResponseInfo *info = [QNResponseInfo responseInfoWithInvalidToken:@"invalid token"];
[QNUploadManager complete:token
key:key
source:data
responseInfo:info
response:nil
taskMetrics:nil
complete:completionHandler];
return;
}
QNServerConfigMonitor.token = token;
[[QNTransactionManager shared] addDnsCheckAndPrefetchTransaction:self.config zone:self.config.zone token:t];
QNUpTaskCompletionHandler complete = ^(QNResponseInfo *info, NSString *key, QNUploadTaskMetrics *metrics, NSDictionary *resp) {
[QNUploadManager complete:token
key:key
source:data
responseInfo:info
response:resp
taskMetrics:metrics
complete:completionHandler];
};
QNFormUpload *up = [[QNFormUpload alloc] initWithData:data
key:key
fileName:fileName
token:t
option:option
configuration:self.config
completionHandler:complete];
QNAsyncRun(^{
[up run];
});
}
- (void)putInputStream:(NSInputStream *)inputStream
sourceId:(NSString *)sourceId
size:(long long)size
fileName:(NSString *)fileName
key:(NSString *)key
token:(NSString *)token
complete:(QNUpCompletionHandler)completionHandler
option:(QNUploadOption *)option {
if ([QNUploadManager checkAndNotifyError:key token:token input:inputStream complete:completionHandler]) {
return;
}
@autoreleasepool {
QNUploadSourceStream *source = [QNUploadSourceStream stream:inputStream sourceId:sourceId size:size fileName:fileName];
[self putInternal:source key:key token:token complete:completionHandler option:option];
}
}
- (void)putFile:(NSString *)filePath
key:(NSString *)key
token:(NSString *)token
complete:(QNUpCompletionHandler)completionHandler
option:(QNUploadOption *)option {
if ([QNUploadManager checkAndNotifyError:key token:token input:filePath complete:completionHandler]) {
return;
}
@autoreleasepool {
NSError *error = nil;
__block QNFile *file = [[QNFile alloc] init:filePath error:&error];
if (error) {
QNResponseInfo *info = [QNResponseInfo responseInfoWithFileError:error];
[QNUploadManager complete:token
key:key
source:nil
responseInfo:info
response:nil
taskMetrics:nil
complete:completionHandler];
return;
}
[self putFileInternal:file key:key token:token complete:completionHandler option:option];
}
}
#if !TARGET_OS_MACCATALYST
- (void)putALAsset:(ALAsset *)asset
key:(NSString *)key
token:(NSString *)token
complete:(QNUpCompletionHandler)completionHandler
option:(QNUploadOption *)option {
#if __IPHONE_OS_VERSION_MIN_REQUIRED
if ([QNUploadManager checkAndNotifyError:key token:token input:asset complete:completionHandler]) {
return;
}
@autoreleasepool {
NSError *error = nil;
__block QNALAssetFile *file = [[QNALAssetFile alloc] init:asset error:&error];
if (error) {
QNResponseInfo *info = [QNResponseInfo responseInfoWithFileError:error];
[QNUploadManager complete:token
key:key
source:nil
responseInfo:info
response:nil
taskMetrics:nil
complete:completionHandler];
return;
}
[self putFileInternal:file key:key token:token complete:completionHandler option:option];
}
#endif
}
#endif
- (void)putPHAsset:(PHAsset *)asset
key:(NSString *)key
token:(NSString *)token
complete:(QNUpCompletionHandler)completionHandler
option:(QNUploadOption *)option {
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 90100)
if ([QNUploadManager checkAndNotifyError:key token:token input:asset complete:completionHandler]) {
return;
}
@autoreleasepool {
NSError *error = nil;
__block QNPHAssetFile *file = [[QNPHAssetFile alloc] init:asset error:&error];
if (error) {
QNResponseInfo *info = [QNResponseInfo responseInfoWithFileError:error];
[QNUploadManager complete:token
key:key
source:nil
responseInfo:info
response:nil
taskMetrics:nil
complete:completionHandler];
return;
}
[self putFileInternal:file key:key token:token complete:completionHandler option:option];
}
#endif
}
- (void)putPHAssetResource:(PHAssetResource *)assetResource
key:(NSString *)key
token:(NSString *)token
complete:(QNUpCompletionHandler)completionHandler
option:(QNUploadOption *)option {
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 90000)
if ([QNUploadManager checkAndNotifyError:key token:token input:assetResource complete:completionHandler]) {
return;
}
@autoreleasepool {
NSError *error = nil;
__block QNPHAssetResource *file = [[QNPHAssetResource alloc] init:assetResource error:&error];
if (error) {
QNResponseInfo *info = [QNResponseInfo responseInfoWithFileError:error];
[QNUploadManager complete:token
key:key
source:nil
responseInfo:info
response:nil
taskMetrics:nil
complete:completionHandler];
return;
}
[self putFileInternal:file key:key token:token complete:completionHandler option:option];
}
#endif
}
- (void)putFileInternal:(id<QNFileDelegate>)file
key:(NSString *)key
token:(NSString *)token
complete:(QNUpCompletionHandler)completionHandler
option:(QNUploadOption *)option {
[self putInternal:[QNUploadSourceFile file:file]
key:key token:token
complete:completionHandler
option:option];
}
- (void)putInternal:(id<QNUploadSource>)source
key:(NSString *)key
token:(NSString *)token
complete:(QNUpCompletionHandler)completionHandler
option:(QNUploadOption *)option {
@autoreleasepool {
QNUpToken *t = [QNUpToken parse:token];
if (t == nil || ![t isValid]) {
QNResponseInfo *info = [QNResponseInfo responseInfoWithInvalidToken:@"invalid token"];
[QNUploadManager complete:token
key:key
source:source
responseInfo:info
response:nil
taskMetrics:nil
complete:completionHandler];
return;
}
QNUpTaskCompletionHandler complete = ^(QNResponseInfo *info, NSString *key, QNUploadTaskMetrics *metrics, NSDictionary *resp) {
[QNUploadManager complete:token
key:key
source:source
responseInfo:info
response:resp
taskMetrics:metrics
complete:completionHandler];
};
QNServerConfigMonitor.token = token;
[[QNTransactionManager shared] addDnsCheckAndPrefetchTransaction:self.config zone:self.config.zone token:t];
long long sourceSize = [source getSize];
if (sourceSize > 0 && sourceSize <= self.config.putThreshold) {
NSError *error;
NSData *data = [source readData:(NSInteger)sourceSize dataOffset:0 error:&error];
[source close];
if (error) {
QNResponseInfo *info = [QNResponseInfo responseInfoWithFileError:error];
[QNUploadManager complete:token
key:key
source:source
responseInfo:info
response:nil
taskMetrics:nil
complete:completionHandler];
return;
}
[self putData:data
fileName:[source getFileName]
key:key
token:token
complete:completionHandler
option:option];
return;
}
NSString *recorderKey = key;
if (self.config.recorder != nil && self.config.recorderKeyGen != nil) {
recorderKey = self.config.recorderKeyGen(key, [source getId]);
}
if (self.config.useConcurrentResumeUpload) {
QNConcurrentResumeUpload *up = [[QNConcurrentResumeUpload alloc]
initWithSource:source
key:key
token:t
option:option
configuration:self.config
recorder:self.config.recorder
recorderKey:recorderKey
completionHandler:complete];
QNAsyncRun(^{
[up run];
});
} else {
QNPartsUpload *up = [[QNPartsUpload alloc]
initWithSource:source
key:key
token:t
option:option
configuration:self.config
recorder:self.config.recorder
recorderKey:recorderKey
completionHandler:complete];
QNAsyncRun(^{
[up run];
});
}
}
}
+ (BOOL)checkAndNotifyError:(NSString *)key
token:(NSString *)token
input:(NSObject *)input
complete:(QNUpCompletionHandler)completionHandler {
if (completionHandler == nil) {
@throw [NSException exceptionWithName:NSInvalidArgumentException
reason:@"no completionHandler"
userInfo:nil];
return YES;
}
QNResponseInfo *info = nil;
if (input == nil) {
info = [QNResponseInfo responseInfoOfZeroData:@"no input data"];
} else if ([input isKindOfClass:[NSData class]] && [(NSData *)input length] == 0) {
info = [QNResponseInfo responseInfoOfZeroData:@"no input data"];
} else if (token == nil || [token isEqual:[NSNull null]] || [token isEqualToString:@""]) {
info = [QNResponseInfo responseInfoWithInvalidToken:@"no token"];
}
if (info != nil) {
[QNUploadManager complete:token
key:key
source:nil
responseInfo:info
response:nil
taskMetrics:nil
complete:completionHandler];
return YES;
} else {
return NO;
}
}
+ (void)complete:(NSString *)token
key:(NSString *)key
source:(NSObject *)source
responseInfo:(QNResponseInfo *)responseInfo
response:(NSDictionary *)response
taskMetrics:(QNUploadTaskMetrics *)taskMetrics
complete:(QNUpCompletionHandler)completionHandler {
[QNUploadManager reportQuality:key source:source responseInfo:responseInfo taskMetrics:taskMetrics token:token];
QNAsyncRunInMain(^{
if (completionHandler) {
completionHandler(responseInfo, key, response);
}
});
}
//MARK:-- quality
+ (void)reportQuality:(NSString *)key
source:(NSObject *)source
responseInfo:(QNResponseInfo *)responseInfo
taskMetrics:(QNUploadTaskMetrics *)taskMetrics
token:(NSString *)token{
QNUpToken *upToken = [QNUpToken parse:token];
QNUploadTaskMetrics *taskMetricsP = taskMetrics ?: [QNUploadTaskMetrics emptyMetrics];
QNReportItem *item = [QNReportItem item];
[item setReportValue:QNReportLogTypeQuality forKey:QNReportQualityKeyLogType];
[item setReportValue:taskMetricsP.upType forKey:QNReportQualityKeyUpType];
[item setReportValue:@([[NSDate date] timeIntervalSince1970]) forKey:QNReportQualityKeyUpTime];
[item setReportValue:responseInfo.qualityResult forKey:QNReportQualityKeyResult];
[item setReportValue:upToken.bucket forKey:QNReportQualityKeyTargetBucket];
[item setReportValue:key forKey:QNReportQualityKeyTargetKey];
[item setReportValue:taskMetricsP.totalElapsedTime forKey:QNReportQualityKeyTotalElapsedTime];
[item setReportValue:taskMetricsP.ucQueryMetrics.totalElapsedTime forKey:QNReportQualityKeyUcQueryElapsedTime];
[item setReportValue:taskMetricsP.requestCount forKey:QNReportQualityKeyRequestsCount];
[item setReportValue:taskMetricsP.regionCount forKey:QNReportQualityKeyRegionsCount];
[item setReportValue:taskMetricsP.bytesSend forKey:QNReportQualityKeyBytesSent];
[item setReportValue:[QNUtils systemName] forKey:QNReportQualityKeyOsName];
[item setReportValue:[QNUtils systemVersion] forKey:QNReportQualityKeyOsVersion];
[item setReportValue:[QNUtils sdkLanguage] forKey:QNReportQualityKeySDKName];
[item setReportValue:[QNUtils sdkVersion] forKey:QNReportQualityKeySDKVersion];
[item setReportValue:responseInfo.requestReportErrorType forKey:QNReportQualityKeyErrorType];
NSString *errorDesc = responseInfo.requestReportErrorType ? responseInfo.message : nil;
[item setReportValue:errorDesc forKey:QNReportQualityKeyErrorDescription];
[item setReportValue:taskMetricsP.lastMetrics.lastMetrics.hijacked forKey:QNReportBlockKeyHijacking];
long long fileSize = -1;
if ([source conformsToProtocol:@protocol(QNUploadSource)]) {
fileSize = [(id <QNUploadSource>)source getSize];
} else if ([source isKindOfClass:[NSData class]]) {
fileSize = [(NSData *)source length];
}
[item setReportValue:@(fileSize) forKey:QNReportQualityKeyFileSize];
if (responseInfo.isOK && fileSize > 0 && taskMetrics.totalElapsedTime) {
NSNumber *speed = [QNUtils calculateSpeed:fileSize totalTime:taskMetrics.totalElapsedTime.longLongValue];
[item setReportValue:speed forKey:QNReportQualityKeyPerceptiveSpeed];
}
[kQNReporter reportItem:item token:token];
}
@end

138
Pods/Qiniu/QiniuSDK/Storage/QNUploadOption.h generated Executable file
View File

@@ -0,0 +1,138 @@
//
// QNUploadOption.h
// QiniuSDK
//
// Created by bailong on 14/10/4.
// Copyright (c) 2014年 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
/**
* 上传进度回调函数
*
* @param key 上传时指定的存储key
* @param percent 进度百分比
*/
typedef void (^QNUpProgressHandler)(NSString *key, float percent);
/**
* 上传进度回调函数
*
* @param key 上传文件的保存文件名
* @param uploadBytes 已上传大小
* @param totalBytes 总大小;无法获取大小时为 -1
*/
typedef void (^QNUpByteProgressHandler)(NSString *key, long long uploadBytes, long long totalBytes);
/**
* 上传中途取消函数
*
* @return 如果想取消返回True, 否则返回No
*/
typedef BOOL (^QNUpCancellationSignal)(void);
/**
* 可选参数集合此类初始化后sdk上传使用时 不会对此进行改变;如果参数没有变化以及没有使用依赖,可以重复使用。
*/
@interface QNUploadOption : NSObject
/**
* 用于服务器上传回调通知的自定义参数参数的key必须以x: 开头 eg: x:foo
*/
@property (copy, nonatomic, readonly) NSDictionary *params;
/**
* 用于设置meta数据参数的key必须以x-qn-meta- 开头 eg: x-qn-meta-key
*/
@property (copy, nonatomic, readonly) NSDictionary *metaDataParam;
/**
* 指定文件的mime类型
*/
@property (copy, nonatomic, readonly) NSString *mimeType;
/**
* 是否进行crc校验
*/
@property (readonly) BOOL checkCrc;
/**
* 进度回调函数
*/
@property (copy, readonly) QNUpProgressHandler progressHandler;
/**
* 进度回调函数
* 注:
* 使用此接口progressHandler 会无效
*/
@property (copy, readonly) QNUpByteProgressHandler byteProgressHandler;
/**
* 中途取消函数
*/
@property (copy, readonly) QNUpCancellationSignal cancellationSignal;
/**
* 可选参数的初始化方法
*
* @param mimeType mime类型
* @param progress 进度函数
* @param params 自定义服务器回调参数 参数的key必须以x: 开头 eg: x:foo
* @param check 是否进行crc检查
* @param cancellation 中途取消函数
*
* @return 可选参数类实例
*/
- (instancetype)initWithMime:(NSString *)mimeType
progressHandler:(QNUpProgressHandler)progress
params:(NSDictionary *)params
checkCrc:(BOOL)check
cancellationSignal:(QNUpCancellationSignal)cancellation;
- (instancetype)initWithMime:(NSString *)mimeType
byteProgressHandler:(QNUpByteProgressHandler)progress
params:(NSDictionary *)params
checkCrc:(BOOL)check
cancellationSignal:(QNUpCancellationSignal)cancellation;
/**
* 可选参数的初始化方法
*
* @param mimeType mime类型
* @param progress 进度函数
* @param params 自定义服务器回调参数 参数的key必须以x: 开头 eg: x:foo
* @param metaDataParams 设置meta数据参数的key必须以x-qn-meta- 开头 eg: x-qn-meta-key
* @param check 是否进行crc检查
* @param cancellation 中途取消函数
*
* @return 可选参数类实例
*/
- (instancetype)initWithMime:(NSString *)mimeType
progressHandler:(QNUpProgressHandler)progress
params:(NSDictionary *)params
metaDataParams:(NSDictionary *)metaDataParams
checkCrc:(BOOL)check
cancellationSignal:(QNUpCancellationSignal)cancellation;
- (instancetype)initWithMime:(NSString *)mimeType
byteProgressHandler:(QNUpByteProgressHandler)progress
params:(NSDictionary *)params
metaDataParams:(NSDictionary *)metaDataParams
checkCrc:(BOOL)check
cancellationSignal:(QNUpCancellationSignal)cancellation;
- (instancetype)initWithProgressHandler:(QNUpProgressHandler)progress;
- (instancetype)initWithByteProgressHandler:(QNUpByteProgressHandler)progress;
/**
* 内部使用,默认的参数实例
*
* @return 可选参数类实例
*/
+ (instancetype)defaultOptions;
@end

134
Pods/Qiniu/QiniuSDK/Storage/QNUploadOption.m generated Executable file
View File

@@ -0,0 +1,134 @@
//
// QNUploadOption.m
// QiniuSDK
//
// Created by bailong on 14/10/4.
// Copyright (c) 2014 Qiniu. All rights reserved.
//
#import "QNUploadOption.h"
#import "QNUploadManager.h"
static NSString *mime(NSString *mimeType) {
if (mimeType == nil || [mimeType isEqualToString:@""]) {
return @"application/octet-stream";
}
return mimeType;
}
@implementation QNUploadOption
+ (NSDictionary *)filterParam:(NSDictionary *)params {
NSMutableDictionary *ret = [NSMutableDictionary dictionary];
if (params == nil) {
return ret;
}
[params enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) {
if ([key hasPrefix:@"x:"] && ![obj isEqualToString:@""]) {
ret[key] = obj;
} else {
NSLog(@"参数%@设置无效,请检查参数格式", key);
}
}];
return ret;
}
+ (NSDictionary *)filterMetaDataParam:(NSDictionary *)params {
NSMutableDictionary *ret = [NSMutableDictionary dictionary];
if (params == nil) {
return ret;
}
[params enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) {
if ([key hasPrefix:@"x-qn-meta-"] && ![obj isEqualToString:@""]) {
ret[key] = obj;
} else {
NSLog(@"参数%@设置无效,请检查参数格式", key);
}
}];
return ret;
}
- (instancetype)initWithProgressHandler:(QNUpProgressHandler)progress {
return [self initWithMime:nil progressHandler:progress params:nil checkCrc:NO cancellationSignal:nil];
}
- (instancetype)initWithByteProgressHandler:(QNUpByteProgressHandler)progress {
return [self initWithMime:nil byteProgressHandler:progress params:nil checkCrc:NO cancellationSignal:nil];
}
- (instancetype)initWithMime:(NSString *)mimeType
progressHandler:(QNUpProgressHandler)progress
params:(NSDictionary *)params
checkCrc:(BOOL)check
cancellationSignal:(QNUpCancellationSignal)cancel {
return [self initWithMime:mimeType
progressHandler:progress
params:params
metaDataParams:nil
checkCrc:check
cancellationSignal:cancel];
}
- (instancetype)initWithMime:(NSString *)mimeType
byteProgressHandler:(QNUpByteProgressHandler)progress
params:(NSDictionary *)params
checkCrc:(BOOL)check
cancellationSignal:(QNUpCancellationSignal)cancellation {
return [self initWithMime:mimeType
byteProgressHandler:progress
params:params
metaDataParams:nil
checkCrc:check
cancellationSignal:cancellation];
}
- (instancetype)initWithMime:(NSString *)mimeType
progressHandler:(QNUpProgressHandler)progress
params:(NSDictionary *)params
metaDataParams:(NSDictionary *)metaDataParams
checkCrc:(BOOL)check
cancellationSignal:(QNUpCancellationSignal)cancellation{
if (self = [super init]) {
_mimeType = mime(mimeType);
_progressHandler = progress != nil ? progress : ^(NSString *key, float percent) {};
_params = [QNUploadOption filterParam:params];
_metaDataParam = [QNUploadOption filterMetaDataParam:metaDataParams];
_checkCrc = check;
_cancellationSignal = cancellation != nil ? cancellation : ^BOOL() {
return NO;
};
}
return self;
}
- (instancetype)initWithMime:(NSString *)mimeType
byteProgressHandler:(QNUpByteProgressHandler)progress
params:(NSDictionary *)params
metaDataParams:(NSDictionary *)metaDataParams
checkCrc:(BOOL)check
cancellationSignal:(QNUpCancellationSignal)cancellation {
if (self = [super init]) {
_mimeType = mime(mimeType);
_byteProgressHandler = progress != nil ? progress : ^(NSString *key, long long uploadBytes, long long totalBytes) {};
_params = [QNUploadOption filterParam:params];
_metaDataParam = [QNUploadOption filterMetaDataParam:metaDataParams];
_checkCrc = check;
_cancellationSignal = cancellation != nil ? cancellation : ^BOOL() {
return NO;
};
}
return self;
}
+ (instancetype)defaultOptions {
return [[QNUploadOption alloc] initWithMime:nil byteProgressHandler:nil params:nil checkCrc:NO cancellationSignal:nil];
}
@end

View File

@@ -0,0 +1,83 @@
//
// QNUploadSource.h
// QiniuSDK
//
// Created by yangsen on 2021/5/10.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@protocol QNUploadSource <NSObject>
/**
* 获取资源唯一标识
* 作为断点续传时判断是否为同一资源的依据之一;
* 如果两个资源的 record key 和 资源唯一标识均相同则认为资源为同一资源,断点续传才会生效
* 注:
* 同一资源的数据必须完全相同,否则上传可能会出现异常
*
* @return 资源修改时间
*/
- (NSString *)getId;
/**
* 是否可以重新加载文件信息,也即是否可以重新读取信息
* @return return
*/
- (BOOL)couldReloadSource;
/**
* 重新加载文件信息,以便于重新读取
*
* @return 重新加载是否成功
*/
- (BOOL)reloadSource;
/**
* 获取资源文件名
* @return 资源文件名
*/
- (NSString *)getFileName;
/**
* 获取资源大小
* 无法获取大小时返回 -1
* 作用:
* 1. 验证资源是否为同一资源
* 2. 计算上传进度
*
* @return 资源大小
*/
- (long long)getSize;
/**
* 读取数据
* 1. 返回 NSData 可能为空,但不会为 null
* 2. 当 NSData 大小和 dataSize 不同时,则源数据已经读取结束
* 3. 读取异常时抛出 error
* 4. 仅支持串行调用,且 dataOffset 依次递增
*
* @param dataSize 数据大小
* @param dataOffset 数据偏移量
* @param error 异常时的错误信息
* @return 数据
*/
- (NSData *)readData:(NSInteger)dataSize dataOffset:(long long)dataOffset error:(NSError **)error;
/**
* 关闭流
*/
- (void)close;
@optional
// 上传源类型
- (NSString *)sourceType;
@end
#define kQNUnknownSourceSize (-1)
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,20 @@
//
// QNUploadSourceFile.h
// QiniuSDK
//
// Created by yangsen on 2021/5/10.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "QNFileDelegate.h"
#import "QNUploadSource.h"
NS_ASSUME_NONNULL_BEGIN
@interface QNUploadSourceFile : NSObject <QNUploadSource>
+ (instancetype)file:(id <QNFileDelegate>)file;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,60 @@
//
// QNUploadSourceFile.m
// QiniuSDK
//
// Created by yangsen on 2021/5/10.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "QNUploadSourceFile.h"
@interface QNUploadSourceFile()
@property(nonatomic, strong)id <QNFileDelegate> file;
@end
@implementation QNUploadSourceFile
+ (instancetype)file:(id <QNFileDelegate>)file {
QNUploadSourceFile *sourceFile = [[QNUploadSourceFile alloc] init];
sourceFile.file = file;
return sourceFile;
}
- (BOOL)couldReloadSource {
return self.file != nil;
}
- (BOOL)reloadSource {
return true;
}
- (nonnull NSString *)getId {
return [NSString stringWithFormat:@"%@_%lld", [self getFileName], [self.file modifyTime]];
}
- (nonnull NSString *)getFileName {
return [[self.file path] lastPathComponent];
}
- (long long)getSize {
return [self.file size];
}
- (NSData *)readData:(NSInteger)dataSize dataOffset:(long long)dataOffset error:(NSError *__autoreleasing _Nullable *)error {
return [self.file read:dataOffset size:dataSize error:error];
}
- (void)close {
[self.file close];
}
- (NSString *)sourceType {
if ([self.file respondsToSelector:@selector(fileType)]) {
return self.file.fileType;
} else {
return @"SourceFile";
}
}
@end

View File

@@ -0,0 +1,22 @@
//
// QNUploadSourceStream.h
// QiniuSDK
//
// Created by yangsen on 2021/5/10.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "QNUploadSource.h"
NS_ASSUME_NONNULL_BEGIN
@interface QNUploadSourceStream : NSObject <QNUploadSource>
+ (instancetype)stream:(NSInputStream * _Nonnull)stream
sourceId:(NSString * _Nullable)sourceId
size:(long long)size
fileName:(NSString * _Nullable)fileName;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,234 @@
//
// QNUploadSourceStream.m
// QiniuSDK
//
// Created by yangsen on 2021/5/10.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "QNErrorCode.h"
#import "QNUploadSourceStream.h"
@interface QNUploadSourceStream()
@property(nonatomic, assign)BOOL hasSize;
@property(nonatomic, assign)long long size;
@property(nonatomic, assign)long long readOffset;
@property(nonatomic, copy)NSString *sourceId;
@property(nonatomic, copy)NSString *fileName;
@property(nonatomic, strong)NSInputStream *stream;
@end
@implementation QNUploadSourceStream
+ (instancetype)stream:(NSInputStream * _Nonnull)stream
sourceId:(NSString * _Nullable)sourceId
size:(long long)size
fileName:(NSString * _Nullable)fileName {
QNUploadSourceStream *sourceStream = [[QNUploadSourceStream alloc] init];
sourceStream.stream = stream;
sourceStream.sourceId = sourceId;
sourceStream.fileName = fileName;
sourceStream.size = size;
sourceStream.hasSize = size > 0;
sourceStream.readOffset = 0;
return sourceStream;
}
- (NSString *)getId {
return self.sourceId;
}
- (BOOL)couldReloadSource {
return false;
}
- (BOOL)reloadSource {
return false;
}
- (NSString *)getFileName {
return self.fileName;
}
- (long long)getSize {
if (self.size > kQNUnknownSourceSize) {
return self.size;
} else {
return kQNUnknownSourceSize;
}
}
- (NSData *)readData:(NSInteger)dataSize dataOffset:(long long)dataOffset error:(NSError **)error {
if (self.stream == nil) {
*error = [NSError errorWithDomain:NSCocoaErrorDomain code:kQNFileError userInfo:@{NSLocalizedDescriptionKey : @"inputStream is empty"}];
return nil;
}
if (dataOffset < self.readOffset) {
*error = [NSError errorWithDomain:NSCocoaErrorDomain code:kQNFileError userInfo:@{NSLocalizedDescriptionKey : @"read data error: error data offset"}];
return nil;
}
//
[self openStreamIfNeeded];
if (dataOffset > self.readOffset) {
//
[self streamSkipSize:dataOffset - self.readOffset error:error];
if (*error != nil) {
return nil;
}
self.readOffset = dataOffset;
}
// 读取数据
BOOL isEOF = false;
NSInteger sliceSize = 1024;
NSInteger readSize = 0;
NSMutableData *data = [NSMutableData data];
while (readSize < dataSize) {
@autoreleasepool {
NSData *sliceData = [self readDataFromStream:sliceSize error:error];
if (*error != nil) {
break;
}
if (sliceData.length > 0) {
readSize += sliceData.length;
[data appendData:sliceData];
}
if (sliceData.length < sliceSize) {
isEOF = true;
break;
}
}
}
self.readOffset += readSize;
if (*error != nil) {
return nil;
}
if (isEOF) {
self.size = self.readOffset;
}
return data;
}
- (void)openStreamIfNeeded {
BOOL isOpening = true;
while (true) {
switch (self.stream.streamStatus) {
case NSStreamStatusNotOpen:
[self.stream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[self.stream open];
continue;
case NSStreamStatusOpening:
continue;
default:
isOpening = false;
break;
}
if (!isOpening) {
break;
}
}
}
- (void)streamSkipSize:(long long)size error:(NSError **)error {
BOOL isEOF = false;
NSInteger sliceSize = 1024;
NSInteger readSize = 0;
while (readSize < size) {
@autoreleasepool {
NSData *sliceData = [self readDataFromStream:sliceSize error:error];
if (*error != nil) {
break;
}
if (sliceData.length > 0) {
readSize += sliceData.length;
}
if (sliceData.length < sliceSize) {
isEOF = true;
break;
}
sliceData = nil;
}
}
}
// read 之前必须先 open stream
- (NSData *)readDataFromStream:(NSInteger)dataSize error:(NSError **)error {
BOOL isEOF = false;
NSInteger readSize = 0;
NSMutableData *data = [NSMutableData data];
uint8_t buffer[dataSize];
while (readSize < dataSize) {
//
switch (self.stream.streamStatus) {
case NSStreamStatusOpen:
break;
case NSStreamStatusReading:
continue;
case NSStreamStatusWriting:
*error = [NSError errorWithDomain:NSCocoaErrorDomain code:kQNFileError userInfo:@{NSLocalizedDescriptionKey : @"stream is writing"}];
break;
case NSStreamStatusAtEnd:
isEOF = true;
break;
case NSStreamStatusClosed:
*error = [NSError errorWithDomain:NSCocoaErrorDomain code:kQNFileError userInfo:@{NSLocalizedDescriptionKey : @"stream is closed"}];
break;
case NSStreamStatusError:
*error = self.stream.streamError;
break;
default:
break;
}
if (*error != nil) {
return nil;
}
if (isEOF) {
break;
}
// 检查是否有数据可读
if (!self.stream.hasBytesAvailable) {
[NSThread sleepForTimeInterval:0.05];
continue;
}
// 读取数据
NSInteger maxLength = dataSize;
NSInteger length = [self.stream read:buffer maxLength:maxLength];
*error = self.stream.streamError;
if (*error != nil) {
return nil;
}
if (length > 0) {
readSize += length;
[data appendBytes:(const void *)buffer length:length];
}
}
return [data copy];
}
- (void)close {
[self.stream close];
[self.stream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}
- (NSString *)sourceType {
return [NSString stringWithFormat:@"SourceStream:%@", _hasSize?@"HasSize":@"NoSize"];
}
@end

View File

@@ -0,0 +1,94 @@
//
// QNServerConfig.h
// QiniuSDK
//
// Created by yangsen on 2021/8/30.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "QNCache.h"
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface QNServerRegionConfig : NSObject
@property(nonatomic, assign, readonly)long clearId;
@property(nonatomic, assign, readonly)BOOL clearCache;
+ (instancetype)config:(NSDictionary *)info;
@end
@interface QNServerDnsServer : NSObject
@property(nonatomic, assign, readonly)BOOL isOverride;
@property(nonatomic, strong, readonly)NSArray <NSString *> *servers;
+ (instancetype)config:(NSDictionary *)info;
@end
@interface QNServerDohConfig : NSObject
@property(nonatomic, strong, readonly)NSNumber *enable;
@property(nonatomic, strong, readonly)QNServerDnsServer *ipv4Server;
@property(nonatomic, strong, readonly)QNServerDnsServer *ipv6Server;
+ (instancetype)config:(NSDictionary *)info;
@end
@interface QNServerUdpDnsConfig : NSObject
@property(nonatomic, strong, readonly)NSNumber *enable;
@property(nonatomic, strong, readonly)QNServerDnsServer *ipv4Server;
@property(nonatomic, strong, readonly)QNServerDnsServer *ipv6Server;
+ (instancetype)config:(NSDictionary *)info;
@end
@interface QNServerDnsConfig : NSObject
@property(nonatomic, strong, readonly)NSNumber *enable;
@property(nonatomic, assign, readonly)long clearId;
@property(nonatomic, assign, readonly)BOOL clearCache;
@property(nonatomic, strong, readonly)QNServerUdpDnsConfig *udpConfig;
@property(nonatomic, strong, readonly)QNServerDohConfig *dohConfig;
+ (instancetype)config:(NSDictionary *)info;
@end
@interface QNConnectCheckConfig : NSObject
@property(nonatomic, assign, readonly)BOOL isOverride;
@property(nonatomic, strong, readonly)NSNumber *enable;
@property(nonatomic, strong, readonly)NSNumber *timeoutMs;
@property(nonatomic, strong, readonly)NSArray <NSString *> *urls;
+ (instancetype)config:(NSDictionary *)info;
@end
@interface QNServerConfig : NSObject<QNCacheObject>
@property(nonatomic, assign, readonly)BOOL isValid;
@property(nonatomic, assign, readonly)long ttl;
@property(nonatomic, strong, readonly)QNServerRegionConfig *regionConfig;
@property(nonatomic, strong, readonly)QNServerDnsConfig *dnsConfig;
@property(nonatomic, strong, readonly)QNConnectCheckConfig *connectCheckConfig;
@property(nonatomic, strong, readonly)NSDictionary *info;
+ (instancetype)config:(NSDictionary *)info;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,160 @@
//
// QNServerConfig.m
// QiniuSDK
//
// Created by yangsen on 2021/8/30.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "QNServerConfig.h"
@interface QNServerRegionConfig()
@property(nonatomic, assign)long clearId;
@property(nonatomic, assign)BOOL clearCache;
@end
@implementation QNServerRegionConfig
+ (instancetype)config:(NSDictionary *)info {
QNServerRegionConfig *config = [[QNServerRegionConfig alloc] init];
config.clearId = [info[@"clear_id"] longValue];
config.clearCache = [info[@"clear_cache"] longValue];
return config;
}
@end
@interface QNServerDnsServer()
@property(nonatomic, assign)BOOL isOverride;
@property(nonatomic, strong)NSArray <NSString *> *servers;
@end
@implementation QNServerDnsServer
+ (instancetype)config:(NSDictionary *)info {
QNServerDnsServer *config = [[QNServerDnsServer alloc] init];
config.isOverride = [info[@"override_default"] boolValue];
if (info[@"ips"] && [info[@"ips"] isKindOfClass:[NSArray class]]) {
config.servers = info[@"ips"];
} else if ([info[@"urls"] isKindOfClass:[NSArray class]]){
config.servers = info[@"urls"];
}
return config;
}
@end
@interface QNServerDohConfig()
@property(nonatomic, strong)NSNumber *enable;
@property(nonatomic, strong)QNServerDnsServer *ipv4Server;
@property(nonatomic, strong)QNServerDnsServer *ipv6Server;
@end
@implementation QNServerDohConfig
+ (instancetype)config:(NSDictionary *)info {
QNServerDohConfig *config = [[QNServerDohConfig alloc] init];
config.enable = info[@"enabled"];
config.ipv4Server = [QNServerDnsServer config:info[@"ipv4"]];
config.ipv6Server = [QNServerDnsServer config:info[@"ipv6"]];
return config;
}
@end
@interface QNServerUdpDnsConfig()
@property(nonatomic, strong)NSNumber *enable;
@property(nonatomic, strong)QNServerDnsServer *ipv4Server;
@property(nonatomic, strong)QNServerDnsServer *ipv6Server;
@end
@implementation QNServerUdpDnsConfig
+ (instancetype)config:(NSDictionary *)info {
QNServerUdpDnsConfig *config = [[QNServerUdpDnsConfig alloc] init];
config.enable = info[@"enabled"];
config.ipv4Server = [QNServerDnsServer config:info[@"ipv4"]];
config.ipv6Server = [QNServerDnsServer config:info[@"ipv6"]];
return config;
}
@end
@interface QNServerDnsConfig()
@property(nonatomic, strong)NSNumber *enable;
@property(nonatomic, assign)long clearId;
@property(nonatomic, assign)BOOL clearCache;
@property(nonatomic, strong)QNServerUdpDnsConfig *udpConfig;
@property(nonatomic, strong)QNServerDohConfig *dohConfig;
@end
@implementation QNServerDnsConfig
+ (instancetype)config:(NSDictionary *)info {
QNServerDnsConfig *config = [[QNServerDnsConfig alloc] init];
config.enable = info[@"enabled"];
config.clearId = [info[@"clear_id"] longValue];
config.clearCache = [info[@"clear_cache"] longValue];
config.dohConfig = [QNServerDohConfig config:info[@"doh"]];
config.udpConfig = [QNServerUdpDnsConfig config:info[@"udp"]];
return config;
}
@end
@interface QNConnectCheckConfig()
@property(nonatomic, assign)BOOL isOverride;
@property(nonatomic, strong)NSNumber *enable;
@property(nonatomic, strong)NSNumber *timeoutMs;
@property(nonatomic, strong)NSArray <NSString *> *urls;
@end
@implementation QNConnectCheckConfig
+ (instancetype)config:(NSDictionary *)info {
QNConnectCheckConfig *config = [[QNConnectCheckConfig alloc] init];
config.isOverride = [info[@"override_default"] boolValue];
config.enable = info[@"enabled"];
config.timeoutMs = info[@"timeout_ms"];
if (info[@"urls"] && [info[@"urls"] isKindOfClass:[NSArray class]]) {
config.urls = info[@"urls"];
}
return config;
}
@end
@interface QNServerConfig()
@property(nonatomic, strong)NSDictionary *info;
@property(nonatomic, assign)double timestamp;
@property(nonatomic, assign)long ttl;
@property(nonatomic, strong)QNServerRegionConfig *regionConfig;
@property(nonatomic, strong)QNServerDnsConfig *dnsConfig;
@property(nonatomic, strong)QNConnectCheckConfig *connectCheckConfig;
@end
@implementation QNServerConfig
+ (instancetype)config:(NSDictionary *)info {
return [[QNServerConfig alloc] initWithDictionary:info];
}
- (nonnull id<QNCacheObject>)initWithDictionary:(nullable NSDictionary *)info {
if (self = [self init]) {
if (info) {
self.ttl = [info[@"ttl"] longValue];
self.regionConfig = [QNServerRegionConfig config:info[@"region"]];
self.dnsConfig = [QNServerDnsConfig config:info[@"dns"]];
self.connectCheckConfig = [QNConnectCheckConfig config:info[@"connection_check"]];
if (self.ttl < 10) {
self.ttl = 10;
}
NSMutableDictionary *mutableInfo = [info mutableCopy];
if (info[@"timestamp"] != nil) {
self.timestamp = [info[@"timestamp"] doubleValue];
}
if (self.timestamp == 0) {
self.timestamp = [[NSDate date] timeIntervalSince1970];
mutableInfo[@"timestamp"] = @(self.timestamp);
}
self.info = [mutableInfo copy];
}
}
return self;
}
- (nullable NSDictionary *)toDictionary {
return [self.info copy];
}
- (BOOL)isValid {
return [[NSDate date] timeIntervalSince1970] < (self.timestamp + self.ttl);
}
@end

View File

@@ -0,0 +1,29 @@
//
// QNServerConfigCache.h
// QiniuSDK
//
// Created by yangsen on 2021/8/30.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "QNServerConfig.h"
#import "QNServerUserConfig.h"
NS_ASSUME_NONNULL_BEGIN
@interface QNServerConfigCache : NSObject
@property(nonatomic, strong)QNServerConfig *config;
@property(nonatomic, strong)QNServerUserConfig *userConfig;
- (QNServerConfig *)getConfigFromDisk;
- (void)saveConfigToDisk:(QNServerConfig *)config;
- (QNServerUserConfig *)getUserConfigFromDisk;
- (void)saveUserConfigToDisk:(QNServerUserConfig *)config;
- (void)removeConfigCache;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,65 @@
//
// QNServerConfigCache.m
// QiniuSDK
//
// Created by yangsen on 2021/8/30.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "QNServerConfigCache.h"
#import "QNUtils.h"
#import "QNCache.h"
#define kQNServerConfigDiskKey @"config"
#define kQNServerUserConfigDiskKey @"userConfig"
@interface QNServerConfigCache(){
QNServerConfig *_config;
QNServerUserConfig *_userConfig;
}
@property(nonatomic, strong) QNCache *configCache;
@property(nonatomic, strong) QNCache *userConfigCache;
@end
@implementation QNServerConfigCache
- (instancetype)init {
if (self = [super init]) {
QNCacheOption *option = [[QNCacheOption alloc] init];
option.version = @"v1.0.0";
self.configCache = [QNCache cache:[QNServerConfig class] option:option];
option = [[QNCacheOption alloc] init];
option.version = @"v1.0.0";
self.userConfigCache = [QNCache cache:[QNServerUserConfig class] option:option];
}
return self;
}
//MARK: --- config
- (QNServerConfig *)getConfigFromDisk {
return [self.configCache cacheForKey:kQNServerConfigDiskKey];
}
- (void)saveConfigToDisk:(QNServerConfig *)config {
[self.configCache cache:config forKey:kQNServerConfigDiskKey atomically:true];
}
//MARK: --- user config
- (QNServerUserConfig *)getUserConfigFromDisk {
return [self.userConfigCache cacheForKey:kQNServerUserConfigDiskKey];
}
- (void)saveUserConfigToDisk:(QNServerUserConfig *)config {
[self.userConfigCache cache:config forKey:kQNServerUserConfigDiskKey atomically:true];
}
- (void)removeConfigCache {
@synchronized (self) {
[self.configCache clearMemoryCache];
[self.configCache clearDiskCache];
[self.userConfigCache clearMemoryCache];
[self.userConfigCache clearDiskCache];
}
}
@end

View File

@@ -0,0 +1,33 @@
//
// QNServerConfiguration.h
// QiniuSDK
//
// Created by yangsen on 2021/8/25.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface QNServerConfigMonitor : NSObject
@property(nonatomic, assign, class)BOOL enable;
@property(class, nonatomic, strong)NSString *token;
// 开始监控
+ (void)startMonitor;
// 停止监控
+ (void)endMonitor;
// 配置 token
+ (void)setToken:(NSString *)token;
// 移除缓存
+ (void)removeConfigCache;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,237 @@
//
// QNServerConfiguration.m
// QiniuSDK
//
// Created by yangsen on 2021/8/25.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "QNLogUtil.h"
#import "QNDefine.h"
#import "QNAutoZone.h"
#import "QNDnsPrefetch.h"
#import "QNConfiguration.h"
#import "QNServerConfigSynchronizer.h"
#import "QNServerConfigCache.h"
#import "QNServerConfigMonitor.h"
#import "QNTransactionManager.h"
#define kQNServerConfigTransactionKey @"QNServerConfig"
@interface QNGlobalConfiguration(DnsDefaultServer)
@property(nonatomic, strong)NSArray *defaultConnectCheckUrls;
@property(nonatomic, strong)NSArray *defaultDohIpv4Servers;
@property(nonatomic, strong)NSArray *defaultDohIpv6Servers;
@property(nonatomic, strong)NSArray *defaultUdpDnsIpv4Servers;
@property(nonatomic, strong)NSArray *defaultUdpDnsIpv6Servers;
@end
@implementation QNGlobalConfiguration(DnsDefaultServer)
@dynamic defaultConnectCheckUrls;
@dynamic defaultDohIpv4Servers;
@dynamic defaultDohIpv6Servers;
@dynamic defaultUdpDnsIpv4Servers;
@dynamic defaultUdpDnsIpv6Servers;
@end
@interface QNServerConfigMonitor()
@property(nonatomic, assign)BOOL enable;
@property(nonatomic, strong)QNServerConfigCache *cache;
@end
@implementation QNServerConfigMonitor
+ (instancetype)share {
static QNServerConfigMonitor *monitor = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
monitor = [[QNServerConfigMonitor alloc] init];
});
return monitor;
}
- (instancetype)init {
if (self = [super init]) {
_enable = true;
_cache = [[QNServerConfigCache alloc] init];
}
return self;
}
+ (BOOL)enable {
return [[QNServerConfigMonitor share] enable];
}
+ (void)setEnable:(BOOL)enable {
[QNServerConfigMonitor share].enable = enable;
}
+ (NSString *)token {
return QNServerConfigSynchronizer.token;
}
// token
+ (void)setToken:(NSString *)token {
QNServerConfigSynchronizer.token = token;
}
//
+ (void)startMonitor {
if (![QNServerConfigMonitor share].enable) {
return;
}
@synchronized (self) {
BOOL isExist = [kQNTransactionManager existTransactionsForName:kQNServerConfigTransactionKey];
if (isExist) {
return;
}
int interval = 120 + arc4random()%240;
QNTransaction *transaction = [QNTransaction timeTransaction:kQNServerConfigTransactionKey after:0 interval:interval action:^{
[[QNServerConfigMonitor share] monitor];
}];
[kQNTransactionManager addTransaction:transaction];
}
}
//
+ (void)endMonitor {
@synchronized (self) {
NSArray *transactions = [kQNTransactionManager transactionsForName:kQNServerConfigTransactionKey];
for (QNTransaction *transaction in transactions) {
[kQNTransactionManager removeTransaction:transaction];
}
}
}
+ (void)removeConfigCache {
[[QNServerConfigMonitor share].cache removeConfigCache];
}
- (void)monitor {
if (!self.enable) {
return;
}
if (self.cache.config == nil) {
QNServerConfig *config = [self.cache getConfigFromDisk];
[self handleServerConfig:config];
self.cache.config = config;
}
if (!self.cache.config.isValid) {
[QNServerConfigSynchronizer getServerConfigFromServer:^(QNServerConfig * _Nonnull config) {
if (config == nil) {
return;
}
[self handleServerConfig:config];
self.cache.config = config;
[self.cache saveConfigToDisk:config];
}];
}
if (self.cache.userConfig == nil) {
QNServerUserConfig *config = [self.cache getUserConfigFromDisk];
[self handleServerUserConfig:config];
self.cache.userConfig = config;
}
if (!self.cache.userConfig.isValid) {
[QNServerConfigSynchronizer getServerUserConfigFromServer:^(QNServerUserConfig * _Nonnull config) {
if (config == nil) {
return;
}
[self handleServerUserConfig:config];
self.cache.userConfig = config;
[self.cache saveUserConfigToDisk:config];
}];
}
}
- (void)handleServerConfig:(QNServerConfig *)config {
if (config == nil) {
return;
}
// region
if (self.cache.config.regionConfig &&
config.regionConfig.clearId > self.cache.config.regionConfig.clearId &&
config.regionConfig.clearCache) {
QNLogDebug(@"server config: clear region cache");
[QNAutoZone clearCache];
}
// dns
if (config.dnsConfig.enable) {
QNLogDebug(@"server config: dns enable %@", config.dnsConfig.enable);
kQNGlobalConfiguration.isDnsOpen = [config.dnsConfig.enable boolValue];
}
// dns
if (self.cache.config.dnsConfig &&
config.dnsConfig.clearId > self.cache.config.dnsConfig.clearId &&
config.dnsConfig.clearCache) {
QNLogDebug(@"server config: clear dns cache");
[kQNDnsPrefetch clearDnsCache:nil];
}
// udp
if (config.dnsConfig.udpConfig.enable) {
QNLogDebug(@"server config: udp enable %@", config.dnsConfig.udpConfig.enable);
kQNGlobalConfiguration.udpDnsEnable = [config.dnsConfig.udpConfig.enable boolValue];
}
if (config.dnsConfig.udpConfig.ipv4Server.isOverride &&
[config.dnsConfig.udpConfig.ipv4Server.servers isKindOfClass:[NSArray class]]) {
QNLogDebug(@"server config: udp config ipv4Server %@", config.dnsConfig.udpConfig.ipv4Server.servers);
kQNGlobalConfiguration.defaultUdpDnsIpv4Servers = [config.dnsConfig.udpConfig.ipv4Server.servers copy];
}
if (config.dnsConfig.udpConfig.ipv6Server.isOverride &&
[config.dnsConfig.udpConfig.ipv6Server.servers isKindOfClass:[NSArray class]]) {
QNLogDebug(@"server config: udp config ipv6Server %@", config.dnsConfig.udpConfig.ipv6Server.servers);
kQNGlobalConfiguration.defaultUdpDnsIpv6Servers = [config.dnsConfig.udpConfig.ipv6Server.servers copy];
}
// doh
if (config.dnsConfig.dohConfig.enable) {
kQNGlobalConfiguration.dohEnable = [config.dnsConfig.dohConfig.enable boolValue];
QNLogDebug(@"server config: doh enable %@", config.dnsConfig.dohConfig.enable);
}
if (config.dnsConfig.dohConfig.ipv4Server.isOverride &&
[config.dnsConfig.dohConfig.ipv4Server.servers isKindOfClass:[NSArray class]]) {
QNLogDebug(@"server config: doh config ipv4Server %@", config.dnsConfig.dohConfig.ipv4Server.servers);
kQNGlobalConfiguration.defaultDohIpv4Servers = [config.dnsConfig.dohConfig.ipv4Server.servers copy];
}
if (config.dnsConfig.dohConfig.ipv6Server.isOverride &&
[config.dnsConfig.dohConfig.ipv6Server.servers isKindOfClass:[NSArray class]]) {
QNLogDebug(@"server config: doh config ipv6Server %@", config.dnsConfig.dohConfig.ipv6Server.servers);
kQNGlobalConfiguration.defaultDohIpv6Servers = [config.dnsConfig.dohConfig.ipv6Server.servers copy];
}
// connect check
if (config.connectCheckConfig.enable) {
kQNGlobalConfiguration.connectCheckEnable = [config.connectCheckConfig.enable boolValue];
QNLogDebug(@"server config: connect check enable %@", config.dnsConfig.dohConfig.enable);
}
if (config.connectCheckConfig.timeoutMs) {
kQNGlobalConfiguration.connectCheckTimeout = [config.connectCheckConfig.timeoutMs doubleValue] / 1000;
QNLogDebug(@"server config: connect check timeout %@", config.connectCheckConfig.timeoutMs);
}
if (config.connectCheckConfig.isOverride &&
config.connectCheckConfig.urls &&
[config.connectCheckConfig.urls isKindOfClass:[NSArray class]]) {
kQNGlobalConfiguration.defaultConnectCheckUrls = config.connectCheckConfig.urls;
QNLogDebug(@"server config: connect check urls %@", config.connectCheckConfig.urls);
}
}
- (void)handleServerUserConfig:(QNServerUserConfig *)config {
if (config == nil) {
return;
}
if (config.networkCheckEnable) {
QNLogDebug(@"server config: connect check enable %@", config.networkCheckEnable);
kQNGlobalConfiguration.connectCheckEnable = [config.networkCheckEnable boolValue];
}
}
@end

View File

@@ -0,0 +1,24 @@
//
// QNServerConfigSynchronizer.h
// QiniuSDK
//
// Created by yangsen on 2021/8/30.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "QNServerConfig.h"
#import "QNServerUserConfig.h"
NS_ASSUME_NONNULL_BEGIN
@interface QNServerConfigSynchronizer : NSObject
@property(class, nonatomic, strong)NSString *token;
@property(class, nonatomic, strong)NSArray <NSString *> *hosts;
+ (void)getServerConfigFromServer:(void(^)(QNServerConfig *config))complete;
+ (void)getServerUserConfigFromServer:(void(^)(QNServerUserConfig *config))complete;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,158 @@
//
// QNServerConfigSynchronizer.m
// QiniuSDK
//
// Created by yangsen on 2021/8/30.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "QNConfig.h"
#import "QNUpToken.h"
#import "QNZoneInfo.h"
#import "QNResponseInfo.h"
#import "QNRequestTransaction.h"
#import "QNServerConfigSynchronizer.h"
#import <pthread.h>
static NSString *Token = nil;
static NSArray <NSString *> *Hosts = nil;
static pthread_mutex_t TokenMutexLock = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t HostsMutexLock = PTHREAD_MUTEX_INITIALIZER;
static QNRequestTransaction *serverConfigTransaction = nil;
static QNRequestTransaction *serverUserConfigTransaction = nil;
@implementation QNServerConfigSynchronizer
//MARK: --- server config
+ (void)getServerConfigFromServer:(void(^)(QNServerConfig *config))complete {
if (complete == nil) {
return;
}
QNRequestTransaction *transaction = [self createServerConfigTransaction];
if (transaction == nil) {
complete(nil);
return;
}
[transaction serverConfig:^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response) {
if (responseInfo.isOK && response != nil) {
complete([QNServerConfig config:response]);
} else {
complete(nil);
}
[self destroyServerConfigRequestTransaction];
}];
}
+ (QNRequestTransaction *)createServerConfigTransaction {
@synchronized (self) {
// token
if (serverConfigTransaction != nil) {
return nil;
}
QNUpToken *token = [QNUpToken parse:self.token];
if (token == nil) {
token = [QNUpToken getInvalidToken];
}
NSArray *hosts = self.hosts;
if (hosts == nil) {
hosts = kQNPreQueryHosts;
}
QNRequestTransaction *transaction = [[QNRequestTransaction alloc] initWithHosts:hosts
regionId:QNZoneInfoEmptyRegionId
token:token];
serverConfigTransaction = transaction;
return transaction;
}
}
+ (void)destroyServerConfigRequestTransaction {
@synchronized (self) {
serverConfigTransaction = nil;
}
}
//MARK: --- server user config
+ (void)getServerUserConfigFromServer:(void(^)(QNServerUserConfig *config))complete {
if (complete == nil) {
return;
}
QNRequestTransaction *transaction = [self createServerUserConfigTransaction];
if (transaction == nil) {
complete(nil);
return;
}
[transaction serverUserConfig:^(QNResponseInfo * _Nullable responseInfo, QNUploadRegionRequestMetrics * _Nullable metrics, NSDictionary * _Nullable response) {
if (responseInfo.isOK && response != nil) {
complete([QNServerUserConfig config:response]);
} else {
complete(nil);
}
[self destroyServerConfigRequestTransaction];
}];
}
+ (QNRequestTransaction *)createServerUserConfigTransaction {
@synchronized (self) {
if (serverConfigTransaction != nil) {
return nil;
}
QNUpToken *token = [QNUpToken parse:self.token];
if (token == nil || !token.isValid) {
return nil;
}
NSArray *hosts = self.hosts;
if (hosts == nil) {
hosts = kQNPreQueryHosts;
}
QNRequestTransaction *transaction = [[QNRequestTransaction alloc] initWithHosts:hosts
regionId:QNZoneInfoEmptyRegionId
token:token];
serverUserConfigTransaction = transaction;
return transaction;
}
}
+ (void)destroyServerUserConfigRequestTransaction {
@synchronized (self) {
serverUserConfigTransaction = nil;
}
}
+ (void)setToken:(NSString *)token {
pthread_mutex_lock(&TokenMutexLock);
Token = token;
pthread_mutex_unlock(&TokenMutexLock);
}
+ (NSString *)token {
NSString *token = nil;
pthread_mutex_lock(&TokenMutexLock);
token = Token;
pthread_mutex_unlock(&TokenMutexLock);
return token;
}
+ (void)setHosts:(NSArray<NSString *> *)servers {
pthread_mutex_lock(&HostsMutexLock);
Hosts = [servers copy];
pthread_mutex_unlock(&HostsMutexLock);
}
+ (NSArray<NSString *> *)hosts {
NSArray<NSString *> *hosts = nil;
pthread_mutex_lock(&HostsMutexLock);
hosts = [Hosts copy];
pthread_mutex_unlock(&HostsMutexLock);
return hosts;
}
@end

View File

@@ -0,0 +1,27 @@
//
// QNServerUserConfig.h
// QiniuSDK
//
// Created by yangsen on 2021/8/30.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "QNCache.h"
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface QNServerUserConfig : NSObject<QNCacheObject>
@property(nonatomic, assign, readonly)BOOL isValid;
@property(nonatomic, assign, readonly)long ttl;
@property(nonatomic, strong, readonly)NSNumber *http3Enable;
@property(nonatomic, strong, readonly)NSNumber *networkCheckEnable;
@property(nonatomic, strong, readonly)NSDictionary *info;
+ (instancetype)config:(NSDictionary *)info;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,58 @@
//
// QNServerUserConfig.m
// QiniuSDK
//
// Created by yangsen on 2021/8/30.
// Copyright © 2021 Qiniu. All rights reserved.
//
#import "QNServerUserConfig.h"
@interface QNServerUserConfig()
@property(nonatomic, strong)NSDictionary *info;
@property(nonatomic, assign)double timestamp;
@property(nonatomic, assign)long ttl;
@property(nonatomic, strong)NSNumber *http3Enable;
@property(nonatomic, strong)NSNumber *retryMax;
@property(nonatomic, strong)NSNumber *networkCheckEnable;
@end
@implementation QNServerUserConfig
+ (instancetype)config:(NSDictionary *)info {
return [[QNServerUserConfig alloc] initWithDictionary:info];
}
- (nonnull id<QNCacheObject>)initWithDictionary:(nullable NSDictionary *)info {
if (self = [super init]) {
if (info) {
self.ttl = [info[@"ttl"] longValue];
self.http3Enable = info[@"http3"][@"enabled"];
self.networkCheckEnable = info[@"network_check"][@"enabled"];
if (self.ttl < 10) {
self.ttl = 10;
}
NSMutableDictionary *mutableInfo = [info mutableCopy];
if (info[@"timestamp"] != nil) {
self.timestamp = [info[@"timestamp"] doubleValue];
}
if (self.timestamp == 0) {
self.timestamp = [[NSDate date] timeIntervalSince1970];
mutableInfo[@"timestamp"] = @(self.timestamp);
}
self.info = [mutableInfo copy];
}
}
return self;
}
- (nullable NSDictionary *)toDictionary {
return [self.info copy];
}
- (BOOL)isValid {
return [[NSDate date] timeIntervalSince1970] < (self.timestamp + self.ttl);
}
@end