Performing a Batch Download
This example downloads objects from an OBS bucket to a local directory in batches. You can list objects whose names contain the specified prefix and download them concurrently based on the specified concurrency. The files are saved to the local directory based on the original key structure. This method is suitable for scenarios such as batch export and offline backup.
#import <Foundation/Foundation.h>
#import <OBS/OBS.h>
#pragma mark - Result Model
@interface BatchDownloadResult : NSObject
@property (nonatomic, copy) NSString *objectKey;
@property (nonatomic, copy) NSString *localPath;
@property (nonatomic, assign) BOOL success;
@property (nonatomic, strong) NSError *error;
@property (nonatomic, copy) NSString *httpStatusCode;
@end
@implementation BatchDownloadResult
@end
#pragma mark - Batch Downloader
@interface BatchDownloader : NSObject
@property (nonatomic, strong) OBSClient *client;
@property (nonatomic, copy) NSString *bucketName;
@property (nonatomic, assign) NSInteger concurrentCount;
@property (nonatomic, strong) NSMutableArray<BatchDownloadResult *> *results;
@property (nonatomic, strong) dispatch_queue_t resultQueue;
- (instancetype)initWithClient:(OBSClient *)client
bucketName:(NSString *)bucketName
concurrentCount:(NSInteger)concurrentCount;
- (void)downloadObjects:(NSArray<NSString *> *)objectKeys
toDirectory:(NSString *)directoryPath
waitUntilDone:(BOOL)wait;
@end
@implementation BatchDownloader
- (instancetype)initWithClient:(OBSClient *)client
bucketName:(NSString *)bucketName
concurrentCount:(NSInteger)concurrentCount {
self = [super init];
if (self) {
_client = client;
_bucketName = bucketName;
_concurrentCount = MIN(concurrentCount > 0 ? concurrentCount : 3, 10);
_results = [NSMutableArray array];
_resultQueue = dispatch_queue_create("com.obs.batchdownload.result", DISPATCH_QUEUE_SERIAL);
}
return self;
}
- (void)downloadObjects:(NSArray<NSString *> *)objectKeys
toDirectory:(NSString *)directoryPath
waitUntilDone:(BOOL)wait {
dispatch_group_t group = dispatch_group_create();
dispatch_semaphore_t semaphore = dispatch_semaphore_create(self.concurrentCount);
dispatch_queue_t queue = dispatch_queue_create("com.obs.batchdownload", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"[BatchDownload] Starting %lu downloads, concurrency: %ld",
(unsigned long)objectKeys.count, (long)self.concurrentCount);
for (NSString *objectKey in objectKeys) {
dispatch_group_enter(group);
dispatch_async(queue, ^{
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 300 * NSEC_PER_SEC);
if (dispatch_semaphore_wait(semaphore, timeout) != 0) {
NSLog(@"[BatchDownload] semaphore wait timeout, skip object: %@", objectKey);
dispatch_group_leave(group);
return;
}
[self downloadObject:objectKey toDirectory:directoryPath completion:^{
dispatch_semaphore_signal(semaphore);
dispatch_group_leave(group);
}];
});
}
if (wait) {
dispatch_time_t groupTimeout = dispatch_time(DISPATCH_TIME_NOW, 300 * NSEC_PER_SEC);
if (dispatch_group_wait(group, groupTimeout) != 0) {
NSLog(@"[BatchDownload] group wait timeout, some downloads may not finish");
}
[self printSummary];
}
}
- (void)downloadObject:(NSString *)objectKey
toDirectory:(NSString *)directoryPath
completion:(void (^)(void))completion {
NSError *dirError; [[NSFileManager defaultManager] createDirectoryAtPath:directoryPath
withIntermediateDirectories:YES
attributes:nil
error:&dirError]; if (dirError) { NSLog(@"Failed to create the directory: %@", dirError); }
NSString *localPath = [directoryPath stringByAppendingPathComponent:objectKey];
dirError = nil; [[NSFileManager defaultManager] createDirectoryAtPath:[localPath stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:&dirError]; if (dirError) { NSLog(@"Failed to create the directory: %@", dirError); }
OBSGetObjectToFileRequest *request = [[OBSGetObjectToFileRequest alloc] initWithBucketName:self.bucketName
objectKey:objectKey
downloadFilePath:localPath];
request.downloadProgressBlock = ^(int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite) {
float progress = totalBytesExpectedToWrite > 0 ? MIN((float)totalBytesWritten * 100.0f / (float)totalBytesExpectedToWrite, 100.0f) : 0;
NSLog(@"[Download] %@ - %0.1f%% (%lld / %lld bytes)", objectKey, progress, totalBytesWritten, totalBytesExpectedToWrite);
};
// Note: The getObject operation does not check whether the object exists. If the object does not exist, the server returns 404.
__weak typeof(self) weakSelf = self; __strong typeof(weakSelf) strongSelf = weakSelf; if (!strongSelf) return; [self.client getObject:request completionHandler:^(OBSGetObjectResponse *response, NSError *error) {
BatchDownloadResult *result = [[BatchDownloadResult alloc] init];
result.objectKey = objectKey;
result.localPath = localPath;
result.error = error;
result.success = (error == nil);
// Note: Record the HTTP status code. If the value of statusCode is nil, use a hyphen (-) as the placeholder.
result.httpStatusCode = response.statusCode ?: @"-";
if (error) {
// Note: If the download fails, it is recommended that an error message be displayed or a retry mechanism be provided.
NSLog(@"[Download] FAILED - %@ : %@", objectKey, error.localizedDescription);
} else {
NSLog(@"[Download] SUCCESS - %@ -> %@", objectKey, localPath);
}
dispatch_sync(strongSelf.resultQueue, ^{ [strongSelf.results addObject:result]; });
if (completion) completion();
}];
}
- (void)printSummary {
NSInteger success = 0, failure = 0;
for (BatchDownloadResult *r in self.results) {
if (r.success) success++;
else failure++;
}
NSLog(@"\n========== Batch Download Summary ==========");
NSLog(@"Total: %lu | Success: %ld | Failed: %ld", (unsigned long)self.results.count, (long)success, (long)failure);
if (failure > 0) {
NSLog(@"Failed items:");
for (BatchDownloadResult *r in self.results) {
if (!r.success) {
NSLog(@" - %@ : %@", r.objectKey, r.error.localizedDescription);
}
}
}
NSLog(@"============================================\n");
}
@end
#pragma mark - Entry Point
int main(int argc, const char * argv[]) {
@autoreleasepool {
// === Configuration ===
NSString *endPoint = @"https://your-endpoint";
// Hard-coded or plaintext AK and SK are risky. For security purposes, encrypt your AK and SK before storing them in the configuration file or environment variables. In this example, the AK and SK are stored in environment variables. Before running the code in this example, configure environment variables AccessKeyID and SecretAccessKey.
// Obtain an AK/SK pair on the management console. For details, see https://support.huaweicloud.com/intl/en-us/usermanual-ca/ca_01_0003.html.
char* ak_env = getenv("AccessKeyID");
char* sk_env = getenv("SecretAccessKey");
if (ak_env == NULL || sk_env == NULL) {
NSLog(@"Error: AccessKeyID or SecretAccessKey environment variable not set");
return 1;
}
NSString *AK = [NSString stringWithUTF8String:ak_env];
NSString *SK = [NSString stringWithUTF8String:sk_env];
NSString *bucketName = @"your-bucket-name";
NSInteger concurrentCount = 3; // Adjust as needed
// Objects to download
NSArray<NSString *> *objectKeys = @[
@"folder/file1.txt",
@"folder/file2.jpg",
@"folder/file3.pdf",
@"folder/file4.mp4",
@"folder/file5.zip"
];
// Local download directory
NSString *downloadDir = [NSTemporaryDirectory() stringByAppendingPathComponent:@"BatchDownload"];
// === Initialize OBS Client ===
OBSStaticCredentialProvider *credentialProvider = [[OBSStaticCredentialProvider alloc] initWithAccessKey:AK secretKey:SK];
OBSServiceConfiguration *conf = [[OBSServiceConfiguration alloc] initWithURLString:endPoint credentialProvider:credentialProvider];
// Optionally increase concurrent download limit
conf.maxConcurrentDownloadRequestCount = concurrentCount;
OBSClient *client = [[OBSClient alloc] initWithConfiguration:conf];
// === Batch Download ===
BatchDownloader *downloader = [[BatchDownloader alloc] initWithClient:client
bucketName:bucketName
concurrentCount:concurrentCount];
// Note: Before downloading objects, check whether the local disk space is sufficient.
[downloader downloadObjects:objectKeys toDirectory:downloadDir waitUntilDone:YES];
}
return 0;
} What is your overall rating for this page?
Thank you very much for your feedback. We will continue working to improve the documentation.See the reply and handling status in My Cloud VOC.
For any further questions, feel free to contact us through the chatbot.
Chatbot