ca2ff244b175bf89f6fe44e4e39632f721c4861a
[pub/Android/ownCloud.git] / src / eu / alefzero / owncloud / Uploader.java
1 /* ownCloud Android client application
2 * Copyright (C) 2011 Bartek Przybylski
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 *
17 */
18 package eu.alefzero.owncloud;
19
20 import java.io.File;
21 import java.net.FileNameMap;
22 import java.net.URLConnection;
23 import java.util.ArrayList;
24 import java.util.Stack;
25
26 import android.accounts.Account;
27 import android.accounts.AccountManager;
28 import android.app.AlertDialog;
29 import android.app.Dialog;
30 import android.app.ListActivity;
31 import android.app.ProgressDialog;
32 import android.app.AlertDialog.Builder;
33 import android.content.ContentValues;
34 import android.content.Context;
35 import android.content.DialogInterface;
36 import android.content.Intent;
37 import android.content.DialogInterface.OnCancelListener;
38 import android.content.DialogInterface.OnClickListener;
39 import android.database.Cursor;
40 import android.net.Uri;
41 import android.os.Bundle;
42 import android.os.Handler;
43 import android.os.Parcelable;
44 import android.provider.MediaStore.Images.Media;
45 import android.util.Log;
46 import android.view.View;
47 import android.view.Window;
48 import android.view.ViewGroup.LayoutParams;
49 import android.widget.AdapterView;
50 import android.widget.Button;
51 import android.widget.EditText;
52 import android.widget.LinearLayout;
53 import android.widget.ListView;
54 import android.widget.SimpleCursorAdapter;
55 import android.widget.Toast;
56 import android.widget.AdapterView.OnItemClickListener;
57 import eu.alefzero.owncloud.authenticator.AccountAuthenticator;
58 import eu.alefzero.owncloud.db.ProviderMeta;
59 import eu.alefzero.owncloud.db.ProviderMeta.ProviderTableMeta;
60 import eu.alefzero.webdav.WebdavClient;
61 import eu.alefzero.webdav.WebdavUtils;
62
63 /**
64 * This can be used to upload things to an ownCloud instance.
65 *
66 * @author Bartek Przybylski
67 *
68 */
69 public class Uploader extends ListActivity implements OnItemClickListener,
70 android.view.View.OnClickListener {
71 private static final String TAG = "ownCloudUploader";
72
73 private Account mAccount;
74 private AccountManager mAccountManager;
75 private String mUsername, mPassword;
76 private Cursor mCursor;
77 private Stack<String> mParents;
78 private Thread mUploadThread;
79 private Handler mHandler;
80 private ArrayList<Parcelable> mStreamsToUpload;
81
82 private final static int DIALOG_NO_ACCOUNT = 0;
83 private final static int DIALOG_WAITING = 1;
84 private final static int DIALOG_NO_STREAM = 2;
85 private final static int DIALOG_MULTIPLE_ACCOUNT = 3;
86 private final static int DIALOG_GET_DIRNAME = 4;
87
88 private final static int REQUEST_CODE_SETUP_ACCOUNT = 0;
89
90 @Override
91 protected void onCreate(Bundle savedInstanceState) {
92 super.onCreate(savedInstanceState);
93 getWindow().requestFeature(Window.FEATURE_NO_TITLE);
94 mParents = new Stack<String>();
95 mHandler = new Handler();
96 if (getIntent().hasExtra(Intent.EXTRA_STREAM)) {
97 prepareStreamsToUpload();
98 mAccountManager = (AccountManager) getSystemService(Context.ACCOUNT_SERVICE);
99 Account[] accounts = mAccountManager
100 .getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE);
101 if (accounts.length == 0) {
102 Log.i(TAG, "No ownCloud account is available");
103 showDialog(DIALOG_NO_ACCOUNT);
104 } else if (accounts.length > 1) {
105 Log.i(TAG, "More then one ownCloud is available");
106 showDialog(DIALOG_MULTIPLE_ACCOUNT);
107 } else {
108 mAccount = accounts[0];
109 setContentView(R.layout.uploader_layout);
110 populateDirectoryList();
111 }
112 } else {
113 showDialog(DIALOG_NO_STREAM);
114 }
115 }
116
117 @Override
118 protected Dialog onCreateDialog(final int id) {
119 final AlertDialog.Builder builder = new Builder(this);
120 switch (id) {
121 case DIALOG_WAITING:
122 ProgressDialog pDialog = new ProgressDialog(this);
123 pDialog.setIndeterminate(false);
124 pDialog.setCancelable(false);
125 pDialog.setMessage(getResources().getString(
126 R.string.uploader_info_uploading));
127 return pDialog;
128 case DIALOG_NO_ACCOUNT:
129 builder.setIcon(android.R.drawable.ic_dialog_alert);
130 builder.setTitle(R.string.uploader_wrn_no_account_title);
131 builder.setMessage(R.string.uploader_wrn_no_account_text);
132 builder.setCancelable(false);
133 builder.setPositiveButton(
134 R.string.uploader_wrn_no_account_setup_btn_text,
135 new OnClickListener() {
136 public void onClick(DialogInterface dialog, int which) {
137 if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.ECLAIR_MR1) {
138 // using string value since in API7 this
139 // constatn is not defined
140 // in API7 < this constatant is defined in
141 // Settings.ADD_ACCOUNT_SETTINGS
142 // and Settings.EXTRA_AUTHORITIES
143 Intent intent = new Intent(
144 "android.settings.ADD_ACCOUNT_SETTINGS");
145 intent.putExtra(
146 "authorities",
147 new String[] { AccountAuthenticator.AUTH_TOKEN_TYPE });
148 startActivityForResult(intent,
149 REQUEST_CODE_SETUP_ACCOUNT);
150 } else {
151 // since in API7 there is no direct call for
152 // account setup, so we need to
153 // show our own AccountSetupAcricity, get
154 // desired results and setup
155 // everything for ourself
156 Intent intent = new Intent(getBaseContext(),
157 AccountAuthenticator.class);
158 startActivityForResult(intent,
159 REQUEST_CODE_SETUP_ACCOUNT);
160 }
161 }
162 });
163 builder.setNegativeButton(
164 R.string.uploader_wrn_no_account_quit_btn_text,
165 new OnClickListener() {
166 public void onClick(DialogInterface dialog, int which) {
167 finish();
168 }
169 });
170 return builder.create();
171 case DIALOG_GET_DIRNAME:
172 final EditText dirName = new EditText(getBaseContext());
173 builder.setView(dirName);
174 builder.setTitle(R.string.uploader_info_dirname);
175 String pathToUpload;
176 if (mParents.empty()) {
177 pathToUpload = "/";
178 } else {
179 mCursor = managedQuery(Uri.withAppendedPath(
180 ProviderTableMeta.CONTENT_URI_FILE, mParents.peek()),
181 null, null, null, null);
182 mCursor.moveToFirst();
183 pathToUpload = mCursor.getString(mCursor
184 .getColumnIndex(ProviderTableMeta.FILE_PATH))
185 + mCursor
186 .getString(
187 mCursor.getColumnIndex(ProviderTableMeta.FILE_NAME))
188 .replace(" ", "%20");
189 }
190 a a = new a(pathToUpload, dirName);
191 builder.setPositiveButton(R.string.common_ok, a);
192 builder.setNegativeButton(R.string.common_cancel,
193 new OnClickListener() {
194 public void onClick(DialogInterface dialog, int which) {
195 dialog.cancel();
196 }
197 });
198 return builder.create();
199 case DIALOG_MULTIPLE_ACCOUNT:
200 CharSequence ac[] = new CharSequence[mAccountManager
201 .getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE).length];
202 for (int i = 0; i < ac.length; ++i) {
203 ac[i] = mAccountManager
204 .getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE)[i].name;
205 }
206 builder.setTitle(R.string.common_choose_account);
207 builder.setItems(ac, new OnClickListener() {
208 public void onClick(DialogInterface dialog, int which) {
209 mAccount = mAccountManager
210 .getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE)[which];
211 populateDirectoryList();
212 }
213 });
214 builder.setCancelable(true);
215 builder.setOnCancelListener(new OnCancelListener() {
216 public void onCancel(DialogInterface dialog) {
217 dialog.cancel();
218 finish();
219 }
220 });
221 return builder.create();
222 default:
223 throw new IllegalArgumentException("Unknown dialog id: " + id);
224 }
225 }
226
227 class a implements OnClickListener {
228 String mPath;
229 EditText mDirname;
230
231 public a(String path, EditText dirname) {
232 mPath = path;
233 mDirname = dirname;
234 }
235
236 public void onClick(DialogInterface dialog, int which) {
237 showDialog(DIALOG_WAITING);
238 mUploadThread = new Thread(new BackgroundUploader(mPath
239 + mDirname.getText().toString(), mStreamsToUpload,
240 mHandler, true));
241 mUploadThread.start();
242 }
243 }
244
245 @Override
246 public void onBackPressed() {
247
248 if (mParents.size() == 0) {
249 super.onBackPressed();
250 return;
251 } else if (mParents.size() == 1) {
252 mParents.pop();
253 mCursor = managedQuery(ProviderTableMeta.CONTENT_URI, null,
254 ProviderTableMeta.FILE_CONTENT_TYPE + "=?",
255 new String[] { "DIR" }, null);
256 } else {
257 mParents.pop();
258 mCursor = managedQuery(Uri.withAppendedPath(
259 ProviderTableMeta.CONTENT_URI_DIR, mParents.peek()), null,
260 ProviderTableMeta.FILE_CONTENT_TYPE + "=?",
261 new String[] { "DIR" }, null);
262 }
263
264 SimpleCursorAdapter sca = new SimpleCursorAdapter(this,
265 R.layout.uploader_list_item_layout, mCursor,
266 new String[] { ProviderTableMeta.FILE_NAME },
267 new int[] { R.id.textView1 });
268 setListAdapter(sca);
269 }
270
271 public void onItemClick(AdapterView<?> parent, View view, int position,
272 long id) {
273 if (!mCursor.moveToPosition(position)) {
274 throw new IndexOutOfBoundsException("Incorrect item selected");
275 }
276 String _id = mCursor.getString(mCursor
277 .getColumnIndex(ProviderTableMeta._ID));
278 mParents.push(_id);
279
280 mCursor.close();
281 mCursor = managedQuery(
282 Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_DIR, _id),
283 null, ProviderTableMeta.FILE_CONTENT_TYPE + "=?",
284 new String[] { "DIR" }, null);
285 SimpleCursorAdapter sca = new SimpleCursorAdapter(this,
286 R.layout.uploader_list_item_layout, mCursor,
287 new String[] { ProviderTableMeta.FILE_NAME },
288 new int[] { R.id.textView1 });
289 setListAdapter(sca);
290 getListView().invalidate();
291 }
292
293 public void onClick(View v) {
294 switch (v.getId()) {
295 case R.id.uploader_choose_folder:
296 String pathToUpload = null;
297 if (mParents.empty()) {
298 pathToUpload = "/";
299 } else {
300 mCursor = managedQuery(Uri.withAppendedPath(
301 ProviderTableMeta.CONTENT_URI_FILE, mParents.peek()),
302 null, null, null, null);
303 mCursor.moveToFirst();
304 pathToUpload = mCursor.getString(
305 mCursor.getColumnIndex(ProviderTableMeta.FILE_PATH))
306 .replace(" ", "%20");
307 }
308 Log.d(TAG, "Uploading file to dir " + pathToUpload);
309
310 showDialog(DIALOG_WAITING);
311 mUploadThread = new Thread(new BackgroundUploader(pathToUpload,
312 mStreamsToUpload, mHandler));
313 mUploadThread.start();
314
315 break;
316 case android.R.id.button1: // dynamic action for create aditional dir
317 showDialog(DIALOG_GET_DIRNAME);
318 break;
319 default:
320 throw new IllegalArgumentException("Wrong element clicked");
321 }
322 }
323
324 public void onUploadComplete(boolean uploadSucc, String message) {
325 dismissDialog(DIALOG_WAITING);
326 Log.i(TAG, "UploadSucc: " + uploadSucc + " message: " + message);
327 if (uploadSucc) {
328 Toast.makeText(this,
329 getResources().getString(R.string.uploader_upload_succeed),
330 Toast.LENGTH_SHORT).show();
331 } else {
332 Toast.makeText(
333 this,
334 getResources().getString(R.string.uploader_upload_failed)
335 + message, Toast.LENGTH_LONG).show();
336 }
337 finish();
338 }
339
340 @Override
341 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
342 super.onActivityResult(requestCode, resultCode, data);
343 Log.i(TAG, "result received. req: " + requestCode + " res: "
344 + resultCode);
345 if (requestCode == REQUEST_CODE_SETUP_ACCOUNT) {
346 dismissDialog(DIALOG_NO_ACCOUNT);
347 if (resultCode == RESULT_CANCELED) {
348 finish();
349 }
350 Account[] accounts = mAccountManager
351 .getAccountsByType(AccountAuthenticator.AUTH_TOKEN_TYPE);
352 if (accounts.length == 0) {
353 showDialog(DIALOG_NO_ACCOUNT);
354 } else {
355 // there is no need for checking for is there more then one
356 // account at this point
357 // since account setup can set only one account at time
358 mAccount = accounts[0];
359 populateDirectoryList();
360 }
361 }
362 }
363
364 private void populateDirectoryList() {
365 mUsername = mAccount.name.substring(0, mAccount.name.indexOf('@'));
366 mPassword = mAccountManager.getPassword(mAccount);
367 setContentView(R.layout.uploader_layout);
368
369 mCursor = managedQuery(ProviderMeta.ProviderTableMeta.CONTENT_URI,
370 null, ProviderTableMeta.FILE_NAME + "=? AND "
371 + ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?",
372 new String[] { "/", mAccount.name }, null);
373
374 if (mCursor.moveToFirst()) {
375 mCursor = managedQuery(
376 ProviderMeta.ProviderTableMeta.CONTENT_URI,
377 null,
378 ProviderTableMeta.FILE_CONTENT_TYPE + "=? AND "
379 + ProviderTableMeta.FILE_ACCOUNT_OWNER + "=? AND "
380 + ProviderTableMeta.FILE_PARENT + "=?",
381 new String[] {
382 "DIR",
383 mAccount.name,
384 mCursor.getString(mCursor
385 .getColumnIndex(ProviderTableMeta._ID)) },
386 null);
387
388 ListView lv = getListView();
389 lv.setOnItemClickListener(this);
390 SimpleCursorAdapter sca = new SimpleCursorAdapter(this,
391 R.layout.uploader_list_item_layout, mCursor,
392 new String[] { ProviderTableMeta.FILE_NAME },
393 new int[] { R.id.textView1 });
394 setListAdapter(sca);
395 Button btn = (Button) findViewById(R.id.uploader_choose_folder);
396 btn.setOnClickListener(this);
397 // insert create new directory for multiple items uploading
398 if (getIntent().getAction().equals(Intent.ACTION_SEND_MULTIPLE)) {
399 Button createDirBtn = new Button(this);
400 createDirBtn.setId(android.R.id.button1);
401 createDirBtn.setText(R.string.uploader_btn_create_dir_text);
402 createDirBtn.setOnClickListener(this);
403 ((LinearLayout) findViewById(R.id.linearLayout1)).addView(
404 createDirBtn, LayoutParams.FILL_PARENT,
405 LayoutParams.WRAP_CONTENT);
406 }
407 }
408 }
409
410 private void prepareStreamsToUpload() {
411 if (getIntent().getAction().equals(Intent.ACTION_SEND)) {
412 mStreamsToUpload = new ArrayList<Parcelable>();
413 mStreamsToUpload.add(getIntent().getParcelableExtra(
414 Intent.EXTRA_STREAM));
415 } else if (getIntent().getAction().equals(Intent.ACTION_SEND_MULTIPLE)) {
416 mStreamsToUpload = getIntent().getParcelableArrayListExtra(
417 Intent.EXTRA_STREAM);
418 } else {
419 // unknow action inserted
420 throw new IllegalArgumentException("Unknown action given: "
421 + getIntent().getAction());
422 }
423 }
424
425 public void PartialupdateUpload(String fileLocalPath, String filename,
426 String filepath, String contentType, String contentLength) {
427 ContentValues cv = new ContentValues();
428 cv.put(ProviderTableMeta.FILE_NAME, filename);
429 cv.put(ProviderTableMeta.FILE_PATH, filepath);
430 cv.put(ProviderTableMeta.FILE_STORAGE_PATH, fileLocalPath);
431 cv.put(ProviderTableMeta.FILE_MODIFIED,
432 WebdavUtils.DISPLAY_DATE_FORMAT.format(new java.util.Date()));
433 cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, contentType);
434 cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, contentLength);
435 cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, mAccount.name);
436 Log.d(TAG, filename + " ++ " + filepath + " ++ " + contentLength
437 + " ++ " + contentType + " ++ " + fileLocalPath);
438 if (!mParents.empty()) {
439 Cursor c = managedQuery(Uri.withAppendedPath(
440 ProviderTableMeta.CONTENT_URI_FILE, mParents.peek()), null,
441 null, null, null);
442 c.moveToFirst();
443 cv.put(ProviderTableMeta.FILE_PARENT,
444 c.getString(c.getColumnIndex(ProviderTableMeta._ID)));
445 c.close();
446 }
447 getContentResolver().insert(ProviderTableMeta.CONTENT_URI_FILE, cv);
448 }
449
450 class BackgroundUploader implements Runnable {
451 private ArrayList<Parcelable> mUploadStreams;
452 private Handler mHandler;
453 private String mUploadPath;
454 private boolean mCreateDir;
455
456 public BackgroundUploader(String pathToUpload,
457 ArrayList<Parcelable> streamsToUpload, Handler handler) {
458 mUploadStreams = streamsToUpload;
459 mHandler = handler;
460 mUploadPath = pathToUpload.replace(" ", "%20");
461 mCreateDir = false;
462 }
463
464 public BackgroundUploader(String pathToUpload,
465 ArrayList<Parcelable> streamsToUpload, Handler handler,
466 boolean createDir) {
467 mUploadStreams = streamsToUpload;
468 mHandler = handler;
469 mUploadPath = pathToUpload.replace(" ", "%20");
470 mCreateDir = createDir;
471 }
472
473 public void run() {
474 WebdavClient wdc = new WebdavClient(Uri.parse(mAccountManager
475 .getUserData(mAccount, AccountAuthenticator.KEY_OC_URL)));
476 wdc.setCredentials(mUsername, mPassword);
477 wdc.allowUnsignedCertificates();
478
479 // create last directory in path if nessesary
480 if (mCreateDir) {
481 wdc.createDirectory(mUploadPath);
482 }
483
484 for (int i = 0; i < mUploadStreams.size(); ++i) {
485 Uri uri = (Uri) mUploadStreams.get(i);
486 if (uri.getScheme().equals("content")) {
487 final Cursor c = getContentResolver()
488 .query((Uri) mUploadStreams.get(i), null, null,
489 null, null);
490
491 if (!wdc.putFile(
492 c.getString(c.getColumnIndex(Media.DATA)),
493 mUploadPath
494 + "/"
495 + c.getString(c
496 .getColumnIndex(Media.DISPLAY_NAME)),
497 c.getString(c.getColumnIndex(Media.MIME_TYPE)))) {
498 mHandler.post(new Runnable() {
499 public void run() {
500 Uploader.this
501 .onUploadComplete(
502 false,
503 "Error while uploading file: "
504 + c.getString(c
505 .getColumnIndex(Media.DISPLAY_NAME)));
506 }
507 });
508 } else {
509 mHandler.post(new Runnable() {
510 public void run() {
511 Uploader.this.PartialupdateUpload(
512 c.getString(c
513 .getColumnIndex(Media.DATA)),
514 c.getString(c
515 .getColumnIndex(Media.DISPLAY_NAME)),
516 mUploadPath
517 + "/"
518 + c.getString(c
519 .getColumnIndex(Media.DISPLAY_NAME)),
520 c.getString(c
521 .getColumnIndex(Media.MIME_TYPE)),
522 c.getString(c
523 .getColumnIndex(Media.SIZE)));
524 }
525 });
526 }
527 } else if (uri.getScheme().equals("file")) {
528 final File file = new File(Uri.decode(uri.toString())
529 .replace(uri.getScheme() + "://", ""));
530 FileNameMap fileNameMap = URLConnection.getFileNameMap();
531 String contentType = fileNameMap.getContentTypeFor(uri
532 .toString());
533 if (contentType == null) {
534 contentType = "text/plain";
535 }
536 if (!wdc.putFile(file.getAbsolutePath(), mUploadPath + "/"
537 + file.getName(), contentType)) {
538 mHandler.post(new Runnable() {
539 public void run() {
540 Uploader.this.onUploadComplete(
541 false,
542 "Error while uploading file: "
543 + file.getName());
544 }
545 });
546 }
547 }
548
549 }
550 mHandler.post(new Runnable() {
551 public void run() {
552 Uploader.this.onUploadComplete(true, null);
553 }
554 });
555 }
556
557 }
558
559 }