Using polled reading and don't allow actual LPR-ing in debug mode
[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 }
44 return self;
45 }
46
47 /**
48 * Sets the status text
49 */
50 - (void)setStatus:(NSString *)msg isError:(BOOL)error
51 {
52 [status setStringValue:msg];
53 if (error)
54 {
55 [status setTextColor:[NSColor redColor]];
56 [progress stopAnimation:self];
57 }
58 else
59 {
60 [status setTextColor:[NSColor blackColor]];
61 }
62 }
63
64 /**
65 * Reads through a channel (in non-blocking) mode until there is no more left to read
66 * and then it returns. This calls sleep(1) so that the channel can have time to process.
67 * Be sure this is threaded otherwis the interface will stall.
68 */
69 - (void)readChannel:(LIBSSH2_CHANNEL *)channel
70 {
71 libssh2_channel_set_blocking(channel, 0);
72
73 char buf[1024];
74 int numbytes;
75 do
76 {
77 memset(&buf, '\0', sizeof(buf));
78 numbytes = libssh2_channel_read(channel, buf, sizeof(buf));
79 sleep(1);
80 #ifdef BLU_DEBUG
81 NSLog(@"buf: %s", buf);
82 #endif
83 } while (libssh2_poll_channel_read(channel, 0));
84 }
85
86 /**
87 * Sends an item to the printer
88 */
89 - (IBAction)print:(id)sender
90 {
91 [NSThread detachNewThreadSelector:@selector(uploadAndPrint:) toTarget:self withObject:sender];
92 }
93
94 /**
95 * Opens an SSH session, creates a SCP channel to upload the file, followed by a shell channel
96 * to queue up LPR
97 */
98 - (void)uploadAndPrint:(id)sender
99 {
100 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
101
102 [progress startAnimation:self];
103 [progress setHidden:NO];
104 [status setHidden:NO];
105
106 FILE *localFile;
107 struct stat fileInfo;
108
109 [self setStatus:@"Connecting to acs.bu.edu" isError:NO];
110 struct sockaddr_in sin;
111 int sock = socket(AF_INET, SOCK_STREAM, 0);
112 sin.sin_port = htons(22);
113 sin.sin_family = AF_INET;
114
115 struct hostent *host = gethostbyname("acs.bu.edu");
116 memcpy(&sin.sin_addr, host->h_addr_list[0], host->h_length);
117
118 if (stat([[dragRegion filePath] UTF8String], &fileInfo))
119 {
120 return [self setStatus:@"Invalid file selected" isError:YES];
121 }
122
123 if (connect(sock, (struct sockaddr *)(&sin), sizeof(struct sockaddr_in)) != 0)
124 {
125 return [self setStatus:@"Could not connect to acs.bu.edu" isError:YES];
126 }
127
128 LIBSSH2_SESSION *ssh = libssh2_session_init();
129 if (ssh == NULL)
130 {
131 return [self setStatus:@"Failed to initialize SSH context" isError:YES];
132 }
133
134 if (libssh2_session_startup(ssh, sock))
135 {
136 return [self setStatus:@"Could not tunnel over SSH" isError:YES];
137 }
138
139 if (libssh2_userauth_password(ssh, [[username stringValue] UTF8String], [[password stringValue] UTF8String]))
140 {
141 [self setStatus:@"Bad username/password" isError:YES];
142 goto shutdown;
143 }
144
145 LIBSSH2_CHANNEL *channel = libssh2_scp_send(ssh, "~/__bu_print_drop__.pdf", 0755, (unsigned long)fileInfo.st_size);
146 if (!channel)
147 {
148 [self setStatus:@"Unable to open upload SCP session" isError:YES];
149 goto shutdown;
150 }
151
152 [self setStatus:@"Uploading file..." isError:NO];
153
154 localFile = fopen([[dragRegion filePath] UTF8String], "r");
155 char buf[1024];
156 char *pbuf;
157 int numread, numwrote;
158 do
159 {
160 numread = fread(buf, 1, sizeof(buf), localFile);
161 if (numread <= 0)
162 {
163 break;
164 }
165
166 pbuf = buf;
167 do
168 {
169 numwrote = libssh2_channel_write(channel, pbuf, numread);
170 pbuf += numwrote;
171 numread -= numread;
172 } while (numwrote > 0);
173
174 } while(1);
175
176 [self setStatus:@"File uploaded!" isError:NO];
177
178 libssh2_channel_send_eof(channel);
179 libssh2_channel_wait_eof(channel);
180 libssh2_channel_wait_closed(channel);
181 libssh2_channel_free(channel);
182 channel = NULL;
183
184 channel = libssh2_channel_open_session(ssh);
185 if (!channel)
186 {
187 [self setStatus:@"Could not open SSH channel for printing" isError:YES];
188 goto shutdown;
189 }
190
191 if (libssh2_channel_request_pty(channel, "vanilla"))
192 {
193 [self setStatus:@"Could not open ANSI TTY" isError:YES];
194 goto shutdown;
195 }
196
197 if (libssh2_channel_shell(channel))
198 {
199 [self setStatus:@"Failed to open remote shell" isError:YES];
200 goto shutdown;
201 }
202
203 [self setStatus:@"Opened remote SSH shell" isError:NO];
204
205 // read the banner
206 [self readChannel:channel];
207
208 // send the job to lpr
209 char *cmd;
210 #ifndef BLU_DEBUG
211 NSString *printer = [[printersController selection] valueForKey:@"unixName"];
212 cmd = (char *)[[NSString stringWithFormat:@"lpr -m -P%@ __bu_print_drop__.pdf\r\n\0", printer] UTF8String];
213 #else
214 cmd = "touch abc.def\r\n\0";
215 #endif
216 libssh2_channel_write(channel, cmd, sizeof(char) * strlen(cmd));
217 [self readChannel:channel];
218
219 // remove our temp file
220 cmd = "rm -f __bu_print_drop__.pdf\r\n\0";
221 libssh2_channel_write(channel, cmd, sizeof(char) * strlen(cmd));
222 [self readChannel:channel];
223
224 [self setStatus:@"Printed!" isError:NO];
225
226 libssh2_channel_send_eof(channel);
227 libssh2_channel_eof(channel);
228 libssh2_channel_close(channel);
229
230 shutdown:
231 if (channel)
232 {
233 //libssh2_channel_free(channel);
234 channel = NULL;
235 }
236 libssh2_session_disconnect(ssh, "Normal disconnect.");
237 libssh2_session_free(ssh);
238
239 close(sock);
240
241 [progress stopAnimation:self];
242
243 [pool release];
244 }
245
246 @end