summaryrefslogtreecommitdiff
path: root/SmartDeviceLink/SDLUploadFileOperation.m
blob: 65910ab5f5f0f0521d611b820f0fa67737d2341a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
//
//  SDLUploadFileOperation.m
//  SmartDeviceLink-iOS
//
//  Created by Joel Fischer on 5/11/16.
//  Copyright © 2016 smartdevicelink. All rights reserved.
//

#import "SDLUploadFileOperation.h"

#import "SDLConnectionManagerType.h"
#import "SDLFile.h"
#import "SDLFileWrapper.h"
#import "SDLGlobals.h"
#import "SDLPutFile.h"
#import "SDLPutFileResponse.h"
#import "SDLRPCResponse.h"


NS_ASSUME_NONNULL_BEGIN

#pragma mark - SDLUploadFileOperation

@interface SDLUploadFileOperation ()

@property (strong, nonatomic) SDLFileWrapper *fileWrapper;
@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager;

@end


@implementation SDLUploadFileOperation {
    BOOL executing;
    BOOL finished;
}

- (instancetype)initWithFile:(SDLFileWrapper *)file connectionManager:(id<SDLConnectionManagerType>)connectionManager {
    self = [super init];
    if (!self) {
        return nil;
    }

    executing = NO;
    finished = NO;

    _fileWrapper = file;
    _connectionManager = connectionManager;

    return self;
}

- (void)start {
    [super start];

    [self sdl_sendPutFiles:[self.class sdl_splitFile:self.fileWrapper.file mtuSize:[SDLGlobals globals].maxMTUSize] withCompletion:self.fileWrapper.completionHandler];
}

- (void)sdl_sendPutFiles:(NSArray<SDLPutFile *> *)putFiles withCompletion:(SDLFileManagerUploadCompletionHandler)completion {
    __block NSError *streamError = nil;
    __block NSUInteger bytesAvailable = 0;
    __block NSInteger highestCorrelationIDReceived = -1;

    dispatch_group_t putFileGroup = dispatch_group_create();
    dispatch_group_enter(putFileGroup);

    // When the putfiles all complete, run this block
    __weak typeof(self) weakself = self;
    dispatch_group_notify(putFileGroup, dispatch_get_main_queue(), ^{
        typeof(weakself) strongself = weakself;
        if (streamError != nil || strongself.isCancelled) {
            completion(NO, bytesAvailable, streamError);
        } else {
            completion(YES, bytesAvailable, nil);
        }

        [weakself finishOperation];
    });

    for (SDLPutFile *putFile in putFiles) {
        dispatch_group_enter(putFileGroup);
        __weak typeof(self) weakself = self;
        [self.connectionManager sendManagerRequest:putFile
                               withResponseHandler:^(__kindof SDLRPCRequest *_Nullable request, __kindof SDLRPCResponse *_Nullable response, NSError *_Nullable error) {
                                   typeof(weakself) strongself = weakself;
                                   // TODO: Is this the right way to handle this case? Should we just abort everything in the future? Should we be deleting what we sent? Should we have an automatic retry strategy based on what the error was?
                                   if (strongself.isCancelled) {
                                       dispatch_group_leave(putFileGroup);
                                       BLOCK_RETURN;
                                   }

                                   // If we encounted an error, abort in the future and call the completion handler
                                   if (error != nil || response == nil || ![response.success boolValue] || strongself.isCancelled) {
                                       [strongself cancel];
                                       streamError = error;

                                       dispatch_group_leave(putFileGroup);
                                       BLOCK_RETURN;
                                   }

                                   // If we haven't encounted an error
                                   SDLPutFileResponse *putFileResponse = (SDLPutFileResponse *)response;

                                   // We need to do this to make sure our bytesAvailable is accurate
                                   if ([request.correlationID integerValue] > highestCorrelationIDReceived) {
                                       highestCorrelationIDReceived = [request.correlationID integerValue];
                                       bytesAvailable = [putFileResponse.spaceAvailable unsignedIntegerValue];
                                   }

                                   dispatch_group_leave(putFileGroup);
                               }];
    }

    dispatch_group_leave(putFileGroup);
}

+ (NSArray<SDLPutFile *> *)sdl_splitFile:(SDLFile *)file mtuSize:(NSUInteger)mtuSize {
    NSData *fileData = [file.data copy];
    NSUInteger currentOffset = 0;
    NSMutableArray<SDLPutFile *> *putFiles = [NSMutableArray array];

    // http://stackoverflow.com/a/503201 Make sure we get the exact number of packets we need
    for (int i = 0; i < (((fileData.length - 1) / mtuSize) + 1); i++) {
        SDLPutFile *putFile = [[SDLPutFile alloc] initWithFileName:file.name fileType:file.fileType persistentFile:file.isPersistent];
        putFile.offset = @(currentOffset);

        // Set the length putfile based on the offset
        if (currentOffset == 0) {
            // If the offset is 0, the putfile expects to have the full file length within it
            putFile.length = @(fileData.length);
        } else if ((fileData.length - currentOffset) < mtuSize) {
            // The file length remaining is less than our total MTU size, so use the file length remaining
            putFile.length = @(fileData.length - currentOffset);
        } else {
            // The file length remaining is greater than our total MTU size, and the offset is not zero, we will fill the packet with our max MTU size
            putFile.length = @(mtuSize);
        }

        // Place the data and set the new offset
        if (currentOffset == 0 && (mtuSize < [putFile.length unsignedIntegerValue])) {
            putFile.bulkData = [fileData subdataWithRange:NSMakeRange(currentOffset, mtuSize)];
            currentOffset = mtuSize;
        } else {
            putFile.bulkData = [fileData subdataWithRange:NSMakeRange(currentOffset, [putFile.length unsignedIntegerValue])];
            currentOffset = [putFile.length unsignedIntegerValue];
        }

        [putFiles addObject:putFile];
    }

    return putFiles;
}


#pragma mark Property Overrides

- (nullable NSString *)name {
    return self.fileWrapper.file.name;
}

- (NSOperationQueuePriority)queuePriority {
    return NSOperationQueuePriorityNormal;
}

@end

NS_ASSUME_NONNULL_END