Add a help menu item to display the current print queue
[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 #include <WebKit/WebKit.h>
24 #include <unistd.h>
25
26 @interface AppController (Private)
27 - (void)setStatus:(NSString *)msg isError:(BOOL)error;
28
29 - (void)readChannel:(LIBSSH2_CHANNEL *)channel;
30 - (void)uploadAndPrint:(id)sender;
31
32 - (NSString *)getUploadSafeName:(NSString *)name;
33 @end
34
35
36 @implementation AppController
37
38 /**
39 * Set up the printer list
40 */
41 - (id)init
42 {
43 if (self = [super init])
44 {
45 printers = [[NSArray alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"Printers" ofType:@"plist"]];
46 }
47 return self;
48 }
49
50 /**
51 * Dealloc
52 */
53 - (void)dealloc
54 {
55 [printers release];
56 [super dealloc];
57 }
58
59 /**
60 * Sets the status text
61 */
62 - (void)setStatus:(NSString *)msg isError:(BOOL)error
63 {
64 [status setStringValue:msg];
65 if (error)
66 {
67 [status setTextColor:[NSColor redColor]];
68 [progress stopAnimation:self];
69 [goButton setEnabled:YES];
70 }
71 else
72 {
73 [status setTextColor:[NSColor blackColor]];
74 }
75 }
76
77 /**
78 * Awake from NIB
79 */
80 - (void)awakeFromNib
81 {
82 [helpWindow center];
83 NSString *helpPath = [[NSBundle mainBundle] pathForResource:@"Help" ofType:@"html"];
84 [[helpWebView mainFrame] loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:helpPath]]];
85
86 [window makeKeyAndOrderFront:self];
87
88 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
89 NSString *hash = [[[NSBundle mainBundle] infoDictionary] valueForKey:@"CFBundleVersion"];
90 if (![[defaults valueForKey:@"LastVersionHelpWindow"] isEqualToString:hash])
91 {
92 [helpWindow makeKeyAndOrderFront:self];
93 [defaults setObject:hash forKey:@"LastVersionHelpWindow"];
94 }
95 }
96
97 /**
98 * Reads through a channel (in non-blocking) mode until there is no more left to read
99 * and then it returns. This calls sleep(1) so that the channel can have time to process.
100 * Be sure this is threaded otherwis the interface will stall.
101 */
102 - (void)readChannel:(LIBSSH2_CHANNEL *)channel
103 {
104 libssh2_channel_set_blocking(channel, 0);
105
106 char buf[1024];
107 int numbytes;
108 do
109 {
110 memset(&buf, '\0', sizeof(buf));
111 numbytes = libssh2_channel_read(channel, buf, sizeof(buf));
112 sleep(1);
113 NSLog(@"SSH buffer: %s", buf);
114 } while (libssh2_poll_channel_read(channel, 0));
115 }
116
117 /**
118 * Sends an item to the printer
119 */
120 - (IBAction)print:(id)sender
121 {
122 [NSThread detachNewThreadSelector:@selector(uploadAndPrint:) toTarget:self withObject:sender];
123 }
124
125 /**
126 * Opens an SSH session, creates a SCP channel to upload the file, followed by a shell channel
127 * to queue up LPR
128 */
129 - (void)uploadAndPrint:(id)sender
130 {
131 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
132
133 [progress startAnimation:self];
134 [progress setHidden:NO];
135 [status setHidden:NO];
136 [goButton setEnabled:NO];
137
138 FILE *localFile;
139 struct stat fileInfo;
140 NSString *uploadName = [self getUploadSafeName:[dragRegion filePath]];
141 const char *fileName = [uploadName UTF8String];
142
143 [self setStatus:@"Connecting to acs.bu.edu" isError:NO];
144 struct sockaddr_in sin;
145 int sock = socket(AF_INET, SOCK_STREAM, 0);
146 sin.sin_port = htons(22);
147 sin.sin_family = AF_INET;
148
149 struct hostent *host = gethostbyname("acs.bu.edu");
150 memcpy(&sin.sin_addr, host->h_addr_list[0], host->h_length);
151
152 if (stat([[dragRegion filePath] UTF8String], &fileInfo))
153 {
154 return [self setStatus:@"Invalid file selected" isError:YES];
155 }
156
157 if (connect(sock, (struct sockaddr *)(&sin), sizeof(struct sockaddr_in)) != 0)
158 {
159 return [self setStatus:@"Could not connect to acs.bu.edu" isError:YES];
160 }
161
162 LIBSSH2_SESSION *ssh = libssh2_session_init();
163 if (ssh == NULL)
164 {
165 return [self setStatus:@"Failed to initialize SSH context" isError:YES];
166 }
167
168 if (libssh2_session_startup(ssh, sock))
169 {
170 return [self setStatus:@"Could not tunnel over SSH" isError:YES];
171 }
172
173 if (libssh2_userauth_password(ssh, [[username stringValue] UTF8String], [[password stringValue] UTF8String]))
174 {
175 [self setStatus:@"Bad username/password" isError:YES];
176 goto shutdown;
177 }
178
179 LIBSSH2_CHANNEL *channel = libssh2_scp_send(ssh, fileName, 0755, (unsigned long)fileInfo.st_size);
180 if (!channel)
181 {
182 [self setStatus:@"Unable to open upload SCP session" isError:YES];
183 goto shutdown;
184 }
185
186 [self setStatus:@"Uploading file..." isError:NO];
187
188 localFile = fopen([[dragRegion filePath] UTF8String], "r");
189 char buf[1024];
190 char *pbuf;
191 int numread, numwrote;
192 do
193 {
194 numread = fread(buf, 1, sizeof(buf), localFile);
195 if (numread <= 0)
196 {
197 break;
198 }
199
200 pbuf = buf;
201 do
202 {
203 numwrote = libssh2_channel_write(channel, pbuf, numread);
204 pbuf += numwrote;
205 numread -= numread;
206 } while (numwrote > 0);
207
208 } while(1);
209
210 [self setStatus:@"File uploaded!" isError:NO];
211
212 libssh2_channel_send_eof(channel);
213 libssh2_channel_wait_eof(channel);
214 libssh2_channel_wait_closed(channel);
215 libssh2_channel_free(channel);
216 channel = NULL;
217
218 channel = libssh2_channel_open_session(ssh);
219 if (!channel)
220 {
221 [self setStatus:@"Could not open SSH channel for printing" isError:YES];
222 goto shutdown;
223 }
224
225 if (libssh2_channel_request_pty(channel, "vanilla"))
226 {
227 [self setStatus:@"Could not open ANSI TTY" isError:YES];
228 goto shutdown;
229 }
230
231 if (libssh2_channel_shell(channel))
232 {
233 [self setStatus:@"Failed to open remote shell" isError:YES];
234 goto shutdown;
235 }
236
237 [self setStatus:@"Opened remote SSH shell" isError:NO];
238
239 // read the banner
240 [self readChannel:channel];
241
242 // ACS outage messages --> skip some more
243 char *delay = "q\r\n\0";
244 libssh2_channel_write(channel, delay, sizeof(char) * strlen(delay));
245 [self readChannel:channel];
246
247 // send the job to lpr
248 char *cmd;
249 #ifndef BLU_DEBUG
250 NSString *printer = [[printersController selection] valueForKey:@"unixName"];
251 cmd = (char *)[[NSString stringWithFormat:@"lpr -m -#%d -P%@ %s\r\n\0", [[copiesButtons selectedCell] tag], printer, fileName] UTF8String];
252 #else
253 if ([[copiesButtons selectedCell] tag] == 1)
254 cmd = "touch __PRINT_1__\r\n\0";
255 else
256 cmd = "touch __PRINT_2__\r\n\0";
257 #endif
258 libssh2_channel_write(channel, cmd, sizeof(char) * strlen(cmd));
259 [self readChannel:channel];
260
261 // remove our temp file
262 cmd = (char *)[[NSString stringWithFormat:@"rm -f %s\r\n\0", fileName] UTF8String];
263 libssh2_channel_write(channel, cmd, sizeof(char) * strlen(cmd));
264 [self readChannel:channel];
265
266 [self setStatus:[NSString stringWithFormat:@"Printed %@", uploadName] isError:NO];
267
268 [dragRegion clearFile];
269
270 libssh2_channel_send_eof(channel);
271 libssh2_channel_eof(channel);
272 libssh2_channel_close(channel);
273
274 shutdown:
275 if (channel)
276 {
277 //libssh2_channel_free(channel);
278 channel = NULL;
279 }
280 libssh2_session_disconnect(ssh, "Normal disconnect.");
281 libssh2_session_free(ssh);
282
283 close(sock);
284
285 [progress stopAnimation:self];
286
287 [pool release];
288 }
289
290 /**
291 * Returns an NSString that is safe for uploding onto the server
292 */
293 - (NSString *)getUploadSafeName:(NSString *)name
294 {
295 NSMutableString *safeName = [NSMutableString string];
296
297 name = [name lastPathComponent];
298 name = [name stringByDeletingPathExtension];
299
300 for (int i = 0, j = 0; i < [name length] && j < 20; i++)
301 {
302 char c = [name characterAtIndex:i];
303 if ((c >= 48 && c <= 57) || (c >= 65 && c <= 90) || (c >= 97 && 122) || c == 95)
304 {
305 [safeName appendFormat:@"%c", c];
306 j++;
307 }
308 else if (c == 32)
309 {
310 if (j > 0 && [safeName characterAtIndex:j - 1] != '-')
311 {
312 [safeName appendString:@"_"];
313 j++;
314 }
315 }
316 else if (c >= 38 && c <= 46)
317 {
318 if (j > 0 && [safeName characterAtIndex:j - 1] != '_')
319 {
320 [safeName appendString:@"-"];
321 j++;
322 }
323 }
324 }
325
326 srandomdev();
327 return [NSString stringWithFormat:@"~/%@.%d.pdf", safeName, random()];
328 }
329
330 /**
331 * Opens the print quota check page
332 */
333 - (IBAction)helpCheckPQuota:(id)sender
334 {
335 NSURL *url = [NSURL URLWithString:@"http://www.bu.edu/phpbin/myacs/"];
336 [[NSWorkspace sharedWorkspace] openURL:url];
337 }
338
339 /**
340 * Opens the link to the ACS help desk homepage
341 */
342 - (IBAction)helpACS:(id)sender
343 {
344 NSURL *url = [NSURL URLWithString:@"http://www.bu.edu/cc/"];
345 [[NSWorkspace sharedWorkspace] openURL:url];
346 }
347
348 /**
349 * Opens the link that displays the queue
350 */
351 - (IBAction)helpQueue:(id)sender
352 {
353 NSURL *url = [NSURL URLWithString:@"http://people.bu.edu/csg-oper/Resnet/"];
354 [[NSWorkspace sharedWorkspace] openURL:url];
355 }
356
357 @end