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