-
Notifications
You must be signed in to change notification settings - Fork 21
/
MYAction.m
226 lines (184 loc) · 6.6 KB
/
MYAction.m
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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
//
// MYAction.m
// MYUtilities
//
// Created by Jens Alfke on 8/28/15.
// Copyright © 2015 Jens Alfke. All rights reserved.
//
#import "MYAction.h"
#import "MYErrorUtils.h"
@implementation MYAction
{
NSMutableArray *_performs, *_backOuts, *_cleanUps;
NSUInteger _nextStep; // next step to perform
}
@synthesize error=_error, failedStep=_failedStep;
static MYActionBlock sNullAction;
+ (void) initialize {
if (self == [MYAction class]) {
sNullAction = ^BOOL(NSError** error) { return YES; };
}
}
- (instancetype) init {
self = [super init];
if (self) {
_performs = [NSMutableArray new];
_backOuts = [NSMutableArray new];
_cleanUps = [NSMutableArray new];
_failedStep = NSNotFound;
}
return self;
}
- (instancetype) initWithPerform: (MYActionBlock)perform
backOut: (MYActionBlock)backOut
cleanUp: (MYActionBlock)cleanUp
{
self = [self init];
if (self) {
[self addPerform: perform backOut: backOut cleanUp: cleanUp];
}
return self;
}
- (void) addPerform: (MYActionBlock)perform
backOut: (MYActionBlock)backOut
cleanUp: (MYActionBlock)cleanUp
{
[_performs addObject: (perform ?: sNullAction)];
[_backOuts addObject: (backOut ?: sNullAction)];
[_cleanUps addObject: (cleanUp ?: sNullAction)];
}
- (void) addPerform: (MYActionBlock)perform
backOutOrCleanUp: (MYActionBlock)backOutOrCleanUp
{
[self addPerform: perform backOut: backOutOrCleanUp cleanUp: backOutOrCleanUp];
}
- (void) addAction: (id<MYAction>)action {
Assert(action);
if ([action isKindOfClass: [MYAction class]]) {
MYAction* a = action;
[_performs addObjectsFromArray: a->_performs];
[_backOuts addObjectsFromArray: a->_backOuts];
[_cleanUps addObjectsFromArray: a->_cleanUps];
} else {
[self addPerform: ^BOOL(NSError** error) { return [action perform: error]; }
backOut: ^BOOL(NSError** error) { return [action backOut: error]; }
cleanUp: ^BOOL(NSError** error) { return [action cleanUp: error]; }];
}
}
- (BOOL) perform: (NSError**)outError {
Assert(_nextStep == 0, @"Actions have already been run");
NSError* error = nil;
_failedStep = NSNotFound;
for (; _nextStep < _performs.count; ++_nextStep) {
error = [self doActionFromArray: _performs];
if (error) {
_failedStep = _nextStep;
if (_nextStep > 0)
[self backOut: NULL]; // back out the steps that already completed
if (outError) *outError = error;
return NO;
}
}
return YES;
}
- (BOOL) backOut: (NSError**)outError {
Assert(_nextStep > 0, @"Actions have not been run");
NSError* backOutError = nil;
while (_nextStep-- > 0) {
backOutError = [self doActionFromArray: _backOuts];
if (backOutError) {
Warn(@"MYAction: Error backing out step #%d: %@", (int)_nextStep, backOutError);
if (outError) *outError = backOutError;
return NO;
}
}
return YES;
}
- (BOOL) cleanUp: (NSError**)outError {
Assert(_nextStep == _performs.count, @"Actions did not all run");
NSError* cleanUpError = nil;
while (_nextStep-- > 0) {
cleanUpError = [self doActionFromArray: _cleanUps];
if (cleanUpError) {
Warn(@"MYAction: Error cleaning up step #%d: %@", (int)_nextStep, cleanUpError);
if (outError) *outError = cleanUpError;
return NO;
}
}
return YES;
}
- (BOOL) run: (NSError**)outError {
NSError* error;
if ([self perform: &error]) {
[self cleanUp: NULL];
_error = nil;
return YES;
} else {
// (perform: has already backed out whatever it did)
_error = error;
if (outError) *outError = error;
return NO;
}
}
// subroutine that calls an action block from either _performs, _backOuts or _cleanUps.
- (NSError*) doActionFromArray: (NSArray*)actionArray {
NSError* error;
@try {
if (((MYActionBlock)actionArray[_nextStep])(&error))
error = nil;
} @catch (NSException *exception) {
Warn(@"MYAction: Exception raised by step #%d: %@", (int)_nextStep, exception);
error = [NSError errorWithDomain: @"MYAction" code: 1
userInfo: @{@"NSException": exception}];
}
return error;
}
#pragma mark - FILE ACTIONS:
static BOOL isFileNotFoundError( NSError* error ) {
NSString* domain = error.domain;
NSInteger code = error.code;
return ($equal(domain, NSPOSIXErrorDomain) && code == ENOENT)
|| ($equal(domain, NSCocoaErrorDomain) && (code == NSFileNoSuchFileError ||
code == NSFileReadNoSuchFileError));
}
+ (instancetype) deleteFile: (NSString*)path {
Assert(path);
NSString* tempPath = [NSTemporaryDirectory() stringByAppendingString: [NSUUID new].UUIDString];
NSFileManager* fmgr = [NSFileManager defaultManager];
__block BOOL exists;
return [[self alloc] initWithPerform: ^BOOL(NSError** outError) {
NSError* error;
exists = [fmgr moveItemAtPath: path toPath: tempPath error: &error];
if (exists || isFileNotFoundError(error))
return YES;
if (outError) *outError = error;
return NO;
} backOut: ^BOOL(NSError** outError) {
return !exists || [fmgr moveItemAtPath: tempPath toPath: path error: outError];
} cleanUp: ^BOOL(NSError** outError) {
return !exists || [fmgr removeItemAtPath: tempPath error: outError];
}];
}
+ (instancetype) moveFile: (NSString*)srcPath toEmptyPath: (NSString*)dstPath {
Assert(srcPath && dstPath);
NSFileManager* fmgr = [NSFileManager defaultManager];
return [[self alloc] initWithPerform: ^BOOL(NSError** outError) {
return [fmgr moveItemAtPath: srcPath toPath: dstPath error: outError];
} backOut: ^BOOL(NSError** outError) {
return [fmgr moveItemAtPath: dstPath toPath: srcPath error: outError];
} cleanUp: nil];
}
+ (instancetype) moveFile: (NSString*)srcPath toPath: (NSString*)dstPath {
MYAction* seq = [self new];
[seq addAction: [self deleteFile: dstPath]];
[seq addAction: [self moveFile: srcPath toEmptyPath: dstPath]];
return seq;
}
+ (instancetype) cleanUpTemporaryFile: (NSString*)path {
Assert(path);
MYActionBlock deleteIt = ^BOOL(NSError** outError) {
return [[NSFileManager defaultManager] removeItemAtPath: path error: outError];
};
return [[self alloc] initWithPerform: nil backOut: deleteIt cleanUp: deleteIt];
}
@end