Statistics
| Branch: | Tag: | Revision:

chatsecureios / ChatSecure / Classes / Controllers / OTRMediaFileManager.m @ ce32510a

History | View | Annotate | Download (9.94 KB)

1
//
2
//  OTRMediaFileManager.m
3
//  ChatSecure
4
//
5
//  Created by David Chiles on 2/19/15.
6
//  Copyright (c) 2015 Chris Ballinger. All rights reserved.
7
//
8

    
9
#import "OTRMediaFileManager.h"
10
@import IOCipher;
11
#import "OTRMediaItem.h"
12
#import "OTRIncomingMessage.h"
13
#import "OTROutgoingMessage.h"
14
#import "OTRDatabaseManager.h"
15
#import "OTRConstants.h"
16

    
17
NSString *const kOTRRootMediaDirectory = @"media";
18

    
19
@interface OTRMediaFileManager () {
20
    void *IsOnInternalQueueKey;
21
}
22

    
23
@property (nonatomic) dispatch_queue_t concurrentQueue;
24

    
25
/** Uses dispatch_barrier_async. Will perform block asynchronously on the internalQueue, unless we're already on internalQueue */
26
- (void) performAsyncWrite:(dispatch_block_t)block;
27

    
28
/** Uses dispatch_sync. Will perform block synchronously on the internalQueue and block for result if called on another queue. */
29
- (void) performSyncRead:(dispatch_block_t)block;
30

    
31
@end
32

    
33
@implementation OTRMediaFileManager
34

    
35
- (instancetype)init
36
{
37
    if (self = [super init]) {
38
        // We use dispatch_barrier_async with a concurrent queue to allow for multiple-read single-write.
39
        _concurrentQueue = dispatch_queue_create(NSStringFromClass(self.class).UTF8String, DISPATCH_QUEUE_CONCURRENT);
40
        
41
        // For safe usage of dispatch_sync
42
        IsOnInternalQueueKey = &IsOnInternalQueueKey;
43
        void *nonNullUnusedPointer = (__bridge void *)self;
44
        dispatch_queue_set_specific(_concurrentQueue, IsOnInternalQueueKey, nonNullUnusedPointer, NULL);
45
        
46
    }
47
    return self;
48
}
49

    
50
#pragma - mark Public Methods
51

    
52
- (BOOL)setupWithPath:(NSString *)path password:(NSString *)password
53
{
54
    _ioCipher = [[IOCipher alloc] initWithPath:path password:password];
55
    return _ioCipher != nil;
56
}
57

    
58
- (void)copyDataFromFilePath:(NSString *)filePath
59
             toEncryptedPath:(NSString *)path
60
                  completion:(void (^)(BOOL success, NSError * _Nullable error))completion
61
             completionQueue:(nullable dispatch_queue_t)completionQueue
62
{
63
    if (!completionQueue) {
64
        completionQueue = dispatch_get_main_queue();
65
    }
66
    __weak typeof(self)weakSelf = self;
67
    [self performAsyncWrite:^{
68
        __strong typeof(weakSelf)strongSelf = weakSelf;
69
        
70
        NSError *error = nil;
71
        BOOL result = [strongSelf.ioCipher copyItemAtFileSystemPath:filePath toEncryptedPath:path error:&error];
72
        
73
        if (completion) {
74
            dispatch_async(completionQueue, ^{
75
                completion(result, error);
76
            });
77
        }
78
    }];
79
}
80

    
81
- (void)setData:(NSData *)data
82
        forItem:(OTRMediaItem *)mediaItem
83
  buddyUniqueId:(NSString *)buddyUniqueId
84
     completion:(void (^)(NSInteger bytesWritten, NSError * _Nullable error))completion
85
completionQueue:(nullable dispatch_queue_t)completionQueue {
86
    if (!completionQueue) {
87
        completionQueue = dispatch_get_main_queue();
88
    }
89
    [self performAsyncWrite:^{
90
        NSString *path = [[self class] pathForMediaItem:mediaItem buddyUniqueId:buddyUniqueId];
91
        if (![path length]) {
92
            NSError *error = [NSError errorWithDomain:kOTRErrorDomain code:150 userInfo:@{NSLocalizedDescriptionKey:@"Unable to create file path"}];
93
            dispatch_async(completionQueue, ^{
94
                completion(-1,error);
95
            });
96
            return;
97
        }
98
        
99
        BOOL fileExists = [self.ioCipher fileExistsAtPath:path isDirectory:NULL];
100
        
101
        if (fileExists) {
102
            NSError *error = nil;
103
            [self.ioCipher removeItemAtPath:path error:&error];
104
            if (error) {
105
                NSError *error = [NSError errorWithDomain:kOTRErrorDomain code:151 userInfo:@{NSLocalizedDescriptionKey:@"Unable to remove existing file"}];
106
                dispatch_async(completionQueue, ^{
107
                    completion(-1,error);
108
                });
109
                return;
110
            }
111
        }
112
        
113
        NSError *error = nil;
114
        BOOL created = [self.ioCipher createFileAtPath:path error:&error];
115
        if (!created) {
116
            NSError *error = [NSError errorWithDomain:kOTRErrorDomain code:152 userInfo:@{NSLocalizedDescriptionKey:@"Unable to create file"}];
117
            dispatch_async(completionQueue, ^{
118
                completion(-1,error);
119
            });
120
            return;
121
        }
122
        __block NSInteger written = [self.ioCipher writeDataToFileAtPath:path data:data offset:0 error:&error];
123
        
124
        dispatch_async(completionQueue, ^{
125
            completion(written, error);
126
        });
127

    
128
    }];
129
}
130

    
131
//#865
132
- (void)deleteDataForItem:(OTRMediaItem *)mediaItem
133
            buddyUniqueId:(NSString *)buddyUniqueId
134
               completion:(void (^)(BOOL success, NSError * _Nullable error))completion
135
          completionQueue:(nullable dispatch_queue_t)completionQueue {
136
    if (!completionQueue) {
137
        completionQueue = dispatch_get_main_queue();
138
    }
139
    [self performAsyncWrite:^{
140
        NSString *path = [[self class] pathForMediaItem:mediaItem buddyUniqueId:buddyUniqueId];
141
        if (![path length]) {
142
            NSError *error = [NSError errorWithDomain:kOTRErrorDomain code:150 userInfo:@{NSLocalizedDescriptionKey:@"Unable to create file path"}];
143
            if (completion) {
144
                dispatch_async(completionQueue, ^{
145
                    completion(NO, error);
146
                });
147
            }
148
            return;
149
        }
150
        
151
        BOOL fileExists = [self.ioCipher fileExistsAtPath:path isDirectory:NULL];
152
        
153
        if (fileExists) {
154
            NSError *error = nil;
155
            [self.ioCipher removeItemAtPath:path error:&error];
156
            if (error) {
157
                NSError *error = [NSError errorWithDomain:kOTRErrorDomain code:151 userInfo:@{NSLocalizedDescriptionKey:@"Unable to remove existing file"}];
158
                if (completion) {
159
                    dispatch_async(completionQueue, ^{
160
                        completion(NO, error);
161
                    });
162
                }
163
                return;
164
            }
165
        }
166
        
167
        if (completion) {
168
            dispatch_async(completionQueue, ^{
169
                completion(YES, nil);
170
            });
171
        }
172
    }];
173
}
174

    
175
/* Internal. If "length" is set, only return the length of the data, otherwise the data ifself */
176
- (nullable NSData*)dataForItem:(OTRMediaItem *)mediaItem
177
                  buddyUniqueId:(NSString *)buddyUniqueId
178
                          error:(NSError* __autoreleasing *)error
179
                         length:(NSNumber* __autoreleasing *)length {
180
    __block NSData *data = nil;
181
    __block NSNumber *dataLength = nil;
182
    [self performSyncRead:^{
183
        NSString *filePath = [[self class] pathForMediaItem:mediaItem buddyUniqueId:buddyUniqueId];
184
        if (!filePath) {
185
            if (error) {
186
                *error = [NSError errorWithDomain:kOTRErrorDomain code:150 userInfo:@{NSLocalizedDescriptionKey:@"Unable to create file path"}];
187
            }
188
            return;
189
        }
190
        BOOL fileExists = [self.ioCipher fileExistsAtPath:filePath isDirectory:nil];
191
        if (!fileExists) {
192
            if (error) {
193
                *error = [NSError errorWithDomain:kOTRErrorDomain code:151 userInfo:@{NSLocalizedDescriptionKey:@"File does not exist!"}];
194
            }
195
            return;
196
        }
197
        NSDictionary *fileAttributes = [self.ioCipher fileAttributesAtPath:filePath error:error];
198
        if (error && *error) {
199
            return;
200
        }
201
        if (length != nil) {
202
            dataLength = fileAttributes[NSFileSize];
203
        } else {
204
            NSNumber *length = fileAttributes[NSFileSize];
205
            data = [self.ioCipher readDataFromFileAtPath:filePath length:length.integerValue offset:0 error:error];
206
        }
207
    }];
208
    if (length != nil) {
209
        *length = dataLength;
210
    }
211
    return data;
212
}
213

    
214
- (nullable NSData*)dataForItem:(OTRMediaItem *)mediaItem
215
                  buddyUniqueId:(NSString *)buddyUniqueId
216
                          error:(NSError* __autoreleasing *)error {
217
    return [self dataForItem:mediaItem buddyUniqueId:buddyUniqueId error:error length:nil];
218
}
219

    
220
- (NSNumber *)dataLengthForItem:(OTRMediaItem *)mediaItem buddyUniqueId:(NSString *)buddyUniqueId error:(NSError * _Nullable __autoreleasing *)error {
221
    NSNumber *length = nil;
222
    [self dataForItem:mediaItem buddyUniqueId:buddyUniqueId error:error length:&length];
223
    return length;
224
}
225

    
226
#pragma - mark Class Methods
227

    
228
+ (OTRMediaFileManager*) shared {
229
    return [self sharedInstance];
230
}
231

    
232
+ (instancetype)sharedInstance
233
{
234
    static id sharedInstance = nil;
235
    static dispatch_once_t onceToken;
236
    dispatch_once(&onceToken, ^{
237
        sharedInstance = [[self alloc] init];
238
    });
239
    
240
    return sharedInstance;
241
}
242

    
243
+ (NSString *)pathForMediaItem:(OTRMediaItem *)mediaItem buddyUniqueId:(NSString *)buddyUniqueId
244
{
245
    return [self pathForMediaItem:mediaItem buddyUniqueId:buddyUniqueId withLeadingSlash:YES];
246
}
247

    
248
+ (NSString *)pathForMediaItem:(OTRMediaItem *)mediaItem buddyUniqueId:(NSString *)buddyUniqueId withLeadingSlash:(BOOL)includeLeadingSlash
249
{
250
    if ([buddyUniqueId length] && [mediaItem.uniqueId length] && [mediaItem.filename length]) {
251
        NSString *path = [NSString pathWithComponents:@[kOTRRootMediaDirectory,buddyUniqueId,mediaItem.uniqueId,mediaItem.filename]];
252
        if (includeLeadingSlash) {
253
            return [NSString stringWithFormat:@"/%@",path];
254
        }
255
        return path;
256
    }
257
    return nil;
258
}
259

    
260
#pragma mark Utility
261

    
262
/** Will perform block synchronously on the internalQueue and block for result if called on another queue. */
263
- (void) performSyncRead:(dispatch_block_t)block {
264
    NSParameterAssert(block != nil);
265
    if (!block) { return; }
266
    if (dispatch_get_specific(IsOnInternalQueueKey)) {
267
        block();
268
    } else {
269
        dispatch_sync(_concurrentQueue, block);
270
    }
271
}
272

    
273
/** Will perform block asynchronously on the internalQueue, unless we're already on internalQueue */
274
- (void) performAsyncWrite:(dispatch_block_t)block {
275
    NSParameterAssert(block != nil);
276
    if (!block) { return; }
277
    if (dispatch_get_specific(IsOnInternalQueueKey)) {
278
        block();
279
    } else {
280
        dispatch_barrier_async(_concurrentQueue, block);
281
    }
282
}
283

    
284
@end