]>
Commit | Line | Data |
---|---|---|
184c5221 JW |
1 | /* Uploader.java |
2 | * Back-end upload logic for Dumload. | |
3 | * | |
4 | * This program is free software: you can redistribute it and/or modify it | |
5 | * under the terms of the GNU General Public License, version 3, as | |
6 | * published by the Free Software Foundation. | |
7 | * | |
8 | * This program is distributed in the hope that it will be useful, | |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
11 | * GNU General Public License for more details. | |
12 | * | |
13 | * You should have received a copy of the GNU General Public License | |
14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
15 | */ | |
16 | ||
0763e16d JW |
17 | package com.joshuawise.dumload; |
18 | ||
19 | import java.io.InputStream; | |
035767ac | 20 | import java.io.OutputStream; |
0763e16d | 21 | |
42aa7dfd | 22 | import android.app.Notification; |
23 | import android.app.NotificationManager; | |
0763e16d | 24 | import android.app.PendingIntent; |
42aa7dfd | 25 | import android.app.Service; |
0763e16d | 26 | import android.content.Context; |
42aa7dfd | 27 | import android.content.Intent; |
28 | import android.content.SharedPreferences; | |
0763e16d JW |
29 | import android.net.Uri; |
30 | import android.os.Bundle; | |
0763e16d | 31 | import android.os.Handler; |
42aa7dfd | 32 | import android.os.IBinder; |
0763e16d JW |
33 | import android.os.Looper; |
34 | import android.os.Message; | |
42aa7dfd | 35 | import android.os.Messenger; |
0763e16d | 36 | import android.os.SystemClock; |
42aa7dfd | 37 | import android.preference.PreferenceManager; |
38 | import android.util.Log; | |
ae61bba6 | 39 | import android.widget.RemoteViews; |
42aa7dfd | 40 | import android.widget.Toast; |
41 | ||
42 | import com.jcraft.jsch.Channel; | |
43 | import com.jcraft.jsch.ChannelExec; | |
44 | import com.jcraft.jsch.JSch; | |
45 | import com.jcraft.jsch.Session; | |
46 | import com.jcraft.jsch.UIKeyboardInteractive; | |
47 | import com.jcraft.jsch.UserInfo; | |
0763e16d JW |
48 | |
49 | public class Uploader extends Service implements Runnable, UserInfo, UIKeyboardInteractive { | |
50 | private Uri uri; | |
51 | private String homedir; | |
52 | private Thread me; | |
53 | private static final int HELPME_ID = 1; | |
ae61bba6 JW |
54 | private RemoteViews remote; |
55 | private int thenotifid; | |
56 | private Notification thenotif; | |
57 | private String headline; | |
bb6544e9 | 58 | private String dest; |
0763e16d | 59 | |
035767ac JW |
60 | private InputStream is; |
61 | ||
0763e16d JW |
62 | public Object _theObject; |
63 | ||
ae61bba6 JW |
64 | private void sayNullNotification(final String scroller, final String headline, final String description) |
65 | { | |
66 | int bogon = (int)SystemClock.elapsedRealtime(); | |
67 | NotificationManager mNotificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); | |
68 | Notification notification = new Notification(R.drawable.icon, scroller, System.currentTimeMillis()); | |
69 | ||
70 | Intent intent = new Intent(this, NotifSlave.class); | |
71 | ||
72 | intent.setAction("com.joshuawise.dumload.NotifSlave"); | |
73 | /* no extras to make the notifslave die */ | |
74 | intent.setData((Uri.parse("suckit://"+SystemClock.elapsedRealtime()))); | |
75 | ||
76 | PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, 0); | |
77 | notification.defaults |= Notification.DEFAULT_VIBRATE; | |
78 | notification.flags |= Notification.FLAG_AUTO_CANCEL; | |
79 | notification.setLatestEventInfo(getApplicationContext(), headline, description, contentIntent); | |
80 | ||
81 | mNotificationManager.notify(bogon, notification); | |
82 | } | |
83 | ||
0763e16d JW |
84 | private Object /* pick one type, and fixate on it */ dance(final String type, final String text) /* for inside the thread */ |
85 | { | |
86 | final Uploader thisupl = this; | |
87 | final Message msg = Message.obtain(); | |
88 | ||
89 | /* t(*A*t) */ | |
90 | Thread t = new Thread() { | |
91 | public void run() { | |
92 | Looper.prepare(); | |
93 | int bogon = (int)SystemClock.elapsedRealtime(); | |
94 | ||
95 | NotificationManager mNotificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); | |
96 | Notification notification = new Notification(R.drawable.icon, "Dumload prompt", System.currentTimeMillis()); | |
97 | ||
98 | Handler h = new Handler() { | |
99 | public void handleMessage(Message M) { | |
100 | msg.copyFrom(M); | |
101 | Looper.myLooper().quit(); | |
102 | } | |
103 | }; | |
104 | Messenger m = new Messenger(h); | |
105 | ||
106 | Intent intent = new Intent(thisupl, NotifSlave.class); | |
107 | ||
108 | intent.setAction("com.joshuawise.dumload.NotifSlave"); | |
109 | intent.putExtra("com.joshuawise.dumload.returnmessenger", m); | |
110 | intent.putExtra("com.joshuawise.dumload.reqtype", type); | |
111 | intent.putExtra("com.joshuawise.dumload.prompt", text); | |
112 | intent.setData((Uri.parse("suckit://"+SystemClock.elapsedRealtime()))); | |
113 | ||
114 | PendingIntent contentIntent = PendingIntent.getActivity(thisupl, 0, intent, 0); | |
115 | notification.defaults |= Notification.DEFAULT_VIBRATE; | |
ae61bba6 | 116 | notification.flags |= Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR; |
0763e16d JW |
117 | notification.setLatestEventInfo(getApplicationContext(), "I've been had!", "Dumload needs your input.", contentIntent); |
118 | ||
119 | Log.e("Dumload.Uploader[thread]", "Notifying..."); | |
120 | ||
121 | mNotificationManager.notify(bogon, notification); | |
122 | ||
123 | Log.e("Dumload.Uploader[thread]", "About to go to 'sleep'..."); | |
124 | Looper.loop(); | |
125 | Log.e("Dumload.Uploader[thread]", "And we're alive!"); | |
126 | ||
127 | Log.e("Dumload.Uploader[thread]", "result was: "+(Integer.toString(msg.arg1))); | |
128 | ||
129 | mNotificationManager.cancel(bogon); | |
130 | } | |
131 | }; | |
132 | ||
133 | t.start(); | |
134 | try { | |
135 | t.join(); | |
136 | } catch (Exception e) { | |
137 | return null; | |
138 | } | |
139 | ||
140 | if (type.equals("yesno")) | |
141 | return new Boolean(msg.arg1 == 1); | |
142 | else if (type.equals("message")) | |
143 | return null; | |
144 | else if (type.equals("password")) { | |
145 | if (msg.arg1 == 0) | |
146 | return null; | |
147 | Bundle b = msg.getData(); | |
148 | return b.getString("response"); | |
149 | } else | |
150 | return null; | |
151 | } | |
152 | ||
153 | /* UserInfo bits */ | |
154 | String _password = null; | |
155 | public String getPassword() | |
156 | { | |
157 | return _password; | |
158 | } | |
159 | public boolean promptPassword(String message) | |
160 | { | |
161 | _password = (String)dance("password", message); | |
162 | return (_password != null); | |
163 | } | |
164 | ||
165 | String _passphrase = null; | |
166 | public String getPassphrase() | |
167 | { | |
168 | return _passphrase; | |
169 | } | |
170 | public boolean promptPassphrase(String message) | |
171 | { | |
172 | _passphrase = (String)dance("password", message); | |
173 | return (_passphrase != null); | |
174 | } | |
175 | ||
176 | public boolean promptYesNo(String str) | |
177 | { | |
178 | return ((Boolean)dance("yesno", str)).booleanValue(); | |
179 | } | |
180 | ||
181 | public void showMessage(String str) | |
182 | { | |
183 | dance("message", str); | |
184 | } | |
185 | ||
186 | public String[] promptKeyboardInteractive(String dest, String name, String instr, String[] prompt, boolean[] echo) | |
187 | { | |
188 | int i; | |
189 | String [] responses = new String[prompt.length]; | |
190 | ||
191 | Log.e("Dumload.Uploader", "dest: "+dest); | |
192 | Log.e("Dumload.Uploader", "name: "+name); | |
193 | Log.e("Dumload.Uploader", "instr: "+instr); | |
194 | for (i = 0; i < prompt.length; i++) | |
195 | { | |
196 | responses[i] = (String) dance("password", "[" + dest + "]\n" + prompt[i]); | |
ae61bba6 JW |
197 | if (responses[i] == null) |
198 | return null; | |
0763e16d JW |
199 | } |
200 | return responses; | |
201 | } | |
202 | ||
035767ac JW |
203 | private void expect_ack(InputStream in) throws Exception, java.io.IOException |
204 | { | |
205 | int b = in.read(); | |
206 | ||
207 | if (b == -1) | |
208 | { | |
209 | throw new Exception("unexpected EOF from remote end"); | |
210 | } | |
211 | ||
212 | if (b == 1 /* error */ || b == 2 /* fatal error */) | |
213 | { | |
214 | StringBuffer sb = new StringBuffer(); | |
215 | int c = 0; | |
216 | ||
217 | while ((c = in.read()) != '\n') | |
218 | sb.append((char)c); | |
219 | ||
220 | throw new Exception("error from remote end: " + sb.toString()); | |
221 | } | |
222 | } | |
223 | ||
ae61bba6 JW |
224 | private void set_up_notif(final String _headline) |
225 | { | |
226 | NotificationManager mNotificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); | |
227 | thenotif = new Notification(R.drawable.icon, headline, System.currentTimeMillis()); | |
228 | thenotifid = (int)SystemClock.elapsedRealtime(); | |
229 | ||
230 | Intent intent = new Intent(this, NotifSlave.class); | |
231 | ||
232 | headline = _headline; | |
233 | ||
234 | intent.setAction("com.joshuawise.dumload.NotifSlave"); | |
235 | /* no extras to make the notifslave die */ | |
236 | intent.setData((Uri.parse("suckit://"+SystemClock.elapsedRealtime()))); | |
237 | ||
238 | PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, 0); | |
239 | thenotif.defaults |= 0; | |
240 | thenotif.flags |= Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR; | |
241 | ||
242 | remote = new RemoteViews(getPackageName(), R.layout.textnotif); | |
243 | remote.setImageViewResource(R.id.image, R.drawable.icon); | |
244 | remote.setTextViewText(R.id.headline, headline); | |
245 | remote.setTextViewText(R.id.status, "Beginning upload..."); | |
246 | thenotif.contentView = remote; | |
247 | thenotif.contentIntent = contentIntent; | |
248 | ||
249 | mNotificationManager.notify(thenotifid, thenotif); | |
250 | } | |
251 | ||
252 | private void destroy_notif() | |
253 | { | |
254 | NotificationManager mNotificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); | |
255 | mNotificationManager.cancel(thenotifid); | |
256 | } | |
257 | ||
258 | private void update_notif(String text) | |
259 | { | |
260 | NotificationManager mNotificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); | |
261 | ||
262 | remote = new RemoteViews(getPackageName(), R.layout.textnotif); | |
263 | remote.setImageViewResource(R.id.image, R.drawable.icon); | |
264 | remote.setTextViewText(R.id.headline, headline); | |
265 | remote.setTextViewText(R.id.status, text); | |
266 | thenotif.contentView = remote; | |
267 | ||
268 | mNotificationManager.notify(thenotifid, thenotif); | |
269 | } | |
270 | ||
271 | private void update_notif(int n, int total) | |
272 | { | |
273 | NotificationManager mNotificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); | |
274 | ||
275 | remote = new RemoteViews(getPackageName(), R.layout.progressnotif); | |
276 | remote.setImageViewResource(R.id.image, R.drawable.icon); | |
277 | remote.setTextViewText(R.id.headline, headline); | |
278 | remote.setProgressBar(R.id.status, total, n, false); | |
279 | thenotif.contentView = remote; | |
280 | ||
281 | mNotificationManager.notify(thenotifid, thenotif); | |
282 | } | |
283 | ||
0763e16d JW |
284 | public void run() |
285 | { | |
286 | Looper.prepare(); | |
287 | ||
288 | Log.e("Dumload.Uploader[thread]", "This brought to you from the new thread."); | |
289 | ||
bb6544e9 | 290 | set_up_notif("Dumload upload: " + dest); |
ae61bba6 | 291 | |
0763e16d | 292 | try { |
035767ac JW |
293 | say("Uploading "+(Integer.toString(is.available()))+" bytes"); |
294 | ||
ae61bba6 JW |
295 | update_notif("Connecting..."); |
296 | ||
0763e16d JW |
297 | JSch jsch = new JSch(); |
298 | jsch.setKnownHosts(homedir + "/known_hosts"); | |
bb6544e9 JW |
299 | try { |
300 | jsch.addIdentity(homedir + "/id_dsa"); | |
301 | } catch (java.lang.Exception e) { | |
302 | } | |
7d9c8975 | 303 | try { |
304 | jsch.addIdentity(homedir + "/id_dsa_generated"); | |
305 | } catch (java.lang.Exception e) { | |
306 | } | |
42aa7dfd | 307 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); |
308 | String server = prefs.getString("server", "").trim(); | |
309 | String userName = prefs.getString("userName", "").trim(); | |
7d9c8975 | 310 | Integer port = Integer.valueOf(prefs.getString("port", "22")); |
42aa7dfd | 311 | Log.d("dbg", userName + "@" + server + ":" + port); |
312 | Session s = jsch.getSession(userName, server, port); | |
0763e16d JW |
313 | s.setUserInfo(this); |
314 | s.connect(); | |
315 | ||
316 | Channel channel = s.openChannel("exec"); | |
bb6544e9 | 317 | ((ChannelExec)channel).setCommand("scp -t "+dest); |
0763e16d JW |
318 | channel.connect(); |
319 | ||
035767ac JW |
320 | OutputStream scp_out = channel.getOutputStream(); |
321 | InputStream scp_in = channel.getInputStream(); | |
322 | ||
ae61bba6 JW |
323 | update_notif("Starting send..."); |
324 | ||
035767ac JW |
325 | /* Okay, BS out of the way. Now go send the file. */ |
326 | expect_ack(scp_in); | |
327 | ||
bb6544e9 JW |
328 | String stfu; |
329 | if (dest.lastIndexOf("/") > 0) | |
330 | stfu = dest.substring(dest.lastIndexOf("/") + 1); | |
331 | else | |
332 | stfu = dest; | |
333 | ||
334 | scp_out.write(("C0644 " + (Integer.toString(is.available())) + " "+stfu+"\n").getBytes()); | |
035767ac JW |
335 | scp_out.flush(); |
336 | ||
337 | expect_ack(scp_in); | |
338 | ||
ae61bba6 JW |
339 | int total, nbytes; |
340 | total = is.available(); | |
341 | nbytes = 0; | |
035767ac JW |
342 | int len; |
343 | byte[] buf = new byte[4096]; | |
344 | while ((len = is.read(buf, 0, buf.length)) > 0) | |
ae61bba6 | 345 | { |
035767ac | 346 | scp_out.write(buf, 0, len); |
ae61bba6 JW |
347 | nbytes += len; |
348 | update_notif(nbytes, total); | |
349 | } | |
035767ac JW |
350 | |
351 | is.close(); | |
352 | ||
e1916520 | 353 | update_notif("Finishing file transfer..."); |
ae61bba6 | 354 | |
035767ac JW |
355 | scp_out.write("\0".getBytes()); |
356 | scp_out.flush(); | |
357 | ||
358 | expect_ack(scp_in); | |
359 | ||
e1916520 JW |
360 | channel.disconnect(); |
361 | ||
362 | update_notif("Preparing to resize image..."); | |
363 | ||
364 | channel = s.openChannel("exec"); | |
bb6544e9 | 365 | ((ChannelExec)channel).setCommand("pscale "+dest); |
e1916520 JW |
366 | channel.connect(); |
367 | ||
368 | scp_in = channel.getInputStream(); | |
369 | ||
370 | update_notif("Resizing image..."); | |
371 | while ((len = scp_in.read(buf, 0, buf.length)) > 0) | |
372 | ; | |
373 | ||
374 | channel.disconnect(); | |
375 | update_notif("Upload complete."); | |
376 | ||
bb6544e9 | 377 | sayNullNotification("Dumload upload complete: " + dest, "Upload complete", "Uploaded: " + dest); |
0763e16d | 378 | |
0763e16d | 379 | s.disconnect(); |
035767ac | 380 | } catch (Exception e) { |
0763e16d | 381 | Log.e("Dumload.uploader[thread]", "JSchException: "+(e.toString())); |
ae61bba6 | 382 | sayNullNotification("Dumload upload failed", "Upload failed", e.toString()); |
0763e16d JW |
383 | } |
384 | ||
ae61bba6 JW |
385 | destroy_notif(); |
386 | ||
0763e16d JW |
387 | Log.e("Dumload.uploader[thread]", "And now I'm back to life!"); |
388 | } | |
389 | ||
390 | private void say(String s) { | |
391 | Toast.makeText(getApplicationContext(), s, Toast.LENGTH_SHORT).show(); | |
392 | } | |
393 | ||
394 | @Override | |
395 | public void onStart(Intent i, int startId) | |
396 | { | |
397 | uri = i.getData(); | |
bb6544e9 | 398 | dest = i.getStringExtra("com.joshuawise.dumload.dest"); |
0763e16d JW |
399 | homedir = getApplicationContext().getFilesDir().getAbsolutePath(); |
400 | int shits = 0; | |
42aa7dfd | 401 | int giggles = 1; |
0763e16d JW |
402 | |
403 | super.onStart(i, startId); | |
404 | ||
405 | Log.e("Dumload.Uploader", "Started."); | |
406 | Log.e("Dumload.Uploader", "My path is "+homedir); | |
407 | ||
408 | try { | |
035767ac | 409 | is = getContentResolver().openInputStream(uri); |
0763e16d | 410 | } catch (Exception e) { |
035767ac JW |
411 | say("Failed to open input file."); |
412 | return; | |
0763e16d JW |
413 | } |
414 | ||
0763e16d JW |
415 | |
416 | me = new Thread(this, "Uploader thread"); | |
417 | me.start(); | |
418 | } | |
419 | ||
420 | @Override | |
421 | public IBinder onBind(Intent i) { | |
422 | Log.e("Dumload.Uploader", "bound"); | |
423 | ||
424 | return null; | |
425 | } | |
426 | } |