We want to store printers as an ivar, so the array needs to be retained
[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
23 @interface AppController (Private)
24
25 - (void)setStatus:(NSString *)msg isError:(BOOL)error;
26
27 - (void)readChannel:(LIBSSH2_CHANNEL *)channel;
28 - (void)uploadAndPrint:(id)sender;
29
30 @end
31
32
33 @implementation AppController
34
35 /**
36 * Set up the printer list
37 */
38 - (id)init
39 {
40 if (self = [super init])
41 {
42 printers = [[NSArray alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"Printers" ofType:@"plist"]];
43 [NSThread detachNewThreadSelector:@selector(versionCheck:) toTarget:self withObject:self];
44 }
45 return self;
46 }
47
48 /**
49 * Dealloc
50 */
51 - (void)dealloc
52 {
53 [printers release];
54 [super dealloc];
55 }
56
57 /**
58 * Checks and sees if the current version is the most up-to-date one
59 */
60 - (void)versionCheck:(id)sender
61 {
62 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
63
64 NSMutableString *version = [NSMutableString stringWithString:[[[NSBundle mainBundle] infoDictionary] valueForKey:@"CFBundleShortVersionString"]];
65 [version replaceOccurrencesOfString:@" " withString:@"-" options:NSLiteralSearch range:NSMakeRange(0, [version length])];
66
67 NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://www.bluestatic.org/versioncheck.php?prod=printdrop&ver=%@", version]];
68 NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:10];
69 NSURLResponse *response;
70 NSData *result = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
71
72 if (result == nil)
73 {
74 goto end;
75 }
76
77 NSXMLDocument *xml = [[NSXMLDocument alloc] initWithData:result options:0 error:nil];
78 NSXMLNode *comp = [[xml rootElement] childAtIndex:0];
79 if ([[comp name] isEqualToString:@"update"])
80 {
81 [updateString setStringValue:[NSString stringWithFormat:[updateString stringValue], [comp stringValue]]];
82 [updateWindow makeKeyAndOrderFront:self];
83 }
84
85 end:
86 [pool release];
87 }
88
89
90 /**
91 * Opens the URL to the download page
92 */
93 - (IBAction)openUpdateInformation:(id)sender
94 {
95 [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://www.bluestatic.org/software/printdrop/"]];
96 }
97
98 /**
99 * Sets the status text
100 */
101 - (void)setStatus:(NSString *)msg isError:(BOOL)error
102 {
103 [status setStringValue:msg];
104 if (error)
105 {
106 [status setTextColor:[NSColor redColor]];
107 [progress stopAnimation:self];
108 }
109 else
110 {
111 [status setTextColor:[NSColor blackColor]];
112 }
113 }
114
115 /**
116 * Reads through a channel (in non-blocking) mode until there is no more left to read
117 * and then it returns. This calls sleep(1) so that the channel can have time to process.
118 * Be sure this is threaded otherwis the interface will stall.
119 */
120 - (void)readChannel:(LIBSSH2_CHANNEL *)channel
121 {
122 libssh2_channel_set_blocking(channel, 0);
123
124 char buf[1024];
125 int numbytes;
126 do
127 {
128 memset(&buf, '\0', sizeof(buf));
129 numbytes = libssh2_channel_read(channel, buf, sizeof(buf));
130 sleep(1);
131 //#ifdef BLU_DEBUG
132 NSLog(@"buf: %s", buf);
133 //#endif
134 } while (libssh2_poll_channel_read(channel, 0));
135 }
136
137 /**
138 * Sends an item to the printer
139 */
140 - (IBAction)print:(id)sender
141 {
142 [NSThread detachNewThreadSelector:@selector(uploadAndPrint:) toTarget:self withObject:sender];
143 }
144
145 /**
146 * Opens an SSH session, creates a SCP channel to upload the file, followed by a shell channel
147 * to queue up LPR
148 */
149 - (void)uploadAndPrint:(id)sender
150 {
151 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
152
153 [progress startAnimation:self];
154 [progress setHidden:NO];
155 [status setHidden:NO];
156
157 FILE *localFile;
158 struct stat fileInfo;
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, "~/__bu_print_drop__.pdf", 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 // f!cking ACS 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%@ __bu_print_drop__.pdf\r\n\0", printer] UTF8String];
269 #else
270 cmd = "touch abc.def\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 = "rm -f __bu_print_drop__.pdf\r\n\0";
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 @end