Used Log_OC in all the logs
[pub/Android/ownCloud.git] / src / com / owncloud / android / files / services / FileUploader.java
1 /* ownCloud Android client application
2 * Copyright (C) 2012 Bartek Przybylski
3 * Copyright (C) 2012-2013 ownCloud Inc.
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20 package com.owncloud.android.files.services;
21
22 import java.io.File;
23 import java.util.AbstractList;
24 import java.util.HashMap;
25 import java.util.Iterator;
26 import java.util.Map;
27 import java.util.Vector;
28 import java.util.concurrent.ConcurrentHashMap;
29 import java.util.concurrent.ConcurrentMap;
30
31 import org.apache.http.HttpStatus;
32 import org.apache.jackrabbit.webdav.MultiStatus;
33 import org.apache.jackrabbit.webdav.client.methods.PropFindMethod;
34
35 import android.accounts.Account;
36 import android.accounts.AccountManager;
37 import android.app.Notification;
38 import android.app.NotificationManager;
39 import android.app.PendingIntent;
40 import android.app.Service;
41 import android.content.Intent;
42 import android.os.Binder;
43 import android.os.Handler;
44 import android.os.HandlerThread;
45 import android.os.IBinder;
46 import android.os.Looper;
47 import android.os.Message;
48 import android.os.Process;
49 import android.util.Log;
50 import android.webkit.MimeTypeMap;
51 import android.widget.RemoteViews;
52 import android.widget.Toast;
53
54 import com.owncloud.android.Log_OC;
55 import com.owncloud.android.R;
56 import com.owncloud.android.authenticator.AccountAuthenticator;
57 import com.owncloud.android.datamodel.FileDataStorageManager;
58 import com.owncloud.android.datamodel.OCFile;
59 import com.owncloud.android.db.DbHandler;
60 import com.owncloud.android.network.OwnCloudClientUtils;
61 import com.owncloud.android.operations.ChunkedUploadFileOperation;
62 import com.owncloud.android.operations.RemoteOperationResult;
63 import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
64 import com.owncloud.android.operations.UploadFileOperation;
65 import com.owncloud.android.ui.activity.FailedUploadActivity;
66 import com.owncloud.android.ui.activity.FileDetailActivity;
67 import com.owncloud.android.ui.activity.InstantUploadActivity;
68 import com.owncloud.android.ui.fragment.FileDetailFragment;
69 import com.owncloud.android.ui.preview.PreviewImageActivity;
70 import com.owncloud.android.ui.preview.PreviewImageFragment;
71 import com.owncloud.android.utils.OwnCloudVersion;
72
73 import eu.alefzero.webdav.OnDatatransferProgressListener;
74 import eu.alefzero.webdav.WebdavClient;
75 import eu.alefzero.webdav.WebdavEntry;
76 import eu.alefzero.webdav.WebdavUtils;
77
78 public class FileUploader extends Service implements OnDatatransferProgressListener {
79
80 public static final String UPLOAD_FINISH_MESSAGE = "UPLOAD_FINISH";
81 public static final String EXTRA_UPLOAD_RESULT = "RESULT";
82 public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH";
83 public static final String EXTRA_OLD_REMOTE_PATH = "OLD_REMOTE_PATH";
84 public static final String EXTRA_OLD_FILE_PATH = "OLD_FILE_PATH";
85 public static final String ACCOUNT_NAME = "ACCOUNT_NAME";
86
87 public static final String KEY_FILE = "FILE";
88 public static final String KEY_LOCAL_FILE = "LOCAL_FILE";
89 public static final String KEY_REMOTE_FILE = "REMOTE_FILE";
90 public static final String KEY_MIME_TYPE = "MIME_TYPE";
91
92 public static final String KEY_ACCOUNT = "ACCOUNT";
93
94 public static final String KEY_UPLOAD_TYPE = "UPLOAD_TYPE";
95 public static final String KEY_FORCE_OVERWRITE = "KEY_FORCE_OVERWRITE";
96 public static final String KEY_INSTANT_UPLOAD = "INSTANT_UPLOAD";
97 public static final String KEY_LOCAL_BEHAVIOUR = "BEHAVIOUR";
98
99 public static final int LOCAL_BEHAVIOUR_COPY = 0;
100 public static final int LOCAL_BEHAVIOUR_MOVE = 1;
101 public static final int LOCAL_BEHAVIOUR_FORGET = 2;
102
103 public static final int UPLOAD_SINGLE_FILE = 0;
104 public static final int UPLOAD_MULTIPLE_FILES = 1;
105
106 private static final String TAG = FileUploader.class.getSimpleName();
107
108 private Looper mServiceLooper;
109 private ServiceHandler mServiceHandler;
110 private IBinder mBinder;
111 private WebdavClient mUploadClient = null;
112 private Account mLastAccount = null;
113 private FileDataStorageManager mStorageManager;
114
115 private ConcurrentMap<String, UploadFileOperation> mPendingUploads = new ConcurrentHashMap<String, UploadFileOperation>();
116 private UploadFileOperation mCurrentUpload = null;
117
118 private NotificationManager mNotificationManager;
119 private Notification mNotification;
120 private int mLastPercent;
121 private RemoteViews mDefaultNotificationContentView;
122
123 /**
124 * Builds a key for mPendingUploads from the account and file to upload
125 *
126 * @param account Account where the file to upload is stored
127 * @param file File to upload
128 */
129 private String buildRemoteName(Account account, OCFile file) {
130 return account.name + file.getRemotePath();
131 }
132
133 private String buildRemoteName(Account account, String remotePath) {
134 return account.name + remotePath;
135 }
136
137 /**
138 * Checks if an ownCloud server version should support chunked uploads.
139 *
140 * @param version OwnCloud version instance corresponding to an ownCloud
141 * server.
142 * @return 'True' if the ownCloud server with version supports chunked
143 * uploads.
144 */
145 private static boolean chunkedUploadIsSupported(OwnCloudVersion version) {
146 return (version != null && version.compareTo(OwnCloudVersion.owncloud_v4_5) >= 0);
147 }
148
149 /**
150 * Service initialization
151 */
152 @Override
153 public void onCreate() {
154 super.onCreate();
155 Log_OC.i(TAG, "mPendingUploads size:" + mPendingUploads.size());
156 mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
157 HandlerThread thread = new HandlerThread("FileUploaderThread", Process.THREAD_PRIORITY_BACKGROUND);
158 thread.start();
159 mServiceLooper = thread.getLooper();
160 mServiceHandler = new ServiceHandler(mServiceLooper, this);
161 mBinder = new FileUploaderBinder();
162 }
163
164 /**
165 * Entry point to add one or several files to the queue of uploads.
166 *
167 * New uploads are added calling to startService(), resulting in a call to
168 * this method. This ensures the service will keep on working although the
169 * caller activity goes away.
170 */
171 @Override
172 public int onStartCommand(Intent intent, int flags, int startId) {
173 if (!intent.hasExtra(KEY_ACCOUNT) || !intent.hasExtra(KEY_UPLOAD_TYPE)
174 || !(intent.hasExtra(KEY_LOCAL_FILE) || intent.hasExtra(KEY_FILE))) {
175 Log_OC.e(TAG, "Not enough information provided in intent");
176 return Service.START_NOT_STICKY;
177 }
178 int uploadType = intent.getIntExtra(KEY_UPLOAD_TYPE, -1);
179 if (uploadType == -1) {
180 Log_OC.e(TAG, "Incorrect upload type provided");
181 return Service.START_NOT_STICKY;
182 }
183 Account account = intent.getParcelableExtra(KEY_ACCOUNT);
184
185 String[] localPaths = null, remotePaths = null, mimeTypes = null;
186 OCFile[] files = null;
187 if (uploadType == UPLOAD_SINGLE_FILE) {
188
189 if (intent.hasExtra(KEY_FILE)) {
190 files = new OCFile[] { intent.getParcelableExtra(KEY_FILE) };
191
192 } else {
193 localPaths = new String[] { intent.getStringExtra(KEY_LOCAL_FILE) };
194 remotePaths = new String[] { intent.getStringExtra(KEY_REMOTE_FILE) };
195 mimeTypes = new String[] { intent.getStringExtra(KEY_MIME_TYPE) };
196 }
197
198 } else { // mUploadType == UPLOAD_MULTIPLE_FILES
199
200 if (intent.hasExtra(KEY_FILE)) {
201 files = (OCFile[]) intent.getParcelableArrayExtra(KEY_FILE); // TODO
202 // will
203 // this
204 // casting
205 // work
206 // fine?
207
208 } else {
209 localPaths = intent.getStringArrayExtra(KEY_LOCAL_FILE);
210 remotePaths = intent.getStringArrayExtra(KEY_REMOTE_FILE);
211 mimeTypes = intent.getStringArrayExtra(KEY_MIME_TYPE);
212 }
213 }
214
215 FileDataStorageManager storageManager = new FileDataStorageManager(account, getContentResolver());
216
217 boolean forceOverwrite = intent.getBooleanExtra(KEY_FORCE_OVERWRITE, false);
218 boolean isInstant = intent.getBooleanExtra(KEY_INSTANT_UPLOAD, false);
219 int localAction = intent.getIntExtra(KEY_LOCAL_BEHAVIOUR, LOCAL_BEHAVIOUR_COPY);
220 boolean fixed = false;
221 if (isInstant) {
222 fixed = checkAndFixInstantUploadDirectory(storageManager); // MUST
223 // be
224 // done
225 // BEFORE
226 // calling
227 // obtainNewOCFileToUpload
228 }
229
230 if (intent.hasExtra(KEY_FILE) && files == null) {
231 Log_OC.e(TAG, "Incorrect array for OCFiles provided in upload intent");
232 return Service.START_NOT_STICKY;
233
234 } else if (!intent.hasExtra(KEY_FILE)) {
235 if (localPaths == null) {
236 Log_OC.e(TAG, "Incorrect array for local paths provided in upload intent");
237 return Service.START_NOT_STICKY;
238 }
239 if (remotePaths == null) {
240 Log_OC.e(TAG, "Incorrect array for remote paths provided in upload intent");
241 return Service.START_NOT_STICKY;
242 }
243 if (localPaths.length != remotePaths.length) {
244 Log_OC.e(TAG, "Different number of remote paths and local paths!");
245 return Service.START_NOT_STICKY;
246 }
247
248 files = new OCFile[localPaths.length];
249 for (int i = 0; i < localPaths.length; i++) {
250 files[i] = obtainNewOCFileToUpload(remotePaths[i], localPaths[i], ((mimeTypes != null) ? mimeTypes[i]
251 : (String) null), storageManager);
252 if (files[i] == null) {
253 // TODO @andromaex add failure Notiification
254 return Service.START_NOT_STICKY;
255 }
256 }
257 }
258
259 OwnCloudVersion ocv = new OwnCloudVersion(AccountManager.get(this).getUserData(account,
260 AccountAuthenticator.KEY_OC_VERSION));
261 boolean chunked = FileUploader.chunkedUploadIsSupported(ocv);
262 AbstractList<String> requestedUploads = new Vector<String>();
263 String uploadKey = null;
264 UploadFileOperation newUpload = null;
265 try {
266 for (int i = 0; i < files.length; i++) {
267 uploadKey = buildRemoteName(account, files[i].getRemotePath());
268 if (chunked) {
269 newUpload = new ChunkedUploadFileOperation(account, files[i], isInstant, forceOverwrite,
270 localAction);
271 } else {
272 newUpload = new UploadFileOperation(account, files[i], isInstant, forceOverwrite, localAction);
273 }
274 if (fixed && i == 0) {
275 newUpload.setRemoteFolderToBeCreated();
276 }
277 mPendingUploads.putIfAbsent(uploadKey, newUpload);
278 newUpload.addDatatransferProgressListener(this);
279 newUpload.addDatatransferProgressListener((FileUploaderBinder)mBinder);
280 requestedUploads.add(uploadKey);
281 }
282
283 } catch (IllegalArgumentException e) {
284 Log_OC.e(TAG, "Not enough information provided in intent: " + e.getMessage());
285 return START_NOT_STICKY;
286
287 } catch (IllegalStateException e) {
288 Log_OC.e(TAG, "Bad information provided in intent: " + e.getMessage());
289 return START_NOT_STICKY;
290
291 } catch (Exception e) {
292 Log_OC.e(TAG, "Unexpected exception while processing upload intent", e);
293 return START_NOT_STICKY;
294
295 }
296
297 if (requestedUploads.size() > 0) {
298 Message msg = mServiceHandler.obtainMessage();
299 msg.arg1 = startId;
300 msg.obj = requestedUploads;
301 mServiceHandler.sendMessage(msg);
302 }
303 Log_OC.i(TAG, "mPendingUploads size:" + mPendingUploads.size());
304 return Service.START_NOT_STICKY;
305 }
306
307 /**
308 * Provides a binder object that clients can use to perform operations on
309 * the queue of uploads, excepting the addition of new files.
310 *
311 * Implemented to perform cancellation, pause and resume of existing
312 * uploads.
313 */
314 @Override
315 public IBinder onBind(Intent arg0) {
316 return mBinder;
317 }
318
319 /**
320 * Called when ALL the bound clients were onbound.
321 */
322 @Override
323 public boolean onUnbind(Intent intent) {
324 ((FileUploaderBinder)mBinder).clearListeners();
325 return false; // not accepting rebinding (default behaviour)
326 }
327
328
329 /**
330 * Binder to let client components to perform operations on the queue of
331 * uploads.
332 *
333 * It provides by itself the available operations.
334 */
335 public class FileUploaderBinder extends Binder implements OnDatatransferProgressListener {
336
337 /**
338 * Map of listeners that will be reported about progress of uploads from a {@link FileUploaderBinder} instance
339 */
340 private Map<String, OnDatatransferProgressListener> mBoundListeners = new HashMap<String, OnDatatransferProgressListener>();
341
342 /**
343 * Cancels a pending or current upload of a remote file.
344 *
345 * @param account Owncloud account where the remote file will be stored.
346 * @param file A file in the queue of pending uploads
347 */
348 public void cancel(Account account, OCFile file) {
349 UploadFileOperation upload = null;
350 synchronized (mPendingUploads) {
351 upload = mPendingUploads.remove(buildRemoteName(account, file));
352 }
353 if (upload != null) {
354 upload.cancel();
355 }
356 }
357
358
359
360 public void clearListeners() {
361 mBoundListeners.clear();
362 }
363
364
365
366
367 /**
368 * Returns True when the file described by 'file' is being uploaded to
369 * the ownCloud account 'account' or waiting for it
370 *
371 * If 'file' is a directory, returns 'true' if some of its descendant files is uploading or waiting to upload.
372 *
373 * @param account Owncloud account where the remote file will be stored.
374 * @param file A file that could be in the queue of pending uploads
375 */
376 public boolean isUploading(Account account, OCFile file) {
377 if (account == null || file == null)
378 return false;
379 String targetKey = buildRemoteName(account, file);
380 synchronized (mPendingUploads) {
381 if (file.isDirectory()) {
382 // this can be slow if there are many uploads :(
383 Iterator<String> it = mPendingUploads.keySet().iterator();
384 boolean found = false;
385 while (it.hasNext() && !found) {
386 found = it.next().startsWith(targetKey);
387 }
388 return found;
389 } else {
390 return (mPendingUploads.containsKey(targetKey));
391 }
392 }
393 }
394
395
396 /**
397 * Adds a listener interested in the progress of the upload for a concrete file.
398 *
399 * @param listener Object to notify about progress of transfer.
400 * @param account ownCloud account holding the file of interest.
401 * @param file {@link OCfile} of interest for listener.
402 */
403 public void addDatatransferProgressListener (OnDatatransferProgressListener listener, Account account, OCFile file) {
404 if (account == null || file == null || listener == null) return;
405 String targetKey = buildRemoteName(account, file);
406 mBoundListeners.put(targetKey, listener);
407 }
408
409
410
411 /**
412 * Removes a listener interested in the progress of the upload for a concrete file.
413 *
414 * @param listener Object to notify about progress of transfer.
415 * @param account ownCloud account holding the file of interest.
416 * @param file {@link OCfile} of interest for listener.
417 */
418 public void removeDatatransferProgressListener (OnDatatransferProgressListener listener, Account account, OCFile file) {
419 if (account == null || file == null || listener == null) return;
420 String targetKey = buildRemoteName(account, file);
421 if (mBoundListeners.get(targetKey) == listener) {
422 mBoundListeners.remove(targetKey);
423 }
424 }
425
426
427 @Override
428 public void onTransferProgress(long progressRate) {
429 // old way, should not be in use any more
430 }
431
432
433 @Override
434 public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer,
435 String fileName) {
436 String key = buildRemoteName(mCurrentUpload.getAccount(), mCurrentUpload.getFile());
437 OnDatatransferProgressListener boundListener = mBoundListeners.get(key);
438 if (boundListener != null) {
439 boundListener.onTransferProgress(progressRate, totalTransferredSoFar, totalToTransfer, fileName);
440 }
441 }
442
443 }
444
445 /**
446 * Upload worker. Performs the pending uploads in the order they were
447 * requested.
448 *
449 * Created with the Looper of a new thread, started in
450 * {@link FileUploader#onCreate()}.
451 */
452 private static class ServiceHandler extends Handler {
453 // don't make it a final class, and don't remove the static ; lint will
454 // warn about a possible memory leak
455 FileUploader mService;
456
457 public ServiceHandler(Looper looper, FileUploader service) {
458 super(looper);
459 if (service == null)
460 throw new IllegalArgumentException("Received invalid NULL in parameter 'service'");
461 mService = service;
462 }
463
464 @Override
465 public void handleMessage(Message msg) {
466 @SuppressWarnings("unchecked")
467 AbstractList<String> requestedUploads = (AbstractList<String>) msg.obj;
468 if (msg.obj != null) {
469 Iterator<String> it = requestedUploads.iterator();
470 while (it.hasNext()) {
471 mService.uploadFile(it.next());
472 }
473 }
474 mService.stopSelf(msg.arg1);
475 }
476 }
477
478 /**
479 * Core upload method: sends the file(s) to upload
480 *
481 * @param uploadKey Key to access the upload to perform, contained in
482 * mPendingUploads
483 */
484 public void uploadFile(String uploadKey) {
485
486 synchronized (mPendingUploads) {
487 mCurrentUpload = mPendingUploads.get(uploadKey);
488 }
489
490 if (mCurrentUpload != null) {
491
492 notifyUploadStart(mCurrentUpload);
493
494 // / prepare client object to send requests to the ownCloud server
495 if (mUploadClient == null || !mLastAccount.equals(mCurrentUpload.getAccount())) {
496 mLastAccount = mCurrentUpload.getAccount();
497 mStorageManager = new FileDataStorageManager(mLastAccount, getContentResolver());
498 mUploadClient = OwnCloudClientUtils.createOwnCloudClient(mLastAccount, getApplicationContext());
499 }
500
501 // / create remote folder for instant uploads
502 if (mCurrentUpload.isRemoteFolderToBeCreated()) {
503 mUploadClient.createDirectory(InstantUploadService.INSTANT_UPLOAD_DIR);
504 // ignoring result fail could just mean that it already exists,
505 // but local database is not synchronized the upload will be
506 // tried anyway
507 }
508
509 // / perform the upload
510 RemoteOperationResult uploadResult = null;
511 try {
512 uploadResult = mCurrentUpload.execute(mUploadClient);
513 if (uploadResult.isSuccess()) {
514 saveUploadedFile();
515 }
516
517 } finally {
518 synchronized (mPendingUploads) {
519 mPendingUploads.remove(uploadKey);
520 Log_OC.i(TAG, "Remove CurrentUploadItem from pending upload Item Map.");
521 }
522 }
523
524 // notify result
525 notifyUploadResult(uploadResult, mCurrentUpload);
526 sendFinalBroadcast(mCurrentUpload, uploadResult);
527
528 }
529
530 }
531
532 /**
533 * Saves a OC File after a successful upload.
534 *
535 * A PROPFIND is necessary to keep the props in the local database
536 * synchronized with the server, specially the modification time and Etag
537 * (where available)
538 *
539 * TODO refactor this ugly thing
540 */
541 private void saveUploadedFile() {
542 OCFile file = mCurrentUpload.getFile();
543 long syncDate = System.currentTimeMillis();
544 file.setLastSyncDateForData(syncDate);
545
546 // / new PROPFIND to keep data consistent with server in theory, should
547 // return the same we already have
548 PropFindMethod propfind = null;
549 RemoteOperationResult result = null;
550 try {
551 propfind = new PropFindMethod(mUploadClient.getBaseUri()
552 + WebdavUtils.encodePath(mCurrentUpload.getRemotePath()));
553 int status = mUploadClient.executeMethod(propfind);
554 boolean isMultiStatus = (status == HttpStatus.SC_MULTI_STATUS);
555 if (isMultiStatus) {
556 MultiStatus resp = propfind.getResponseBodyAsMultiStatus();
557 WebdavEntry we = new WebdavEntry(resp.getResponses()[0], mUploadClient.getBaseUri().getPath());
558 updateOCFile(file, we);
559 file.setLastSyncDateForProperties(syncDate);
560
561 } else {
562 mUploadClient.exhaustResponse(propfind.getResponseBodyAsStream());
563 }
564
565 result = new RemoteOperationResult(isMultiStatus, status);
566 Log_OC.i(TAG, "Update: synchronizing properties for uploaded " + mCurrentUpload.getRemotePath() + ": "
567 + result.getLogMessage());
568
569 } catch (Exception e) {
570 result = new RemoteOperationResult(e);
571 Log_OC.e(TAG, "Update: synchronizing properties for uploaded " + mCurrentUpload.getRemotePath() + ": "
572 + result.getLogMessage(), e);
573
574 } finally {
575 if (propfind != null)
576 propfind.releaseConnection();
577 }
578
579 // / maybe this would be better as part of UploadFileOperation... or
580 // maybe all this method
581 if (mCurrentUpload.wasRenamed()) {
582 OCFile oldFile = mCurrentUpload.getOldFile();
583 if (oldFile.fileExists()) {
584 oldFile.setStoragePath(null);
585 mStorageManager.saveFile(oldFile);
586
587 } // else: it was just an automatic renaming due to a name
588 // coincidence; nothing else is needed, the storagePath is right
589 // in the instance returned by mCurrentUpload.getFile()
590 }
591
592 mStorageManager.saveFile(file);
593 }
594
595 private void updateOCFile(OCFile file, WebdavEntry we) {
596 file.setCreationTimestamp(we.createTimestamp());
597 file.setFileLength(we.contentLength());
598 file.setMimetype(we.contentType());
599 file.setModificationTimestamp(we.modifiedTimestamp());
600 file.setModificationTimestampAtLastSyncForData(we.modifiedTimestamp());
601 // file.setEtag(mCurrentUpload.getEtag()); // TODO Etag, where available
602 }
603
604 private boolean checkAndFixInstantUploadDirectory(FileDataStorageManager storageManager) {
605 OCFile instantUploadDir = storageManager.getFileByPath(InstantUploadService.INSTANT_UPLOAD_DIR);
606 if (instantUploadDir == null) {
607 // first instant upload in the account, or never account not
608 // synchronized after the remote InstantUpload folder was created
609 OCFile newDir = new OCFile(InstantUploadService.INSTANT_UPLOAD_DIR);
610 newDir.setMimetype("DIR");
611 OCFile path = storageManager.getFileByPath(OCFile.PATH_SEPARATOR);
612
613 if (path != null) {
614 newDir.setParentId(path.getFileId());
615 storageManager.saveFile(newDir);
616 return true;
617 } else {
618 return false;
619 }
620
621 }
622 return false;
623 }
624
625 private OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType,
626 FileDataStorageManager storageManager) {
627 OCFile newFile = new OCFile(remotePath);
628 newFile.setStoragePath(localPath);
629 newFile.setLastSyncDateForProperties(0);
630 newFile.setLastSyncDateForData(0);
631
632 // size
633 if (localPath != null && localPath.length() > 0) {
634 File localFile = new File(localPath);
635 newFile.setFileLength(localFile.length());
636 newFile.setLastSyncDateForData(localFile.lastModified());
637 } // don't worry about not assigning size, the problems with localPath
638 // are checked when the UploadFileOperation instance is created
639
640 // MIME type
641 if (mimeType == null || mimeType.length() <= 0) {
642 try {
643 mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
644 remotePath.substring(remotePath.lastIndexOf('.') + 1));
645 } catch (IndexOutOfBoundsException e) {
646 Log_OC.e(TAG, "Trying to find out MIME type of a file without extension: " + remotePath);
647 }
648 }
649 if (mimeType == null) {
650 mimeType = "application/octet-stream";
651 }
652 newFile.setMimetype(mimeType);
653
654 // parent dir
655 String parentPath = new File(remotePath).getParent();
656 parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR;
657 OCFile parentDir = storageManager.getFileByPath(parentPath);
658 if (parentDir == null) {
659 Toast t = Toast
660 .makeText(
661 getApplicationContext(),
662 "The first time the InstantUpload is running you must be online, so the target folder can successfully created by the upload process",
663 30);
664 t.show();
665 return null;
666 }
667 long parentDirId = parentDir.getFileId();
668 newFile.setParentId(parentDirId);
669 return newFile;
670 }
671
672 /**
673 * Creates a status notification to show the upload progress
674 *
675 * @param upload Upload operation starting.
676 */
677 @SuppressWarnings("deprecation")
678 private void notifyUploadStart(UploadFileOperation upload) {
679 // / create status notification with a progress bar
680 mLastPercent = 0;
681 mNotification = new Notification(R.drawable.icon, getString(R.string.uploader_upload_in_progress_ticker),
682 System.currentTimeMillis());
683 mNotification.flags |= Notification.FLAG_ONGOING_EVENT;
684 mDefaultNotificationContentView = mNotification.contentView;
685 mNotification.contentView = new RemoteViews(getApplicationContext().getPackageName(),
686 R.layout.progressbar_layout);
687 mNotification.contentView.setProgressBar(R.id.status_progress, 100, 0, false);
688 mNotification.contentView.setTextViewText(R.id.status_text,
689 String.format(getString(R.string.uploader_upload_in_progress_content), 0, upload.getFileName()));
690 mNotification.contentView.setImageViewResource(R.id.status_icon, R.drawable.icon);
691
692 /// includes a pending intent in the notification showing the details view of the file
693 Intent showDetailsIntent = null;
694 if (PreviewImageFragment.canBePreviewed(upload.getFile())) {
695 showDetailsIntent = new Intent(this, PreviewImageActivity.class);
696 } else {
697 showDetailsIntent = new Intent(this, FileDetailActivity.class);
698 showDetailsIntent.putExtra(FileDetailActivity.EXTRA_MODE, FileDetailActivity.MODE_DETAILS);
699 }
700 showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, upload.getFile());
701 showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, upload.getAccount());
702 showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
703 mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(),
704 (int) System.currentTimeMillis(), showDetailsIntent, 0);
705
706 mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotification);
707 }
708
709 /**
710 * Callback method to update the progress bar in the status notification
711 */
712 @Override
713 public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String fileName) {
714 int percent = (int) (100.0 * ((double) totalTransferredSoFar) / ((double) totalToTransfer));
715 if (percent != mLastPercent) {
716 mNotification.contentView.setProgressBar(R.id.status_progress, 100, percent, false);
717 String text = String.format(getString(R.string.uploader_upload_in_progress_content), percent, fileName);
718 mNotification.contentView.setTextViewText(R.id.status_text, text);
719 mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotification);
720 }
721 mLastPercent = percent;
722 }
723
724 /**
725 * Callback method to update the progress bar in the status notification
726 * (old version)
727 */
728 @Override
729 public void onTransferProgress(long progressRate) {
730 // NOTHING TO DO HERE ANYMORE
731 }
732
733 /**
734 * Updates the status notification with the result of an upload operation.
735 *
736 * @param uploadResult Result of the upload operation.
737 * @param upload Finished upload operation
738 */
739 private void notifyUploadResult(RemoteOperationResult uploadResult, UploadFileOperation upload) {
740 Log_OC.d(TAG, "NotifyUploadResult with resultCode: " + uploadResult.getCode());
741 if (uploadResult.isCancelled()) {
742 // / cancelled operation -> silent removal of progress notification
743 mNotificationManager.cancel(R.string.uploader_upload_in_progress_ticker);
744
745 } else if (uploadResult.isSuccess()) {
746 // / success -> silent update of progress notification to success
747 // message
748 mNotification.flags ^= Notification.FLAG_ONGOING_EVENT; // remove
749 // the
750 // ongoing
751 // flag
752 mNotification.flags |= Notification.FLAG_AUTO_CANCEL;
753 mNotification.contentView = mDefaultNotificationContentView;
754
755 /// includes a pending intent in the notification showing the details view of the file
756 Intent showDetailsIntent = null;
757 if (PreviewImageFragment.canBePreviewed(upload.getFile())) {
758 showDetailsIntent = new Intent(this, PreviewImageActivity.class);
759 } else {
760 showDetailsIntent = new Intent(this, FileDetailActivity.class);
761 showDetailsIntent.putExtra(FileDetailActivity.EXTRA_MODE, FileDetailActivity.MODE_DETAILS);
762 }
763 showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, upload.getFile());
764 showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, upload.getAccount());
765 showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
766 mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(),
767 (int) System.currentTimeMillis(), showDetailsIntent, 0);
768
769 mNotification.setLatestEventInfo(getApplicationContext(),
770 getString(R.string.uploader_upload_succeeded_ticker),
771 String.format(getString(R.string.uploader_upload_succeeded_content_single), upload.getFileName()),
772 mNotification.contentIntent);
773
774 mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotification); // NOT
775 // AN
776 DbHandler db = new DbHandler(this.getBaseContext());
777 db.removeIUPendingFile(mCurrentUpload.getFile().getStoragePath());
778 db.close();
779
780 } else {
781
782 // / fail -> explicit failure notification
783 mNotificationManager.cancel(R.string.uploader_upload_in_progress_ticker);
784 Notification finalNotification = new Notification(R.drawable.icon,
785 getString(R.string.uploader_upload_failed_ticker), System.currentTimeMillis());
786 finalNotification.flags |= Notification.FLAG_AUTO_CANCEL;
787
788 String content = null;
789 if (uploadResult.getCode() == ResultCode.LOCAL_STORAGE_FULL
790 || uploadResult.getCode() == ResultCode.LOCAL_STORAGE_NOT_COPIED) {
791 // TODO we need a class to provide error messages for the users
792 // from a RemoteOperationResult and a RemoteOperation
793 content = String.format(getString(R.string.error__upload__local_file_not_copied), upload.getFileName(),
794 getString(R.string.app_name));
795 } else if (uploadResult.getCode() == ResultCode.QUOTA_EXCEEDED) {
796 content = getString(R.string.failed_upload_quota_exceeded_text);
797 } else {
798 content = String
799 .format(getString(R.string.uploader_upload_failed_content_single), upload.getFileName());
800 }
801
802 // we add only for instant-uploads the InstantUploadActivity and the
803 // db entry
804 Intent detailUploadIntent = null;
805 if (upload.isInstant() && InstantUploadActivity.IS_ENABLED) {
806 detailUploadIntent = new Intent(this, InstantUploadActivity.class);
807 detailUploadIntent.putExtra(FileUploader.KEY_ACCOUNT, upload.getAccount());
808 } else {
809 detailUploadIntent = new Intent(this, FailedUploadActivity.class);
810 detailUploadIntent.putExtra(FailedUploadActivity.MESSAGE, content);
811 }
812 finalNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(),
813 (int) System.currentTimeMillis(), detailUploadIntent, PendingIntent.FLAG_UPDATE_CURRENT
814 | PendingIntent.FLAG_ONE_SHOT);
815
816 if (upload.isInstant()) {
817 DbHandler db = null;
818 try {
819 db = new DbHandler(this.getBaseContext());
820 String message = uploadResult.getLogMessage() + " errorCode: " + uploadResult.getCode();
821 Log_OC.e(TAG, message + " Http-Code: " + uploadResult.getHttpCode());
822 if (uploadResult.getCode() == ResultCode.QUOTA_EXCEEDED) {
823 message = getString(R.string.failed_upload_quota_exceeded_text);
824 }
825 if (db.updateFileState(upload.getOriginalStoragePath(), DbHandler.UPLOAD_STATUS_UPLOAD_FAILED,
826 message) == 0) {
827 db.putFileForLater(upload.getOriginalStoragePath(), upload.getAccount().name, message);
828 }
829 } finally {
830 if (db != null) {
831 db.close();
832 }
833 }
834 }
835 finalNotification.setLatestEventInfo(getApplicationContext(),
836 getString(R.string.uploader_upload_failed_ticker), content, finalNotification.contentIntent);
837
838 mNotificationManager.notify(R.string.uploader_upload_failed_ticker, finalNotification);
839 }
840
841 }
842
843 /**
844 * Sends a broadcast in order to the interested activities can update their
845 * view
846 *
847 * @param upload Finished upload operation
848 * @param uploadResult Result of the upload operation
849 */
850 private void sendFinalBroadcast(UploadFileOperation upload, RemoteOperationResult uploadResult) {
851 Intent end = new Intent(UPLOAD_FINISH_MESSAGE);
852 end.putExtra(EXTRA_REMOTE_PATH, upload.getRemotePath()); // real remote
853 // path, after
854 // possible
855 // automatic
856 // renaming
857 if (upload.wasRenamed()) {
858 end.putExtra(EXTRA_OLD_REMOTE_PATH, upload.getOldFile().getRemotePath());
859 }
860 end.putExtra(EXTRA_OLD_FILE_PATH, upload.getOriginalStoragePath());
861 end.putExtra(ACCOUNT_NAME, upload.getAccount().name);
862 end.putExtra(EXTRA_UPLOAD_RESULT, uploadResult.isSuccess());
863 sendStickyBroadcast(end);
864 }
865
866 }