/
DPSendFileOp.m
234 lines (196 loc) · 7.01 KB
/
DPSendFileOp.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
227
228
229
230
231
232
233
234
#import "DPSendFileOp.h"
#include <stdlib.h> /* system() */
@implementation DPSendFileOp
@synthesize delegate;
-(id)initWithPath:(NSString *)p name:(NSString *)n conf:(NSDictionary *)c {
self = [super init];
task = nil;
path = p;
name = n;
conf = c;
didInterruptTaskOnPurpose = NO;
fexmon = [[DSFileExistenceMonitor alloc] initWithPath:path checkInterval:1.0 delegate:self];
[g_opq addOperation:fexmon];
return self;
}
-(void)fileDidDisappear:(NSString *)path {
NSLog(@"[%@] cancelling because %@ seized to exist", self, name);
[self cancel];
}
- (void)cancel {
NSLog(@"[%@] cancelling (task=%@)", self, task);
if (task) {
#if DEBUG
NSLog(@"[%@] sending SIGINT to scp process %d", self, [task processIdentifier]);
#endif
didInterruptTaskOnPurpose = YES;
kill([task processIdentifier], SIGINT);
}
if (fexmon && [fexmon respondsToSelector:@selector(cancel)])
[fexmon cancel];
[super cancel];
}
- (int)executeRemoteShellCommand:(NSString *)cmd {
NSString *dstHost = [conf objectForKey:@"remoteHost"];
if (!dstHost || ![dstHost length]) {
// unlikely
NSLog(@"[%@] executeRemoteShellCommand: missing 'remoteHost' (or it's empty) in config", self);
return -1;
}
cmd = [cmd stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];
cmd = [NSString stringWithFormat:@"ssh -n '%@' -- \"%@\"", dstHost, cmd];
return system([cmd UTF8String]);
}
- (void)main {
NSString *dstHost, *dstPath, *dstPathFinal, *scpPath, *tempName;
NSArray *args;
NSError *error = nil;
int status;
scpPath = [[NSUserDefaults standardUserDefaults] stringForKey:@"scpPath"];
if (!scpPath || [[NSFileManager defaultManager] fileExistsAtPath:scpPath])
scpPath = @"/usr/bin/scp";
#if DEBUG
NSLog(@"[%@] starting with conf %@", self, conf);
#endif
if (!(dstHost = [conf objectForKey:@"remoteHost"])) {
#if DEBUG
NSLog(@"[%@] missing 'remoteHost' in config -- aborting", self);
#endif
error = [NSError droPubErrorWithDescription:@"missing 'remoteHost' in config"];
goto fail;
}
if ([dstHost length] < 1) {
#if DEBUG
NSLog(@"[%@] empty 'remoteHost' in config -- aborting", self);
#endif
error = [NSError droPubErrorWithDescription:@"empty 'remoteHost' in config"];
goto fail;
}
tempName = [@".dpupload_" stringByAppendingString:name];
if (!(dstPath = [conf objectForKey:@"remotePath"])) {
dstPath = tempName;
dstPathFinal = name;
}
else {
dstPathFinal = [dstPath stringByAppendingPathComponent:name];
dstPath = [dstPath stringByAppendingPathComponent:tempName];
}
args = [NSArray arrayWithObjects:
@"-o", @"ConnectTimeout=10",
@"-o", @"ServerAliveCountMax=30",
@"-o", @"ServerAliveInterval=30",
@"-pCB",
path,
[NSString stringWithFormat:@"%@:'%@'", dstHost, dstPath],
nil];
#if DEBUG
NSLog(@"[%@] sending %@ --> %@:%@", self, path, dstHost, dstPath);
NSLog(@"[%@] starting task: %@ %@", self, scpPath, [[args description] stringByReplacingOccurrencesOfString:@"\n" withString:@" "]);
#endif
// todo use popen or NSTask so we can send cancel signal to our child process
task = [[NSTask alloc] init];
[task setLaunchPath:scpPath];
[task setArguments:args];
NSPipe *pipe = [NSPipe pipe];
NSFileHandle* readHandle = [pipe fileHandleForReading];
[readHandle readInBackgroundAndNotify];
[task setStandardError:pipe];
NSMutableString *stderrStr = [NSMutableString string];
[task setCurrentDirectoryPath:path];
[task launch];
if (!task) {
NSLog(@"[%@] failed to start scp with arguments %@", self, args);
error = [NSError droPubErrorWithFormat:@"failed to launch %@ with arguments '%@'",
scpPath, [args componentsJoinedByString:@"' '"]];
goto fail;
}
// wait for scp to exit
#if DEBUG
NSLog(@"[%@] SCP %@ started with PID %d", self, task, [task processIdentifier]);
#endif
// read stderr until scp exists
while([task isRunning]) {
NSData *data = [readHandle availableData];
if (data && [data length])
[stderrStr appendString:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]];
}
status = [task terminationStatus];
task = nil;
// cancel file existence monitor
[fexmon cancel];
fexmon = nil;
// handle status
if (status != 0) {
if (didInterruptTaskOnPurpose) {
#if DEBUG
NSLog(@"[%@] aborted", self);
#endif
// try to remove remote temp file
NSString *cmd = [NSString stringWithFormat:@"rm -f %@", [dstPath shellArgumentRepresentation]];
if ([self executeRemoteShellCommand:cmd] != 0) {
NSLog(@"[%@] notice: failed to remove remote temp file %@", self, dstPath);
}
#if DEBUG
else {
NSLog(@"[%@] successfully removed remote tempfile %@ after abortion", self, dstPath);
}
#endif
// inform delegate
if (delegate && [delegate respondsToSelector:@selector(fileTransmission:didAbortForPath:)])
[delegate fileTransmission:self didAbortForPath:path];
#if DEBUG
else if (delegate)
NSLog(@"[%@] warn: delegate not responding to fileTransmission:didAbortForPath:");
#endif
}
else {
NSLog(@"[%@] failed with status %d", self, status);
if ([name rangeOfString:@"/"].length && [stderrStr rangeOfString:@"No such file or directory"].length) {
// oh, target directory need to be created. Let's try ssh mkdir -p:
NSString *rmkdir = [NSString stringWithFormat:@"mkdir -p %@", [[dstPath stringByDeletingLastPathComponent] shellArgumentRepresentation]];
if ([self executeRemoteShellCommand:rmkdir] == 0) {
NSLog(@"[%@] created remote directory %@:%@", self, dstHost, dstPath);
}
else {
NSLog(@"[%@] failed to create remote directory (%@) => !0", self, rmkdir);
}
}
error = [NSError droPubErrorWithDescription:[stderrStr stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] code:status];
goto fail;
}
}
else {
#if DEBUG
NSLog(@"[%@] done", self);
#endif
// try to move remote temp file
NSString *cmd = [NSString stringWithFormat:@"mv -f %@ %@", [dstPath shellArgumentRepresentation], [dstPathFinal shellArgumentRepresentation]];
if ([self executeRemoteShellCommand:cmd] != 0) {
NSLog(@"[%@] failed to move remote temp file %@ --> %@", self, dstPath, dstPathFinal);
error = [NSError droPubErrorWithFormat:@"failed to move remote temp file %@ --> %@", dstPath, dstPathFinal];
goto fail;
}
#if DEBUG
else {
NSLog(@"[%@] successfully moved remote %@ --> %@", self, dstPath, dstPathFinal);
}
#endif
// inform delegate
if (delegate && [delegate respondsToSelector:@selector(fileTransmission:didSucceedForPath:remoteURI:)])
[delegate fileTransmission:self didSucceedForPath:path remoteURI:[NSString stringWithFormat:@"%@:%@", dstHost, dstPath]];
#if DEBUG
else if (delegate)
NSLog(@"[%@] warn: delegate not responding to fileTransmission:didSucceedForPath:");
#endif
}
return;
fail:
// inform delegate
if (delegate && [delegate respondsToSelector:@selector(fileTransmission:didFailForPath:reason:)])
[delegate fileTransmission:self didFailForPath:path reason:error];
#if DEBUG
else if (delegate)
NSLog(@"[%@] warn: delegate not responding to fileTransmission:didSucceedForPath:");
#endif
}
@end