diff --git a/QiniuDemo/Podfile b/QiniuDemo/Podfile index 1532ff0d..cd8782dc 100755 --- a/QiniuDemo/Podfile +++ b/QiniuDemo/Podfile @@ -7,6 +7,7 @@ target "QiniuDemo" do # pod 'Qiniu', '7.1.7' pod 'Qiniu',:path => '../' pod 'HappyDNS',:path => '../../happy-dns-objc' + pod 'TZImagePickerController', '~> 3.8.7' end target "QiniuDemoTests" do diff --git a/QiniuDemo/QiniuDemo.xcodeproj/project.pbxproj b/QiniuDemo/QiniuDemo.xcodeproj/project.pbxproj index 0995e5c6..213ee64d 100644 --- a/QiniuDemo/QiniuDemo.xcodeproj/project.pbxproj +++ b/QiniuDemo/QiniuDemo.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 26F62ADDD5B6306E978C9A3F /* libPods-QiniuDemoTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 14CB2139715DD7B6FD5B146E /* libPods-QiniuDemoTests.a */; }; + 45169DF22C3D263200737759 /* Uploader.m in Sources */ = {isa = PBXBuildFile; fileRef = 45169DF12C3D263200737759 /* Uploader.m */; }; 4561F02C28D9A6F80098A697 /* UploadResource_14M.zip in Resources */ = {isa = PBXBuildFile; fileRef = 4561F02B28D9A6F80098A697 /* UploadResource_14M.zip */; }; 45E6080929ADD57100634200 /* UploadResource_1G.zip in Resources */ = {isa = PBXBuildFile; fileRef = 45E6080829ADD57100634200 /* UploadResource_1G.zip */; }; 93D230241C86D7F700434F6D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D230231C86D7F700434F6D /* main.m */; }; @@ -42,6 +43,8 @@ 14CB2139715DD7B6FD5B146E /* libPods-QiniuDemoTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-QiniuDemoTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 24EE530BBB100BED89B08A3D /* Pods-QiniuDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-QiniuDemo.release.xcconfig"; path = "Pods/Target Support Files/Pods-QiniuDemo/Pods-QiniuDemo.release.xcconfig"; sourceTree = ""; }; 3189882026469145003CCA68 /* QiniuDemo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = QiniuDemo.entitlements; sourceTree = ""; }; + 45169DF02C3D263200737759 /* Uploader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Uploader.h; sourceTree = ""; }; + 45169DF12C3D263200737759 /* Uploader.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Uploader.m; sourceTree = ""; }; 4561F02B28D9A6F80098A697 /* UploadResource_14M.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = UploadResource_14M.zip; sourceTree = ""; }; 4561F02F28D9AB090098A697 /* Configure.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Configure.h; sourceTree = ""; }; 45E6080829ADD57100634200 /* UploadResource_1G.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = UploadResource_1G.zip; sourceTree = ""; }; @@ -169,6 +172,8 @@ 93D230301C86D7F700434F6D /* LaunchScreen.storyboard */, 93D230331C86D7F700434F6D /* Info.plist */, 93D230221C86D7F700434F6D /* Supporting Files */, + 45169DF02C3D263200737759 /* Uploader.h */, + 45169DF12C3D263200737759 /* Uploader.m */, ); path = QiniuDemo; sourceTree = ""; @@ -340,11 +345,13 @@ "${PODS_ROOT}/Target Support Files/Pods-QiniuDemo/Pods-QiniuDemo-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/HappyDNS/HappyDNS.privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/Qiniu/Qiniu.privacy.bundle", + "${PODS_ROOT}/TZImagePickerController/TZImagePickerController/TZImagePickerController/TZImagePickerController.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/HappyDNS.privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Qiniu.privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TZImagePickerController.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -395,6 +402,7 @@ buildActionMask = 2147483647; files = ( 93D2302A1C86D7F700434F6D /* ViewController.m in Sources */, + 45169DF22C3D263200737759 /* Uploader.m in Sources */, 93D230271C86D7F700434F6D /* AppDelegate.m in Sources */, 93D230241C86D7F700434F6D /* main.m in Sources */, ); diff --git a/QiniuDemo/QiniuDemo/Uploader.h b/QiniuDemo/QiniuDemo/Uploader.h new file mode 100644 index 00000000..9906fb99 --- /dev/null +++ b/QiniuDemo/QiniuDemo/Uploader.h @@ -0,0 +1,17 @@ +// +// Uploader.h +// QiniuDemo +// +// Created by yangsen on 2024/7/9. +// Copyright © 2024 Aaron. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface Uploader : NSObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/QiniuDemo/QiniuDemo/Uploader.m b/QiniuDemo/QiniuDemo/Uploader.m new file mode 100644 index 00000000..40e9b5f6 --- /dev/null +++ b/QiniuDemo/QiniuDemo/Uploader.m @@ -0,0 +1,135 @@ +// +// Uploader.m +// QiniuDemo +// +// Created by yangsen on 2024/7/9. +// Copyright © 2024 Aaron. All rights reserved. +// + +#import "Uploader.h" +#import + +@implementation Uploader + +- (void)upload { + + QNConfiguration *config = [QNConfiguration build:^(QNConfigurationBuilder *builder) { + // Upload_Domain1 和 Upload_Domain2 为加速域名,可以参考七牛云存储控制台域名管理页面,建议通过用户服务下发,不要硬编码 + builder.zone = [[QNFixedZone alloc] initWithUpDomainList:@[@"Upload_Domain1", @"Upload_Domain2"]]; + }]; + + QNUploadManager *manager = [[QNUploadManager alloc] initWithConfiguration:config]; + + // QNFixedZone *zone = [QNFixedZone createWithRegionId:@"z0"]; + + QNAutoZone *zone = [QNAutoZone zoneWithUcHosts:@[@"UCHost0", @"UCHost1"]]; + +} + +- (void)upload0 { + + + QNConfiguration *configuration = [QNConfiguration build:^(QNConfigurationBuilder *builder) { + // 配置上传区域,Host0、Host1 建议通过服务方式下发 + builder.zone = [[QNFixedZone alloc] initWithUpDomainList:@[@"Host0", @"Host2"]]; + // 分片上传阈值:4MB,大于 4MB 采用分片上传,小于 4MB 采用表单上传 + builder.putThreshold = 4*1024*1024; + // 开启并发分片上传 + builder.useConcurrentResumeUpload = true; + // 使用分片 V2 + builder.resumeUploadVersion = QNResumeUploadVersionV2; + // 文件分片上传时断点续传信息保存,表单上传此配置无效 + NSString *recorderPath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"]; + builder.recorder = [QNFileRecorder fileRecorderWithFolder:recorderPath error:nil]; + }]; + QNUploadManager *upManager = [[QNUploadManager alloc] initWithConfiguration:configuration]; + + __weak typeof(self) weakSelf = self; + QNUploadOption *uploadOption = [[QNUploadOption alloc] initWithMime:nil progressHandler:^(NSString *key, float percent) { + NSLog(@"percent == %.2f", percent); + } + params:nil + checkCrc:NO + cancellationSignal:^BOOL{ + // 当需要取消时,此处返回 true,SDK 内部会多次检查返回值,当返回值为 true 时会取消上传操作 + return false; + }]; + + NSString *filePath = @""; // 文件路径 + NSString *key = @""; // 文件 key + NSString *uploadToken = @""; // 上传的 Token + [upManager putFile:filePath key:key token:uploadToken complete:^(QNResponseInfo *info, NSString *key, NSDictionary *resp) { + if (info.isOK) { + // 上传成功 + } else { + // 上传失败 + } + } option:uploadOption]; +} + + +- (void)upload1 { + + QNConfiguration *configuration = [QNConfiguration build:^(QNConfigurationBuilder *builder) { + builder.zone = [[QNAutoZone alloc] init]; // 配置上传区域,使用 QNAutoZone + builder.recorderKeyGen = ^NSString *(NSString *uploadKey, NSString *filePath) { + // 自定义 文件分片上传时断点续传信息保存的 key,默认使用 uploadKey + return [NSString stringWithFormat:@"%@-%@", uploadKey, filePath]; + }; + // 文件分片上传时断点续传信息保存,表单上传此配置无效 + NSString *recorderPath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"]; + builder.recorder = [QNFileRecorder fileRecorderWithFolder:recorderPath error:nil]; + + // 文件采用分片上传时,分片大小为 2MB + builder.chunkSize = 2*1024*1024; + + // 分片上传阈值:4MB,大于 4MB 采用分片上传,小于 4MB 采用表单上传 + builder.putThreshold = 4*1024*1024; + + // 单个域名/IP请求失败后最大重试次数为 1 次 + builder.retryMax = 1; + + // 重试时间间隔:0.5s + builder.retryInterval = 0.5; + + // 请求超时时间:60s + builder.timeoutInterval = 60; + + // 使用 HTTPS + builder.useHttps = true; + + // 使用备用域名进行重试 + builder.allowBackupHost = true; + + // 开启加速上传 + builder.accelerateUploading = true; + + // 开启并发分片上传 + builder.useConcurrentResumeUpload = true; + + // 使用并发分片上传时,一个文件并发上传的分片个数 + builder.concurrentTaskCount = 2; + + // 使用分片 V2 + builder.resumeUploadVersion = QNResumeUploadVersionV2; + }]; + + + // 指定文件 mime type + NSString *mimeType = @""; + // 用于服务器上传回调通知的自定义参数,参数的key必须以x: 开头 eg: x:foo + NSDictionary * params = @{}; + // 用于设置meta数据,参数的key必须以x-qn-meta- 开头 eg: x-qn-meta-key + NSDictionary * metaDataParams = @{}; + BOOL checkCrc = true; + QNUploadOption *option = [[QNUploadOption alloc] initWithMime:mimeType + byteProgressHandler:^(NSString *key, long long uploadBytes, long long totalBytes) { + // 处理上传进度 + } params:params metaDataParams:metaDataParams checkCrc:checkCrc cancellationSignal:^BOOL{ + // 当需要取消时,此处返回 false,SDK 内部会不间断检测此返回值 + return false; + }]; + + +} +@end diff --git a/QiniuDemo/QiniuDemo/ViewController.m b/QiniuDemo/QiniuDemo/ViewController.m index a9d8ae3a..b0e61272 100755 --- a/QiniuDemo/QiniuDemo/ViewController.m +++ b/QiniuDemo/QiniuDemo/ViewController.m @@ -16,13 +16,14 @@ //#import "QNTransactionManager.h" #import #import +#import typedef NS_ENUM(NSInteger, UploadState){ UploadStatePrepare, UploadStateUploading, UploadStateCancelling }; -@interface DnsItem : NSObject +@interface DnsItem : NSObject @property(nonatomic, copy)NSString *hostValue; @property(nonatomic, copy)NSString *ipValue; @property(nonatomic, strong)NSNumber *ttlValue; @@ -42,6 +43,7 @@ @interface ViewController () *fetchResult = [PHAsset fetchAssetsInAssetCollection:collection options:option]; + if (fetchResult.count > 0) { + return fetchResult.firstObject; } } @@ -266,26 +272,28 @@ - (PHAsset *)getPHAssert { } - (void)gotoImageLibrary { - if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) { - UIImagePickerController *picker = [[UIImagePickerController alloc] init]; - picker.delegate = self; - picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; - [self presentViewController:picker animated:YES completion:nil]; - } else { - [self alertMessage:@"访问图片库错误"]; - } -} - -#pragma mark UIImagePickerControllerDelegate -- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { - self.pickImage = info[UIImagePickerControllerOriginalImage]; - self.preViewImage.image = self.pickImage; - [picker dismissViewControllerAnimated:YES completion:^{ + + TZImagePickerController *imagePickerVc = [[TZImagePickerController alloc] initWithMaxImagesCount:1 delegate:self]; + + // You can get the photos by block, the same as by delegate. + // 你可以通过block或者代理,来得到用户选择的照片. + [imagePickerVc setDidFinishPickingPhotosHandle:^(NSArray *photos, NSArray *assets, BOOL isSelectOriginalPhoto) { + if (assets.firstObject) { + self.pickFile = assets.firstObject; + self.preViewImage.image = photos.firstObject; +// [[TZImageManager manager] getOriginalPhotoWithAsset:self.pickFile completion:^(UIImage *photo, NSDictionary *info) { +// self.preViewImage.image = photo; +// }]; + } }]; -} - -- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { - [picker dismissViewControllerAnimated:YES completion:nil]; + [imagePickerVc setDidFinishPickingVideoHandle:^(UIImage *coverImage, PHAsset *asset) { + self.pickFile = asset; + self.preViewImage.image = coverImage; +// [[TZImageManager manager] getVideoWithAsset:asset completion:^(AVPlayerItem *playerItem, NSDictionary *info) { +// self.preViewImage.image = photo; +// }]; + }]; + [self presentViewController:imagePickerVc animated:YES completion:nil]; } //照片获取本地路径转换 diff --git a/QiniuSDK/Utils/QNPHAssetResource.m b/QiniuSDK/Utils/QNPHAssetResource.m index 344e1b30..b11845a5 100755 --- a/QiniuSDK/Utils/QNPHAssetResource.m +++ b/QiniuSDK/Utils/QNPHAssetResource.m @@ -17,11 +17,7 @@ kAMASSETMETADATA_ALLFINISHED = 0 }; -@interface QNPHAssetResource () { - BOOL _hasGotInfo; -} - -@property (nonatomic) PHAsset *phAsset; +@interface QNPHAssetResource () @property (nonatomic) PHAssetResource *phAssetResource; @@ -31,7 +27,9 @@ @interface QNPHAssetResource () { @property (nonatomic, strong) NSData *assetData; -@property (nonatomic, strong) NSURL *assetURL; +@property (nonatomic, assign)BOOL hasRealFilePath; +@property (nonatomic, copy) NSString *filePath; +@property (nonatomic, strong) NSFileHandle *file; @property (nonatomic, strong) NSLock *lock; @@ -41,16 +39,20 @@ @implementation QNPHAssetResource - (instancetype)init:(PHAssetResource *)phAssetResource error:(NSError *__autoreleasing *)error { if (self = [super init]) { - PHAsset *phasset = [PHAsset fetchAssetsWithBurstIdentifier:self.phAssetResource.assetLocalIdentifier options:nil][0]; - NSDate *createTime = phasset.creationDate; - int64_t t = 0; - if (createTime != nil) { - t = [createTime timeIntervalSince1970]; + PHFetchResult *results = [PHAsset fetchAssetsWithBurstIdentifier:phAssetResource.assetLocalIdentifier options:nil]; + if (results.firstObject != nil) { + PHAsset *phasset = results.firstObject; + NSDate *createTime = phasset.creationDate; + int64_t t = 0; + if (createTime != nil) { + t = [createTime timeIntervalSince1970]; + } + _fileModifyTime = t; } - _fileModifyTime = t; + _phAssetResource = phAssetResource; _lock = [[NSLock alloc] init]; - [self getInfo]; + [self getInfo:error]; } return self; } @@ -62,13 +64,12 @@ - (NSData *)read:(long long)offset NSData *data = nil; @try { [_lock lock]; - if (!self.assetData) { - self.assetData = [self fetchDataFromAsset:self.phAssetResource error:error]; - } - if (_assetData != nil && offset < _assetData.length) { NSUInteger realSize = MIN((NSUInteger)size, _assetData.length - (NSUInteger)offset); data = [_assetData subdataWithRange:NSMakeRange((NSUInteger)offset, realSize)]; + } else if (_file != nil && offset < _fileSize) { + [_file seekToFileOffset:offset]; + data = [_file readDataOfLength:size]; } else { data = [NSData data]; } @@ -86,10 +87,18 @@ - (NSData *)readAllWithError:(NSError **)error { } - (void)close { + if (self.file) { + [self.file closeFile]; + } + + // 如果是导出的 file 删除 + if (self.filePath) { + [[NSFileManager defaultManager] removeItemAtPath:self.filePath error:nil]; + } } - (NSString *)path { - return self.assetURL.path; + return self.filePath ? self.filePath : nil; } - (int64_t)modifyTime { @@ -104,78 +113,63 @@ - (NSString *)fileType { return @"PHAssetResource"; } -- (void)getInfo { - if (!_hasGotInfo) { - _hasGotInfo = YES; - NSConditionLock *assetReadLock = [[NSConditionLock alloc] initWithCondition:kAMASSETMETADATA_PENDINGREADS]; - - NSString *fileName = [NSString stringWithFormat:@"tempAsset-%f-%d.mov", [[NSDate date] timeIntervalSince1970], arc4random()%100000]; - NSString *pathToWrite = [NSTemporaryDirectory() stringByAppendingString:fileName]; - NSURL *localpath = [NSURL fileURLWithPath:pathToWrite]; - PHAssetResourceRequestOptions *options = [PHAssetResourceRequestOptions new]; - options.networkAccessAllowed = YES; - [[PHAssetResourceManager defaultManager] writeDataForAssetResource:self.phAssetResource toFile:localpath options:options completionHandler:^(NSError *_Nullable error) { - if (error == nil) { - AVURLAsset *urlAsset = [AVURLAsset URLAssetWithURL:localpath options:nil]; - NSNumber *fileSize = nil; - [urlAsset.URL getResourceValue:&fileSize forKey:NSURLFileSizeKey error:nil]; - self.fileSize = [fileSize unsignedLongLongValue]; - self.assetURL = urlAsset.URL; - self.assetData = [NSData dataWithData:[NSData dataWithContentsOfURL:urlAsset.URL]]; - } else { - NSLog(@"%@", error); +- (void)getInfo:(NSError **)error { + [self exportAssert]; + + NSError *error2 = nil; + NSDictionary *fileAttr = [[NSFileManager defaultManager] attributesOfItemAtPath:self.filePath error:&error2]; + if (error2 != nil) { + if (error != nil) { + *error = error2; + } + return; + } + + _fileSize = [fileAttr fileSize]; + NSFileHandle *file = nil; + NSData *data = nil; + if (_fileSize > 16 * 1024 * 1024) { + file = [NSFileHandle fileHandleForReadingFromURL:[NSURL fileURLWithPath:self.filePath] error:error]; + if (file == nil) { + if (error != nil) { + *error = [[NSError alloc] initWithDomain:self.filePath code:kQNFileError userInfo:[*error userInfo]]; } - - BOOL blHave = [[NSFileManager defaultManager] fileExistsAtPath:pathToWrite]; - if (!blHave) { - return; - } else { - [[NSFileManager defaultManager] removeItemAtPath:pathToWrite error:nil]; + return; + } + } else { + data = [NSData dataWithContentsOfFile:self.filePath options:NSDataReadingMappedIfSafe error:&error2]; + if (error2 != nil) { + if (error != nil) { + *error = error2; } - [assetReadLock lock]; - [assetReadLock unlockWithCondition:kAMASSETMETADATA_ALLFINISHED]; - }]; - - [assetReadLock lockWhenCondition:kAMASSETMETADATA_ALLFINISHED]; - [assetReadLock unlock]; - assetReadLock = nil; + return; + } } + + self.file = file; + self.assetData = data; } -- (NSData *)fetchDataFromAsset:(PHAssetResource *)videoResource error:(NSError **)err { - __block NSData *tmpData = [NSData data]; - __block NSError *innerError = *err; - - NSConditionLock *assetReadLock = [[NSConditionLock alloc] initWithCondition:kAMASSETMETADATA_PENDINGREADS]; - +- (void)exportAssert { + PHAssetResource *resource = self.phAssetResource; NSString *fileName = [NSString stringWithFormat:@"tempAsset-%f-%d.mov", [[NSDate date] timeIntervalSince1970], arc4random()%100000]; - NSString *pathToWrite = [NSTemporaryDirectory() stringByAppendingString:fileName]; - NSURL *localpath = [NSURL fileURLWithPath:pathToWrite]; PHAssetResourceRequestOptions *options = [PHAssetResourceRequestOptions new]; - options.networkAccessAllowed = YES; - [[PHAssetResourceManager defaultManager] writeDataForAssetResource:videoResource toFile:localpath options:options completionHandler:^(NSError *_Nullable error) { - if (error == nil) { - AVURLAsset *urlAsset = [AVURLAsset URLAssetWithURL:localpath options:nil]; - NSData *videoData = [NSData dataWithContentsOfURL:urlAsset.URL]; - tmpData = [NSData dataWithData:videoData]; - } else { - innerError = error; - } - BOOL blHave = [[NSFileManager defaultManager] fileExistsAtPath:pathToWrite]; - if (!blHave) { - return; + //不支持icloud上传 + options.networkAccessAllowed = NO; + + NSString *PATH_VIDEO_FILE = [NSTemporaryDirectory() stringByAppendingPathComponent:fileName]; + [[NSFileManager defaultManager] removeItemAtPath:PATH_VIDEO_FILE error:nil]; + + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + [[PHAssetResourceManager defaultManager] writeDataForAssetResource:resource toFile:[NSURL fileURLWithPath:PATH_VIDEO_FILE] options:options completionHandler:^(NSError *_Nullable error) { + if (error) { + self.filePath = nil; } else { - [[NSFileManager defaultManager] removeItemAtPath:pathToWrite error:nil]; + self.filePath = PATH_VIDEO_FILE; } - [assetReadLock lock]; - [assetReadLock unlockWithCondition:kAMASSETMETADATA_ALLFINISHED]; + dispatch_semaphore_signal(semaphore); }]; - - [assetReadLock lockWhenCondition:kAMASSETMETADATA_ALLFINISHED]; - [assetReadLock unlock]; - assetReadLock = nil; - - return tmpData; + dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); } @end