42806375acc1273f11b75a112c9ee8ed44667466
[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 * @author Bartek Przybylski
66 *
67 */
68 public class Uploader extends ListActivity implements OnItemClickListener, android.view.View.OnClickListener {
69 private static final String TAG = "ownCloudUploader";
70
71 private Account mAccount;
72 private AccountManager mAccountManager;
73 private String mUsername, mPassword;
74 private Cursor mCursor;
75 private Stack<String> mParents;
76 private Thread mUploadThread;
77 private Handler mHandler;
78 private ArrayList<Parcelable> mStreamsToUpload;
79
80 private final static int DIALOG_NO_ACCOUNT = 0;
81 private final static int DIALOG_WAITING = 1;
82 private final static int DIALOG_NO_STREAM = 2;
83 private final static int DIALOG_MULTIPLE_ACCOUNT = 3;
84 private final static int DIALOG_GET_DIRNAME = 4;
85
86 private final static int REQUEST_CODE_SETUP_ACCOUNT = 0;
87
88 @Override
89 protected void onCreate(Bundle savedInstanceState) {
90 super.onCreate(savedInstanceState);
91 getWindow().requestFeature(Window.FEATURE_NO_TITLE);
92 mParents = new Stack<String>();
93 mHandler = new Handler();
94 if (getIntent().hasExtra(Intent.EXTRA_STREAM)) {
95 prepareStreamsToUpload();
96 mAccountManager = (AccountManager)getSystemService(Context.ACCOUNT_SERVICE);
97 Account[] accounts = mAccountManager.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE);
98 if (accounts.length == 0) {
99 Log.i(TAG, "No ownCloud account is available");
100 showDialog(DIALOG_NO_ACCOUNT);
101 } else if (accounts.length > 1) {
102 Log.i(TAG, "More then one ownCloud is available");
103 showDialog(DIALOG_MULTIPLE_ACCOUNT);
104 } else {
105 mAccount = accounts[0];
106 setContentView(R.layout.uploader_layout);
107 populateDirectoryList();
108 }
109 } else {
110 showDialog(DIALOG_NO_STREAM);
111 }
112 }
113
114 @Override
115 protected Dialog onCreateDialog(final int id) {
116 final AlertDialog.Builder builder = new Builder(this);
117 switch (id) {
118 case DIALOG_WAITING:
119 ProgressDialog pDialog = new ProgressDialog(this);
120 pDialog.setIndeterminate(false);
121 pDialog.setCancelable(false);
122 pDialog.setMessage(getResources().getString(R.string.uploader_info_uploading));
123 return pDialog;
124 case DIALOG_NO_ACCOUNT:
125 builder.setIcon(android.R.drawable.ic_dialog_alert);
126 builder.setTitle(R.string.uploader_wrn_no_account_title);
127 builder.setMessage(R.string.uploader_wrn_no_account_text);
128 builder.setCancelable(false);
129 builder.setPositiveButton(R.string.uploader_wrn_no_account_setup_btn_text, new OnClickListener() {
130 public void onClick(DialogInterface dialog, int which) {
131 if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.ECLAIR_MR1) {
132 // using string value since in API7 this constatn is not defined
133 // in API7 < this constatant is defined in Settings.ADD_ACCOUNT_SETTINGS
134 // and Settings.EXTRA_AUTHORITIES
135 Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS");
136 intent.putExtra("authorities", new String[] {AccountAuthenticator.AUTH_TOKEN_TYPE});
137 startActivityForResult(intent, REQUEST_CODE_SETUP_ACCOUNT);
138 } else {
139 // since in API7 there is no direct call for account setup, so we need to
140 // show our own AccountSetupAcricity, get desired results and setup
141 // everything for ourself
142 Intent intent = new Intent(getBaseContext(), AccountAuthenticator.class);
143 startActivityForResult(intent, REQUEST_CODE_SETUP_ACCOUNT);
144 }
145 }
146 });
147 builder.setNegativeButton(R.string.uploader_wrn_no_account_quit_btn_text, new OnClickListener() {
148 public void onClick(DialogInterface dialog, int which) {
149 finish();
150 }
151 });
152 return builder.create();
153 case DIALOG_GET_DIRNAME:
154 final EditText dirName = new EditText(getBaseContext());
155 builder.setView(dirName);
156 builder.setTitle(R.string.uploader_info_dirname);
157 String pathToUpload;
158 if (mParents.empty()) {
159 pathToUpload = "/";
160 } else {
161 mCursor = managedQuery(Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_FILE, mParents.peek()),
162 null,
163 null,
164 null,
165 null);
166 mCursor.moveToFirst();
167 pathToUpload = mCursor.getString(mCursor.getColumnIndex(ProviderTableMeta.FILE_PATH)) +
168 mCursor.getString(mCursor.getColumnIndex(ProviderTableMeta.FILE_NAME)).replace(" ", "%20");
169 }
170 a a = new a(pathToUpload, dirName);
171 builder.setPositiveButton(R.string.common_ok, a);
172 builder.setNegativeButton(R.string.common_cancel, new OnClickListener() {
173 public void onClick(DialogInterface dialog, int which) {
174 dialog.cancel();
175 }
176 });
177 return builder.create();
178 case DIALOG_MULTIPLE_ACCOUNT:
179 CharSequence ac[] = new CharSequence[mAccountManager.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE).length];
180 for (int i = 0; i < ac.length; ++i) {
181 ac[i] = mAccountManager.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE)[i].name;
182 }
183 builder.setTitle(R.string.common_choose_account);
184 builder.setItems(ac, new OnClickListener() {
185 public void onClick(DialogInterface dialog, int which) {
186 mAccount = mAccountManager.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE)[which];
187 populateDirectoryList();
188 }
189 });
190 builder.setCancelable(true);
191 builder.setOnCancelListener(new OnCancelListener() {
192 public void onCancel(DialogInterface dialog) {
193 dialog.cancel();
194 finish();
195 }
196 });
197 return builder.create();
198 default:
199 throw new IllegalArgumentException("Unknown dialog id: " + id);
200 }
201 }
202
203 class a implements OnClickListener {
204 String mPath;
205 EditText mDirname;
206 public a(String path, EditText dirname) {
207 mPath = path; mDirname = dirname;
208 }
209 public void onClick(DialogInterface dialog, int which) {
210 showDialog(DIALOG_WAITING);
211 mUploadThread = new Thread(new BackgroundUploader(mPath+mDirname.getText().toString(), mStreamsToUpload, mHandler, true));
212 mUploadThread.start();
213 }
214 }
215
216 @Override
217 public void onBackPressed() {
218
219 if (mParents.size()==0) {
220 super.onBackPressed();
221 return;
222 } else if (mParents.size() == 1) {
223 mParents.pop();
224 mCursor = managedQuery(ProviderTableMeta.CONTENT_URI,
225 null,
226 ProviderTableMeta.FILE_CONTENT_TYPE+"=?",
227 new String[]{"DIR"},
228 null);
229 } else {
230 mParents.pop();
231 mCursor = managedQuery(Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_DIR, mParents.peek()),
232 null,
233 ProviderTableMeta.FILE_CONTENT_TYPE+"=?",
234 new String[]{"DIR"},
235 null);
236 }
237
238 SimpleCursorAdapter sca = new SimpleCursorAdapter(this, R.layout.uploader_list_item_layout,
239 mCursor,
240 new String[]{ProviderTableMeta.FILE_NAME},
241 new int[]{R.id.textView1});
242 setListAdapter(sca);
243 }
244
245 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
246 if (!mCursor.moveToPosition(position)) {
247 throw new IndexOutOfBoundsException("Incorrect item selected");
248 }
249 String _id = mCursor.getString(mCursor.getColumnIndex(ProviderTableMeta._ID));
250 mParents.push(_id);
251
252 mCursor.close();
253 mCursor = managedQuery(Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_DIR, _id),
254 null,
255 ProviderTableMeta.FILE_CONTENT_TYPE+"=?",
256 new String[]{"DIR"},
257 null);
258 SimpleCursorAdapter sca = new SimpleCursorAdapter(this, R.layout.uploader_list_item_layout,
259 mCursor,
260 new String[]{ProviderTableMeta.FILE_NAME},
261 new int[]{R.id.textView1});
262 setListAdapter(sca);
263 getListView().invalidate();
264 }
265
266 public void onClick(View v) {
267 switch (v.getId()) {
268 case R.id.uploader_choose_folder:
269 String pathToUpload = null;
270 if (mParents.empty()) {
271 pathToUpload = "/";
272 } else {
273 mCursor = managedQuery(Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_FILE, mParents.peek()),
274 null,
275 null,
276 null,
277 null);
278 mCursor.moveToFirst();
279 pathToUpload = mCursor.getString(mCursor.getColumnIndex(ProviderTableMeta.FILE_PATH)) +
280 mCursor.getString(mCursor.getColumnIndex(ProviderTableMeta.FILE_NAME)).replace(" ", "%20");
281 }
282
283 showDialog(DIALOG_WAITING);
284 mUploadThread = new Thread(new BackgroundUploader(pathToUpload, mStreamsToUpload, mHandler));
285 mUploadThread.start();
286
287 break;
288 case android.R.id.button1: // dynamic action for create aditional dir
289 showDialog(DIALOG_GET_DIRNAME);
290 break;
291 default:
292 throw new IllegalArgumentException("Wrong element clicked");
293 }
294 }
295
296 public void onUploadComplete(boolean uploadSucc, String message) {
297 dismissDialog(DIALOG_WAITING);
298 Log.i(TAG, "UploadSucc: " + uploadSucc + " message: " + message);
299 if (uploadSucc) {
300 Toast.makeText(this, getResources().getString(R.string.uploader_upload_succeed), Toast.LENGTH_SHORT).show();
301 } else {
302 Toast.makeText(this, getResources().getString(R.string.uploader_upload_failed) + message, Toast.LENGTH_LONG).show();
303 }
304 finish();
305 }
306
307 @Override
308 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
309 super.onActivityResult(requestCode, resultCode, data);
310 Log.i(TAG, "result received. req: " + requestCode + " res: " + resultCode);
311 if (requestCode == REQUEST_CODE_SETUP_ACCOUNT) {
312 dismissDialog(DIALOG_NO_ACCOUNT);
313 if (resultCode == RESULT_CANCELED) {
314 finish();
315 }
316 Account[] accounts = mAccountManager.getAccountsByType(AccountAuthenticator.AUTH_TOKEN_TYPE);
317 if (accounts.length == 0) {
318 showDialog(DIALOG_NO_ACCOUNT);
319 } else {
320 // there is no need for checking for is there more then one account at this point
321 // since account setup can set only one account at time
322 mAccount = accounts[0];
323 populateDirectoryList();
324 }
325 }
326 }
327
328 private void populateDirectoryList() {
329 mUsername = mAccount.name.substring(0, mAccount.name.indexOf('@'));
330 mPassword = mAccountManager.getPassword(mAccount);
331 setContentView(R.layout.uploader_layout);
332 mCursor = managedQuery(ProviderMeta.ProviderTableMeta.CONTENT_URI,
333 null,
334 ProviderTableMeta.FILE_CONTENT_TYPE+"=? AND " + ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?",
335 new String[]{"DIR", mAccount.name},
336 null);
337
338 ListView lv = getListView();
339 lv.setOnItemClickListener(this);
340 SimpleCursorAdapter sca = new SimpleCursorAdapter(this,
341 R.layout.uploader_list_item_layout,
342 mCursor,
343 new String[]{ProviderTableMeta.FILE_NAME},
344 new int[]{R.id.textView1});
345 setListAdapter(sca);
346 Button btn = (Button) findViewById(R.id.uploader_choose_folder);
347 btn.setOnClickListener(this);
348 // insert create new directory for multiple items uploading
349 if (getIntent().getAction().equals(Intent.ACTION_SEND_MULTIPLE)) {
350 Button createDirBtn = new Button(this);
351 createDirBtn.setId(android.R.id.button1);
352 createDirBtn.setText(R.string.uploader_btn_create_dir_text);
353 createDirBtn.setOnClickListener(this);
354 ((LinearLayout)findViewById(R.id.linearLayout1)).addView(createDirBtn, LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
355 }
356 }
357
358 private void prepareStreamsToUpload() {
359 if (getIntent().getAction().equals(Intent.ACTION_SEND)) {
360 mStreamsToUpload = new ArrayList<Parcelable>();
361 mStreamsToUpload.add(getIntent().getParcelableExtra(Intent.EXTRA_STREAM));
362 } else if (getIntent().getAction().equals(Intent.ACTION_SEND_MULTIPLE)) {
363 mStreamsToUpload = getIntent().getParcelableArrayListExtra(Intent.EXTRA_STREAM);
364 } else {
365 // unknow action inserted
366 throw new IllegalArgumentException("Unknown action given: " + getIntent().getAction());
367 }
368 }
369
370 public void PartialupdateUpload(String fileLocalPath, String filename, String filepath, String contentType, String contentLength) {
371 ContentValues cv = new ContentValues();
372 cv.put(ProviderTableMeta.FILE_NAME, filename);
373 cv.put(ProviderTableMeta.FILE_PATH, filepath);
374 cv.put(ProviderTableMeta.FILE_STORAGE_PATH, fileLocalPath);
375 cv.put(ProviderTableMeta.FILE_MODIFIED, WebdavUtils.DISPLAY_DATE_FORMAT.format(new java.util.Date()));
376 cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, contentType);
377 cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, contentLength);
378 cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, mAccount.name);
379 Log.d(TAG, filename+" ++ "+filepath+" ++ " + contentLength + " ++ " + contentType + " ++ " + fileLocalPath);
380 if (!mParents.empty()) {
381 Cursor c = managedQuery(Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_FILE, mParents.peek()),
382 null,
383 null,
384 null,
385 null);
386 c.moveToFirst();
387 cv.put(ProviderTableMeta.FILE_PARENT, c.getString(c.getColumnIndex(ProviderTableMeta._ID)));
388 c.close();
389 }
390 getContentResolver().insert(ProviderTableMeta.CONTENT_URI_FILE, cv);
391 }
392
393 class BackgroundUploader implements Runnable {
394 private ArrayList<Parcelable> mUploadStreams;
395 private Handler mHandler;
396 private String mUploadPath;
397 private boolean mCreateDir;
398
399 public BackgroundUploader(String pathToUpload, ArrayList<Parcelable> streamsToUpload,
400 Handler handler) {
401 mUploadStreams = streamsToUpload;
402 mHandler = handler;
403 mUploadPath = pathToUpload.replace(" ", "%20");
404 mCreateDir = false;
405 }
406
407 public BackgroundUploader(String pathToUpload, ArrayList<Parcelable> streamsToUpload,
408 Handler handler, boolean createDir) {
409 mUploadStreams = streamsToUpload;
410 mHandler = handler;
411 mUploadPath = pathToUpload.replace(" ", "%20");
412 mCreateDir = createDir;
413 }
414
415 public void run() {
416 WebdavClient wdc = new WebdavClient(Uri.parse(mAccountManager.getUserData(mAccount,
417 AccountAuthenticator.KEY_OC_URL)));
418 wdc.setCredentials(mUsername, mPassword);
419 wdc.allowUnsignedCertificates();
420
421 // create last directory in path if nessesary
422 if (mCreateDir) {
423 wdc.createDirectory(mUploadPath);
424 }
425
426 for (int i = 0; i < mUploadStreams.size(); ++i) {
427 Uri uri = (Uri) mUploadStreams.get(i);
428 if (uri.getScheme().equals("content")) {
429 final Cursor c = getContentResolver().query((Uri) mUploadStreams.get(i), null, null, null, null);
430 c.moveToFirst();
431
432 if (!wdc.putFile(c.getString(c.getColumnIndex(Media.DATA)),
433 mUploadPath+"/"+c.getString(c.getColumnIndex(Media.DISPLAY_NAME)),
434 c.getString(c.getColumnIndex(Media.MIME_TYPE)))) {
435 mHandler.post(new Runnable() {
436 public void run() {
437 Uploader.this.onUploadComplete(false, "Error while uploading file: " + c.getString(c.getColumnIndex(Media.DISPLAY_NAME)));
438 }
439 });
440 } else {
441 mHandler.post(new Runnable() {
442 public void run() {
443 Uploader.this.PartialupdateUpload(c.getString(c.getColumnIndex(Media.DATA)),
444 c.getString(c.getColumnIndex(Media.DISPLAY_NAME)),
445 mUploadPath+"/"+c.getString(c.getColumnIndex(Media.DISPLAY_NAME)),
446 c.getString(c.getColumnIndex(Media.MIME_TYPE)),
447 c.getString(c.getColumnIndex(Media.SIZE)));
448 }
449 });
450 }
451 } else if (uri.getScheme().equals("file")) {
452 final File file = new File(Uri.decode(uri.toString()).replace(uri.getScheme()+"://", ""));
453 FileNameMap fileNameMap = URLConnection.getFileNameMap();
454 String contentType = fileNameMap.getContentTypeFor(uri.toString());
455 if (contentType == null) {
456 contentType = "text/plain";
457 }
458 if (!wdc.putFile(file.getAbsolutePath(), mUploadPath+"/"+file.getName(), contentType)) {
459 mHandler.post(new Runnable() {
460 public void run() {
461 Uploader.this.onUploadComplete(false, "Error while uploading file: " + file.getName());
462 }
463 });
464 }
465 }
466
467 }
468 mHandler.post(new Runnable() {
469 public void run() {
470 Uploader.this.onUploadComplete(true, null);
471 }
472 });
473 }
474
475 }
476
477 }