Skip to content

Commit eaed4ec

Browse files
Added PFFileStagingController to manage staging.
Previously, our file staging code was not centralized, wasn't very efficient, and most importantly made testing & migration difficult. Fixes half of #18.
1 parent feb57ad commit eaed4ec

File tree

9 files changed

+236
-74
lines changed

9 files changed

+236
-74
lines changed

Parse.xcodeproj/project.pbxproj

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,8 @@
801801
F50C66331B33A708001941A6 /* PFPushUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = F50C66311B33A708001941A6 /* PFPushUtilities.h */; };
802802
F50C66341B33A708001941A6 /* PFPushUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = F50C66321B33A708001941A6 /* PFPushUtilities.m */; };
803803
F50C667C1B34B231001941A6 /* PFPushUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = F50C66321B33A708001941A6 /* PFPushUtilities.m */; };
804+
F50E486E1B83ED270055094D /* PFFileStagingController.h in Headers */ = {isa = PBXBuildFile; fileRef = F50E486C1B83ED270055094D /* PFFileStagingController.h */; };
805+
F50E486F1B83ED270055094D /* PFFileStagingController.m in Sources */ = {isa = PBXBuildFile; fileRef = F50E486D1B83ED270055094D /* PFFileStagingController.m */; };
804806
F510509F1B6AA4CE00749060 /* ExtensionDataSharingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 814915E61B66D44500EFD14F /* ExtensionDataSharingTests.m */; };
805807
F51050A01B6AA4D100749060 /* ExtensionDataSharingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 814915E61B66D44500EFD14F /* ExtensionDataSharingTests.m */; };
806808
F51050A11B6AA4D600749060 /* ExtensionDataSharingMobileTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 814915E51B66D44500EFD14F /* ExtensionDataSharingMobileTests.m */; };
@@ -901,6 +903,7 @@
901903
F5C42CDB1B38761B00C720D8 /* PFObjectSubclassInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = F5C42CD81B38761B00C720D8 /* PFObjectSubclassInfo.h */; };
902904
F5C42CDC1B38761B00C720D8 /* PFObjectSubclassInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = F5C42CD91B38761B00C720D8 /* PFObjectSubclassInfo.m */; };
903905
F5C42CDD1B38761B00C720D8 /* PFObjectSubclassInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = F5C42CD91B38761B00C720D8 /* PFObjectSubclassInfo.m */; };
906+
F5C6B38B1B83F7A100690F3A /* PFFileStagingController.m in Sources */ = {isa = PBXBuildFile; fileRef = F50E486D1B83ED270055094D /* PFFileStagingController.m */; };
904907
F5C8F2C01B1F7E7800CD98E7 /* PFAsyncTaskQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = F5C8F2BF1B1F7E6B00CD98E7 /* PFAsyncTaskQueue.m */; };
905908
F5C8F2C11B1F7E7900CD98E7 /* PFAsyncTaskQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = F5C8F2BF1B1F7E6B00CD98E7 /* PFAsyncTaskQueue.m */; };
906909
F5E381311B68832000A3B9F2 /* URLSessionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F5556A141B66F36000410837 /* URLSessionTests.m */; };
@@ -1488,6 +1491,8 @@
14881491
E9E81E8316EEF93E001D034F /* PFSubclassing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFSubclassing.h; sourceTree = "<group>"; };
14891492
F50C66311B33A708001941A6 /* PFPushUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFPushUtilities.h; sourceTree = "<group>"; };
14901493
F50C66321B33A708001941A6 /* PFPushUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PFPushUtilities.m; sourceTree = "<group>"; };
1494+
F50E486C1B83ED270055094D /* PFFileStagingController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFFileStagingController.h; sourceTree = "<group>"; };
1495+
F50E486D1B83ED270055094D /* PFFileStagingController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PFFileStagingController.m; sourceTree = "<group>"; };
14911496
F51534F61B571E9100C49F56 /* PFACLPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFACLPrivate.h; sourceTree = "<group>"; };
14921497
F51534F81B571E9100C49F56 /* PFACLState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFACLState.h; sourceTree = "<group>"; };
14931498
F51534F91B571E9100C49F56 /* PFACLState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PFACLState.m; sourceTree = "<group>"; };
@@ -2892,6 +2897,8 @@
28922897
children = (
28932898
81EB595C1AF46434001EA1FC /* PFFileController.h */,
28942899
81EB595D1AF46434001EA1FC /* PFFileController.m */,
2900+
F50E486C1B83ED270055094D /* PFFileStagingController.h */,
2901+
F50E486D1B83ED270055094D /* PFFileStagingController.m */,
28952902
);
28962903
path = Controller;
28972904
sourceTree = "<group>";
@@ -3154,6 +3161,7 @@
31543161
F5B0B2DF1B449EEF00F3EBC4 /* PFCommandCache_Private.h in Headers */,
31553162
F5B0B2E01B449EEF00F3EBC4 /* PFCommandResult.h in Headers */,
31563163
812B02961B5DE3EE003846EE /* PFURLSession.h in Headers */,
3164+
F50E486E1B83ED270055094D /* PFFileStagingController.h in Headers */,
31573165
8166FC731B50376D003841A2 /* PFObjectController.h in Headers */,
31583166
F5B0B2EB1B449EEF00F3EBC4 /* PFAlertView.h in Headers */,
31593167
8119C9971A76E28F0085B516 /* PFNetworkCommand.h in Headers */,
@@ -4031,6 +4039,7 @@
40314039
814881471B795C63008763BF /* PFKeyValueCache.m in Sources */,
40324040
81C3825119CCAD2C0066284A /* PFNetworkActivityIndicatorManager.m in Sources */,
40334041
81C3824819CCAD2C0066284A /* PFObject.m in Sources */,
4042+
F50E486F1B83ED270055094D /* PFFileStagingController.m in Sources */,
40344043
F51D06351B792CF10044539E /* PFSQLiteDatabaseController.m in Sources */,
40354044
815960A31ABCA3B30069EBCC /* PFFileManager.m in Sources */,
40364045
81CD66561B4DA5A70042FC0B /* PFCurrentInstallationController.m in Sources */,
@@ -4198,6 +4207,7 @@
41984207
81EBF3461B33E7DE00991947 /* PFPushChannelsController.m in Sources */,
41994208
9701108A1630B45800AB761E /* PFRole.m in Sources */,
42004209
9701108C1630B45800AB761E /* PFUser.m in Sources */,
4210+
F5C6B38B1B83F7A100690F3A /* PFFileStagingController.m in Sources */,
42014211
81E7A2281B6042BD006CB680 /* PFObjectFileCodingLogic.m in Sources */,
42024212
8166FCEB1B504083003841A2 /* PFPushManager.m in Sources */,
42034213
819A4B0B1A67330200D01241 /* PFHash.m in Sources */,

Parse/Internal/File/Controller/PFFileController.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@
1616
@class BFCancellationToken;
1717
@class BFTask;
1818
@class PFFileState;
19+
@class PFFileStagingController;
1920

2021
@interface PFFileController : NSObject
2122

2223
@property (nonatomic, weak, readonly) id<PFCommandRunnerProvider, PFFileManagerProvider> dataSource;
2324

25+
@property (nonatomic, strong, readonly) PFFileStagingController *fileStagingController;
26+
2427
@property (nonatomic, copy, readonly) NSString *cacheFilesDirectoryPath;
25-
@property (nonatomic, copy, readonly) NSString *stagedFilesDirectoryPath;
2628

2729
///--------------------------------------
2830
/// @name Init

Parse/Internal/File/Controller/PFFileController.m

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@
1717
#import "PFCommandResult.h"
1818
#import "PFCommandRunning.h"
1919
#import "PFFileManager.h"
20+
#import "PFFileStagingController.h"
2021
#import "PFFileState.h"
2122
#import "PFHash.h"
2223
#import "PFMacros.h"
2324
#import "PFRESTFileCommand.h"
2425

2526
static NSString *const PFFileControllerCacheDirectoryName_ = @"PFFileCache";
26-
static NSString *const PFFileControllerStagingDirectoryName_ = @"PFFileStaging";
2727

2828
@interface PFFileController () {
2929
NSMutableDictionary *_downloadTasks; // { "urlString" : BFTask }
@@ -48,6 +48,7 @@ - (instancetype)initWithDataSource:(id<PFCommandRunnerProvider, PFFileManagerPro
4848
if (!self) return nil;
4949

5050
_dataSource = dataSource;
51+
_fileStagingController = [PFFileStagingController controllerWithDataSource:dataSource];
5152

5253
_downloadTasks = [NSMutableDictionary dictionary];
5354
_downloadProgressBlocks = [NSMutableDictionary dictionary];
@@ -244,15 +245,4 @@ - (BFTask *)clearFileCacheAsync {
244245
return [PFFileManager removeDirectoryContentsAsyncAtPath:path];
245246
}
246247

247-
///--------------------------------------
248-
#pragma mark - Staging
249-
///--------------------------------------
250-
251-
- (NSString *)stagedFilesDirectoryPath {
252-
NSString *folderPath = [self.dataSource.fileManager parseLocalSandboxDataDirectoryPath];
253-
NSString *path = [folderPath stringByAppendingPathComponent:PFFileControllerStagingDirectoryName_];
254-
[[PFFileManager createDirectoryIfNeededAsyncAtPath:path] waitForResult:nil withMainThreadWarning:NO];
255-
return path;
256-
}
257-
258248
@end
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/**
2+
* Copyright (c) 2015-present, Parse, LLC.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
#import <Foundation/Foundation.h>
11+
12+
#import <Bolts/BFTask.h>
13+
14+
NS_ASSUME_NONNULL_BEGIN
15+
16+
@protocol PFFileManagerProvider;
17+
18+
@interface PFFileStagingController : NSObject
19+
20+
@property (nonatomic, weak, readonly) id<PFFileManagerProvider> dataSource;
21+
22+
@property (nonatomic, copy, readonly) NSString *stagedFilesDirectoryPath;
23+
24+
///--------------------------------------
25+
/// @name Init
26+
///--------------------------------------
27+
28+
- (instancetype)init NS_UNAVAILABLE;
29+
- (instancetype)initWithDataSource:(id<PFFileManagerProvider>)dataSource NS_DESIGNATED_INITIALIZER;
30+
31+
+ (instancetype)controllerWithDataSource:(id<PFFileManagerProvider>)dataSource;
32+
33+
///--------------------------------------
34+
/// @name Staging
35+
///--------------------------------------
36+
37+
/*!
38+
Moves a file from the specified path to the staging directory based off of the name and unique ID passed in.
39+
40+
@param filePath The source path to stage
41+
@param name The name of the file to stage
42+
@param uniqueId A unique ID for this file to be used when differentiating between files with the same name.
43+
44+
@return A task, which yields the path of the staged file on disk.
45+
*/
46+
- (BFTask *)stageFileAsyncAtPath:(NSString *)filePath name:(NSString *)name uniqueId:(uint64_t)uniqueId;
47+
48+
/*!
49+
Creates a file from the specified data and places it into the staging directory based off of the name and unique
50+
ID passed in.
51+
52+
@param fileData The data to stage
53+
@param name The name of the file to stage
54+
@param uniqueId The unique ID for this file to be used when differentiating between files with the same name.
55+
56+
@return A task, which yields the path of the staged file on disk.
57+
*/
58+
- (BFTask *)stageFileAsyncWithData:(NSData *)fileData name:(NSString *)name uniqueId:(uint64_t)uniqueId;
59+
60+
/*!
61+
Get the staged directory path for a file with the specified name and unique ID.
62+
63+
@param name The name of the staged file
64+
@param uniqueId The unique ID of the staged file
65+
66+
@return The path in the staged directory folder which contains the contents of the requested file.
67+
*/
68+
- (NSString *)stagedFilePathForFileWithName:(NSString *)name uniqueId:(uint64_t)uniqueId;
69+
70+
@end
71+
72+
NS_ASSUME_NONNULL_END
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/**
2+
* Copyright (c) 2015-present, Parse, LLC.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
#import "PFFileStagingController.h"
11+
12+
#import "BFTask+Private.h"
13+
#import "PFAssert.h"
14+
#import "PFAsyncTaskQueue.h"
15+
#import "PFDataProvider.h"
16+
#import "PFFileManager.h"
17+
#import "PFLogging.h"
18+
19+
static NSString *const PFFileStagingControllerDirectoryName_ = @"PFFileStaging";
20+
21+
@implementation PFFileStagingController {
22+
PFAsyncTaskQueue *_taskQueue;
23+
}
24+
25+
///--------------------------------------
26+
#pragma mark - Init
27+
///--------------------------------------
28+
29+
- (instancetype)init {
30+
PFNotDesignatedInitializer();
31+
}
32+
33+
- (instancetype)initWithDataSource:(id<PFFileManagerProvider>)dataSource {
34+
self = [super init];
35+
if (!self) return nil;
36+
37+
_dataSource = dataSource;
38+
_taskQueue = [PFAsyncTaskQueue taskQueue];
39+
40+
[self _clearStagedFilesAsync];
41+
42+
return self;
43+
}
44+
45+
+ (instancetype)controllerWithDataSource:(id<PFFileManagerProvider>)dataSource {
46+
return [[self alloc] initWithDataSource:dataSource];
47+
}
48+
49+
///--------------------------------------
50+
#pragma mark - Properties
51+
///--------------------------------------
52+
53+
- (NSString *)stagedFilesDirectoryPath {
54+
NSString *folderPath = [self.dataSource.fileManager parseLocalSandboxDataDirectoryPath];
55+
return [folderPath stringByAppendingPathComponent:PFFileStagingControllerDirectoryName_];
56+
}
57+
58+
///--------------------------------------
59+
#pragma mark - Staging
60+
///--------------------------------------
61+
62+
- (BFTask *)stageFileAsyncAtPath:(NSString *)filePath name:(NSString *)name uniqueId:(uint64_t)uniqueId {
63+
return [_taskQueue enqueue:^id(BFTask *task) {
64+
return [[PFFileManager createDirectoryIfNeededAsyncAtPath:[self stagedFilesDirectoryPath]] continueWithBlock:^id(BFTask *task) {
65+
NSString *destinationPath = [self stagedFilePathForFileWithName:name uniqueId:uniqueId];
66+
return [[PFFileManager copyItemAsyncAtPath:filePath toPath:destinationPath] continueWithSuccessResult:destinationPath];
67+
}];
68+
}];
69+
}
70+
71+
- (BFTask *)stageFileAsyncWithData:(NSData *)fileData name:(NSString *)name uniqueId:(uint64_t)uniqueId {
72+
return [_taskQueue enqueue:^id(BFTask *task) {
73+
return [[PFFileManager createDirectoryIfNeededAsyncAtPath:[self stagedFilesDirectoryPath]] continueWithBlock:^id(BFTask *task) {
74+
NSString *destinationPath = [self stagedFilePathForFileWithName:name uniqueId:uniqueId];
75+
return [[PFFileManager writeDataAsync:fileData toFile:destinationPath] continueWithSuccessResult:destinationPath];
76+
}];
77+
}];
78+
}
79+
80+
- (NSString *)stagedFilePathForFileWithName:(NSString *)name uniqueId:(uint64_t)uniqueId {
81+
NSString *fileName = [NSString stringWithFormat:@"%llX_%@", uniqueId, name];
82+
return [[self stagedFilesDirectoryPath] stringByAppendingPathComponent:fileName];
83+
}
84+
85+
///--------------------------------------
86+
#pragma mark - Clearing
87+
///--------------------------------------
88+
89+
- (BFTask *)_clearStagedFilesAsync {
90+
return [_taskQueue enqueue:^id(BFTask *task) {
91+
NSString *stagedFilesDirectoryPath = [self stagedFilesDirectoryPath];
92+
return [PFFileManager removeItemAtPathAsync:stagedFilesDirectoryPath];
93+
}];
94+
}
95+
96+
@end

Parse/PFFile.m

Lines changed: 34 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#import "PFCoreManager.h"
2020
#import "PFFileController.h"
2121
#import "PFFileManager.h"
22+
#import "PFFileStagingController.h"
2223
#import "PFInternalUtils.h"
2324
#import "PFMacros.h"
2425
#import "PFMutableFileState.h"
@@ -80,16 +81,8 @@ + (instancetype)fileWithName:(NSString *)name contentsAtPath:(NSString *)path er
8081
PFParameterAssert(length <= PFFileMaxFileSize, @"PFFile cannot be larger than %lli bytes", PFFileMaxFileSize);
8182

8283
PFFile *file = [self fileWithName:name url:nil];
83-
if (file) {
84-
// Copy the file write away, since we can construct staged file path only from a PFFile.
85-
NSError *copyError = nil;
86-
[fileManager copyItemAtPath:path toPath:file.stagedFilePath error:&copyError];
87-
if (copyError) {
88-
if (error) {
89-
*error = copyError;
90-
}
91-
return nil;
92-
}
84+
if (![file _stageWithPath:path error:error]) {
85+
return nil;
9386
}
9487
return file;
9588
}
@@ -111,17 +104,7 @@ + (instancetype)fileWithName:(NSString *)name
111104
@"PFFile cannot be larger than %llu bytes", PFFileMaxFileSize);
112105

113106
PFFile *file = [[self alloc] initWithName:name urlString:nil mimeType:contentType];
114-
115-
// Save the file write away, since we can construct staged file path only from a PFFile.
116-
NSError *writeError = nil;
117-
[[PFFileManager writeDataAsync:data toFile:file.stagedFilePath]
118-
waitForResult:&writeError
119-
withMainThreadWarning:NO];
120-
121-
if (writeError) {
122-
if (error) {
123-
*error = writeError;
124-
}
107+
if (![file _stageWithData:data error:error]) {
125108
return nil;
126109
}
127110
return file;
@@ -427,6 +410,36 @@ - (NSInputStream *)_cachedDataStream {
427410
return [NSInputStream inputStreamWithFileAtPath:filePath];
428411
}
429412

413+
///--------------------------------------
414+
#pragma mark - Staging
415+
///--------------------------------------
416+
417+
- (BOOL)_stageWithData:(NSData *)data error:(NSError **)error {
418+
__block BOOL result = NO;
419+
[self _performDataAccessBlock:^{
420+
_stagedFilePath = [[[[self class] fileController].fileStagingController stageFileAsyncWithData:data
421+
name:self.state.name
422+
uniqueId:(uintptr_t)self]
423+
waitForResult:error withMainThreadWarning:NO];
424+
425+
result = (_stagedFilePath != nil);
426+
}];
427+
return result;
428+
}
429+
430+
- (BOOL)_stageWithPath:(NSString *)path error:(NSError **)error {
431+
__block BOOL result = NO;
432+
[self _performDataAccessBlock:^{
433+
_stagedFilePath = [[[[self class] fileController].fileStagingController stageFileAsyncAtPath:path
434+
name:self.state.name
435+
uniqueId:(uintptr_t)self]
436+
waitForResult:error withMainThreadWarning:NO];
437+
438+
result = (_stagedFilePath != nil);
439+
}];
440+
return result;
441+
}
442+
430443
#pragma mark Data Access
431444

432445
- (NSString *)name {
@@ -469,22 +482,6 @@ - (PFFileState *)_fileState {
469482
return state;
470483
}
471484

472-
- (NSString *)stagedFilePath {
473-
// Construct a path in PFFile instead of PFFileController, because we need a pointer to PFFile itself.
474-
__block NSString *path = nil;
475-
@weakify(self);
476-
[self _performDataAccessBlock:^{
477-
@strongify(self);
478-
if (!_stagedFilePath) {
479-
NSString *filename = [NSString stringWithFormat:@"%p_%@", self, self.state.name];
480-
NSString *stagedDirectoryPath = [[self class] fileController].stagedFilesDirectoryPath;
481-
_stagedFilePath = [stagedDirectoryPath stringByAppendingPathComponent:filename];
482-
}
483-
path = _stagedFilePath;
484-
}];
485-
return path;
486-
}
487-
488485
#pragma mark Progress
489486

490487
- (void)_performProgressBlockAsync:(PFProgressBlock)block withProgress:(int)progress {

0 commit comments

Comments
 (0)