Use the original file name instead of the __bu_print_drop__.pdf
[printdrop.git] / Source / AppController.m
1 /*
2 * PrintDrop
3 * Copyright (c) 2008, Blue Static <http://www.bluestatic.org>
4 *
5 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6 * General Public License as published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
10 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along with this program; if not,
14 * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17 #import "AppController.h"
18 #import <libssh2.h>
19 #import <sys/socket.h>
20 #import <arpa/inet.h>
21 #include <netdb.h>
22 #include <stdlib.h>
23
24 @interface AppController (Private)
25 - (void)setStatus:(NSString *)msg isError:(BOOL)error;
26
27 - (void)readChannel:(LIBSSH2_CHANNEL *)channel;
28 - (void)uploadAndPrint:(id)sender;
29
30 - (NSString *)getUploadSafeName:(NSString *)name;
31 @end
32
33
34 @implementation AppController
35
36 /**
37 * Set up the printer list
38 */
39 - (id)init
40 {
41 if (self = [super init])
42 {
43 printers = [[NSArray alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"Printers" ofType:@"plist"]];
44 [NSThread detachNewThreadSelector:@selector(versionCheck:) toTarget:self withObject:self];
45 }
46 return self;
47 }
48
49 /**
50 * Dealloc
51 */
52 - (void)dealloc
53 {
54 [printers release];
55 [super dealloc];
56 }
57
58 /**
59 * Checks and sees if the current version is the most up-to-date one
60 */
61 - (void)versionCheck:(id)sender
62 {
63 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
64
65 NSMutableString *version = [NSMutableString stringWithString:[[[NSBundle mainBundle] infoDictionary] valueForKey:@"CFBundleShortVersionString"]];
66 [version replaceOccurrencesOfString:@" " withString:@"-" options:NSLiteralSearch range:NSMakeRange(0, [version length])];
67
68 NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://www.bluestatic.org/versioncheck.php?prod=printdrop&ver=%@", version]];
69 NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:10];
70 NSURLResponse *response;
71 NSData *result = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
72
73 if (result == nil)
74 {
75 goto end;
76 }
77
78 NSXMLDocument *xml = [[NSXMLDocument alloc] initWithData:result options:0 error:nil];
79 NSXMLNode *comp = [[xml rootElement] childAtIndex:0];
80 if ([[comp name] isEqualToString:@"update"])
81 {
82 [updateString setStringValue:[NSString stringWithFormat:[updateString stringValue], [comp stringValue]]];
83 [updateWindow makeKeyAndOrderFront:self];
84 }
85
86 end:
87 [pool release];
88 }
89
90
91 /**
92 * Opens the URL to the download page
93 */
94 - (IBAction)openUpdateInformation:(id)sender
95 {
96 [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://www.bluestatic.org/software/printdrop/"]];
97 }
98
99 /**
100 * Sets the status text
101 */
102 - (void)setStatus:(NSString *)msg isError:(BOOL)error
103 {
104 [status setStringValue:msg];
105 if (error)
106 {
107 [status setTextColor:[NSColor redColor]];
108 [progress stopAnimation:self];
109 }
110 else
111 {
112 [status setTextColor:[NSColor blackColor]];
113 }
114 }
115
116 /**
117 * Reads through a channel (in non-blocking) mode until there is no more left to read
118 * and then it returns. This calls sleep(1) so that the channel can have time to process.
119 * Be sure this is threaded otherwis the interface will stall.
120 */
121 - (void)readChannel:(LIBSSH2_CHANNEL *)channel
122 {
123 libssh2_channel_set_blocking(channel, 0);
124
125 char buf[1024];
126 int numbytes;
127 do
128 {
129 memset(&buf, '\0', sizeof(buf));
130 numbytes = libssh2_channel_read(channel, buf, sizeof(buf));
131 sleep(1);
132 NSLog(@"SSH buffer: %s", buf);
133 } while (libssh2_poll_channel_read(channel, 0));
134 }
135
136 /**
137 * Sends an item to the printer
138 */
139 - (IBAction)print:(id)sender
140 {
141 [NSThread detachNewThreadSelector:@selector(uploadAndPrint:) toTarget:self withObject:sender];
142 }
143
144 /**
145 * Opens an SSH session, creates a SCP channel to upload the file, followed by a shell channel
146 * to queue up LPR
147 */
148 - (void)uploadAndPrint:(id)sender
149 {
150 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
151
152 [progress startAnimation:self];
153 [progress setHidden:NO];
154 [status setHidden:NO];
155
156 FILE *localFile;
157 struct stat fileInfo;
158 const char *fileName = [[self getUploadSafeName:[dragRegion filePath]] UTF8String];
159
160 [self setStatus:@"Connecting to acs.bu.edu" isError:NO];
161 struct sockaddr_in sin;
162 int sock = socket(AF_INET, SOCK_STREAM, 0);
163 sin.sin_port = htons(22);
164 sin.sin_family = AF_INET;
165
166 struct hostent *host = gethostbyname("acs.bu.edu");
167 memcpy(&sin.sin_addr, host->h_addr_list[0], host->h_length);
168
169 if (stat([[dragRegion filePath] UTF8String], &fileInfo))
170 {
171 return [self setStatus:@"Invalid file selected" isError:YES];
172 }
173
174 if (connect(sock, (struct sockaddr *)(&sin), sizeof(struct sockaddr_in)) != 0)
175 {
176 return [self setStatus:@"Could not connect to acs.bu.edu" isError:YES];
177 }
178
179 LIBSSH2_SESSION *ssh = libssh2_session_init();
180 if (ssh == NULL)
181 {
182 return [self setStatus:@"Failed to initialize SSH context" isError:YES];
183 }
184
185 if (libssh2_session_startup(ssh, sock))
186 {
187 return [self setStatus:@"Could not tunnel over SSH" isError:YES];
188 }
189
190 if (libssh2_userauth_password(ssh, [[username stringValue] UTF8String], [[password stringValue] UTF8String]))
191 {
192 [self setStatus:@"Bad username/password" isError:YES];
193 goto shutdown;
194 }
195
196 LIBSSH2_CHANNEL *channel = libssh2_scp_send(ssh, fileName, 0755, (unsigned long)fileInfo.st_size);
197 if (!channel)
198 {
199 [self setStatus:@"Unable to open upload SCP session" isError:YES];
200 goto shutdown;
201 }
202
203 [self setStatus:@"Uploading file..." isError:NO];
204
205 localFile = fopen([[dragRegion filePath] UTF8String], "r");
206 char buf[1024];
207 char *pbuf;
208 int numread, numwrote;
209 do
210 {
211 numread = fread(buf, 1, sizeof(buf), localFile);
212 if (numread <= 0)
213 {
214 break;
215 }
216
217 pbuf = buf;
218 do
219 {
220 numwrote = libssh2_channel_write(channel, pbuf, numread);
221 pbuf += numwrote;
222 numread -= numread;
223 } while (numwrote > 0);
224
225 } while(1);
226
227 [self setStatus:@"File uploaded!" isError:NO];
228
229 libssh2_channel_send_eof(channel);
230 libssh2_channel_wait_eof(channel);
231 libssh2_channel_wait_closed(channel);
232 libssh2_channel_free(channel);
233 channel = NULL;
234
235 channel = libssh2_channel_open_session(ssh);
236 if (!channel)
237 {
238 [self setStatus:@"Could not open SSH channel for printing" isError:YES];
239 goto shutdown;
240 }
241
242 if (libssh2_channel_request_pty(channel, "vanilla"))
243 {
244 [self setStatus:@"Could not open ANSI TTY" isError:YES];
245 goto shutdown;
246 }
247
248 if (libssh2_channel_shell(channel))
249 {
250 [self setStatus:@"Failed to open remote shell" isError:YES];
251 goto shutdown;
252 }
253
254 [self setStatus:@"Opened remote SSH shell" isError:NO];
255
256 // read the banner
257 [self readChannel:channel];
258
259 // ACS outage messages --> skip some more
260 char *delay = "q\r\n\0";
261 libssh2_channel_write(channel, delay, sizeof(char) * strlen(delay));
262 [self readChannel:channel];
263
264 // send the job to lpr
265 char *cmd;
266 #ifndef BLU_DEBUG
267 NSString *printer = [[printersController selection] valueForKey:@"unixName"];
268 cmd = (char *)[[NSString stringWithFormat:@"lpr -m -P%@ %s\r\n\0", printer, fileName] UTF8String];
269 #else
270 cmd = "touch __PRINT__\r\n\0";
271 #endif
272 libssh2_channel_write(channel, cmd, sizeof(char) * strlen(cmd));
273 [self readChannel:channel];
274
275 // remove our temp file
276 cmd = (char *)[[NSString stringWithFormat:@"rm -f %s\r\n\0", fileName] UTF8String];
277 libssh2_channel_write(channel, cmd, sizeof(char) * strlen(cmd));
278 [self readChannel:channel];
279
280 [self setStatus:@"Printed!" isError:NO];
281
282 libssh2_channel_send_eof(channel);
283 libssh2_channel_eof(channel);
284 libssh2_channel_close(channel);
285
286 shutdown:
287 if (channel)
288 {
289 //libssh2_channel_free(channel);
290 channel = NULL;
291 }
292 libssh2_session_disconnect(ssh, "Normal disconnect.");
293 libssh2_session_free(ssh);
294
295 close(sock);
296
297 [progress stopAnimation:self];
298
299 [pool release];
300 }
301
302 /**
303 * Returns an NSString that is safe for uploding onto the server
304 */
305 - (NSString *)getUploadSafeName:(NSString *)name
306 {
307 NSMutableString *n = [NSMutableString stringWithString:name];
308 [n replaceOccurrencesOfString:@" " withString:@"" options:NSBackwardsSearch range:NSMakeRange(0, [name length])];
309 name = (NSString *)n;
310 name = [name lastPathComponent];
311 name = [name stringByDeletingPathExtension];
312 if ([name length] > 20)
313 name = [name substringToIndex:20];
314
315 srandomdev();
316 return [NSString stringWithFormat:@"~/%@.%d.pdf", name, random()];
317 }
318
319 @end