Don't force the version string to be beta 1 and remove a test button from the main...
[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 arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"Printers" ofType:@"plist"]];
43 [NSThread detachNewThreadSelector:@selector(versionCheck:) toTarget:self withObject:self];
44 }
45 return self;
46 }
47
48 /**
49 * Checks and sees if the current version is the most up-to-date one
50 */
51 - (void)versionCheck:(id)sender
52 {
53 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
54
55 NSMutableString *version = [NSMutableString stringWithString:[[[NSBundle mainBundle] infoDictionary] valueForKey:@"CFBundleShortVersionString"]];
56 [version replaceOccurrencesOfString:@" " withString:@"-" options:NSLiteralSearch range:NSMakeRange(0, [version length])];
57
58 NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://www.bluestatic.org/versioncheck.php?prod=printdrop&ver=%@", version]];
59 NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:10];
60 NSURLResponse *response;
61 NSData *result = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
62
63 if (result == nil)
64 {
65 goto end;
66 }
67
68 NSXMLDocument *xml = [[NSXMLDocument alloc] initWithData:result options:0 error:nil];
69 NSXMLNode *comp = [[xml rootElement] childAtIndex:0];
70 if ([[comp name] isEqualToString:@"update"])
71 {
72 [updateString setStringValue:[NSString stringWithFormat:[updateString stringValue], [comp stringValue]]];
73 [updateWindow makeKeyAndOrderFront:self];
74 }
75
76 end:
77 [pool release];
78 }
79
80
81 /**
82 * Opens the URL to the download page
83 */
84 - (IBAction)openUpdateInformation:(id)sender
85 {
86 [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://www.bluestatic.org/software/printdrop/"]];
87 }
88
89 /**
90 * Sets the status text
91 */
92 - (void)setStatus:(NSString *)msg isError:(BOOL)error
93 {
94 [status setStringValue:msg];
95 if (error)
96 {
97 [status setTextColor:[NSColor redColor]];
98 [progress stopAnimation:self];
99 }
100 else
101 {
102 [status setTextColor:[NSColor blackColor]];
103 }
104 }
105
106 /**
107 * Reads through a channel (in non-blocking) mode until there is no more left to read
108 * and then it returns. This calls sleep(1) so that the channel can have time to process.
109 * Be sure this is threaded otherwis the interface will stall.
110 */
111 - (void)readChannel:(LIBSSH2_CHANNEL *)channel
112 {
113 libssh2_channel_set_blocking(channel, 0);
114
115 char buf[1024];
116 int numbytes;
117 do
118 {
119 memset(&buf, '\0', sizeof(buf));
120 numbytes = libssh2_channel_read(channel, buf, sizeof(buf));
121 sleep(1);
122 #ifdef BLU_DEBUG
123 NSLog(@"buf: %s", buf);
124 #endif
125 } while (libssh2_poll_channel_read(channel, 0));
126 }
127
128 /**
129 * Sends an item to the printer
130 */
131 - (IBAction)print:(id)sender
132 {
133 [NSThread detachNewThreadSelector:@selector(uploadAndPrint:) toTarget:self withObject:sender];
134 }
135
136 /**
137 * Opens an SSH session, creates a SCP channel to upload the file, followed by a shell channel
138 * to queue up LPR
139 */
140 - (void)uploadAndPrint:(id)sender
141 {
142 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
143
144 [progress startAnimation:self];
145 [progress setHidden:NO];
146 [status setHidden:NO];
147
148 FILE *localFile;
149 struct stat fileInfo;
150
151 [self setStatus:@"Connecting to acs.bu.edu" isError:NO];
152 struct sockaddr_in sin;
153 int sock = socket(AF_INET, SOCK_STREAM, 0);
154 sin.sin_port = htons(22);
155 sin.sin_family = AF_INET;
156
157 struct hostent *host = gethostbyname("acs.bu.edu");
158 memcpy(&sin.sin_addr, host->h_addr_list[0], host->h_length);
159
160 if (stat([[dragRegion filePath] UTF8String], &fileInfo))
161 {
162 return [self setStatus:@"Invalid file selected" isError:YES];
163 }
164
165 if (connect(sock, (struct sockaddr *)(&sin), sizeof(struct sockaddr_in)) != 0)
166 {
167 return [self setStatus:@"Could not connect to acs.bu.edu" isError:YES];
168 }
169
170 LIBSSH2_SESSION *ssh = libssh2_session_init();
171 if (ssh == NULL)
172 {
173 return [self setStatus:@"Failed to initialize SSH context" isError:YES];
174 }
175
176 if (libssh2_session_startup(ssh, sock))
177 {
178 return [self setStatus:@"Could not tunnel over SSH" isError:YES];
179 }
180
181 if (libssh2_userauth_password(ssh, [[username stringValue] UTF8String], [[password stringValue] UTF8String]))
182 {
183 [self setStatus:@"Bad username/password" isError:YES];
184 goto shutdown;
185 }
186
187 LIBSSH2_CHANNEL *channel = libssh2_scp_send(ssh, "~/__bu_print_drop__.pdf", 0755, (unsigned long)fileInfo.st_size);
188 if (!channel)
189 {
190 [self setStatus:@"Unable to open upload SCP session" isError:YES];
191 goto shutdown;
192 }
193
194 [self setStatus:@"Uploading file..." isError:NO];
195
196 localFile = fopen([[dragRegion filePath] UTF8String], "r");
197 char buf[1024];
198 char *pbuf;
199 int numread, numwrote;
200 do
201 {
202 numread = fread(buf, 1, sizeof(buf), localFile);
203 if (numread <= 0)
204 {
205 break;
206 }
207
208 pbuf = buf;
209 do
210 {
211 numwrote = libssh2_channel_write(channel, pbuf, numread);
212 pbuf += numwrote;
213 numread -= numread;
214 } while (numwrote > 0);
215
216 } while(1);
217
218 [self setStatus:@"File uploaded!" isError:NO];
219
220 libssh2_channel_send_eof(channel);
221 libssh2_channel_wait_eof(channel);
222 libssh2_channel_wait_closed(channel);
223 libssh2_channel_free(channel);
224 channel = NULL;
225
226 channel = libssh2_channel_open_session(ssh);
227 if (!channel)
228 {
229 [self setStatus:@"Could not open SSH channel for printing" isError:YES];
230 goto shutdown;
231 }
232
233 if (libssh2_channel_request_pty(channel, "vanilla"))
234 {
235 [self setStatus:@"Could not open ANSI TTY" isError:YES];
236 goto shutdown;
237 }
238
239 if (libssh2_channel_shell(channel))
240 {
241 [self setStatus:@"Failed to open remote shell" isError:YES];
242 goto shutdown;
243 }
244
245 [self setStatus:@"Opened remote SSH shell" isError:NO];
246
247 // read the banner
248 [self readChannel:channel];
249
250 // send the job to lpr
251 char *cmd;
252 #ifndef BLU_DEBUG
253 NSString *printer = [[printersController selection] valueForKey:@"unixName"];
254 cmd = (char *)[[NSString stringWithFormat:@"lpr -m -P%@ __bu_print_drop__.pdf\r\n\0", printer] UTF8String];
255 #else
256 cmd = "touch abc.def\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 = "rm -f __bu_print_drop__.pdf\r\n\0";
263 libssh2_channel_write(channel, cmd, sizeof(char) * strlen(cmd));
264 [self readChannel:channel];
265
266 [self setStatus:@"Printed!" isError:NO];
267
268 libssh2_channel_send_eof(channel);
269 libssh2_channel_eof(channel);
270 libssh2_channel_close(channel);
271
272 shutdown:
273 if (channel)
274 {
275 //libssh2_channel_free(channel);
276 channel = NULL;
277 }
278 libssh2_session_disconnect(ssh, "Normal disconnect.");
279 libssh2_session_free(ssh);
280
281 close(sock);
282
283 [progress stopAnimation:self];
284
285 [pool release];
286 }
287
288 @end