FileUploader.java 56 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405
  1. /**
  2. * ownCloud Android client application
  3. *
  4. * @author Bartek Przybylski
  5. * @author masensio
  6. * @author LukeOwnCloud
  7. * @author David A. Velasco
  8. * @author Chris Narkiewicz
  9. *
  10. * Copyright (C) 2012 Bartek Przybylski
  11. * Copyright (C) 2012-2016 ownCloud Inc.
  12. * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
  13. *
  14. * This program is free software: you can redistribute it and/or modify
  15. * it under the terms of the GNU General Public License version 2,
  16. * as published by the Free Software Foundation.
  17. *
  18. * This program is distributed in the hope that it will be useful,
  19. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. * GNU General Public License for more details.
  22. *
  23. * You should have received a copy of the GNU General Public License
  24. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  25. */
  26. package com.owncloud.android.files.services;
  27. import android.accounts.Account;
  28. import android.accounts.AccountManager;
  29. import android.accounts.OnAccountsUpdateListener;
  30. import android.annotation.SuppressLint;
  31. import android.app.Notification;
  32. import android.app.NotificationManager;
  33. import android.app.PendingIntent;
  34. import android.app.Service;
  35. import android.content.Context;
  36. import android.content.Intent;
  37. import android.graphics.BitmapFactory;
  38. import android.os.Binder;
  39. import android.os.Build;
  40. import android.os.Handler;
  41. import android.os.HandlerThread;
  42. import android.os.IBinder;
  43. import android.os.Looper;
  44. import android.os.Message;
  45. import android.os.Parcelable;
  46. import android.os.Process;
  47. import android.util.Pair;
  48. import com.nextcloud.client.account.User;
  49. import com.nextcloud.client.account.UserAccountManager;
  50. import com.nextcloud.client.device.BatteryStatus;
  51. import com.nextcloud.client.device.PowerManagementService;
  52. import com.nextcloud.client.network.Connectivity;
  53. import com.nextcloud.client.network.ConnectivityService;
  54. import com.nextcloud.java.util.Optional;
  55. import com.owncloud.android.MainApp;
  56. import com.owncloud.android.R;
  57. import com.owncloud.android.authentication.AuthenticatorActivity;
  58. import com.owncloud.android.datamodel.FileDataStorageManager;
  59. import com.owncloud.android.datamodel.OCFile;
  60. import com.owncloud.android.datamodel.ThumbnailsCacheManager;
  61. import com.owncloud.android.datamodel.UploadsStorageManager;
  62. import com.owncloud.android.datamodel.UploadsStorageManager.UploadStatus;
  63. import com.owncloud.android.db.OCUpload;
  64. import com.owncloud.android.db.UploadResult;
  65. import com.owncloud.android.lib.common.OwnCloudAccount;
  66. import com.owncloud.android.lib.common.OwnCloudClient;
  67. import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
  68. import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
  69. import com.owncloud.android.lib.common.operations.RemoteOperationResult;
  70. import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
  71. import com.owncloud.android.lib.common.utils.Log_OC;
  72. import com.owncloud.android.lib.resources.files.FileUtils;
  73. import com.owncloud.android.operations.UploadFileOperation;
  74. import com.owncloud.android.ui.activity.ConflictsResolveActivity;
  75. import com.owncloud.android.ui.activity.UploadListActivity;
  76. import com.owncloud.android.ui.notifications.NotificationUtils;
  77. import com.owncloud.android.utils.ErrorMessageAdapter;
  78. import com.owncloud.android.utils.theme.ThemeColorUtils;
  79. import java.io.File;
  80. import java.security.SecureRandom;
  81. import java.util.ArrayList;
  82. import java.util.HashMap;
  83. import java.util.List;
  84. import java.util.Map;
  85. import javax.annotation.Nullable;
  86. import javax.inject.Inject;
  87. import androidx.annotation.NonNull;
  88. import androidx.core.app.NotificationCompat;
  89. import androidx.localbroadcastmanager.content.LocalBroadcastManager;
  90. import dagger.android.AndroidInjection;
  91. import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
  92. /**
  93. * Service for uploading files. Invoke using context.startService(...).
  94. *
  95. * Files to be uploaded are stored persistently using {@link UploadsStorageManager}.
  96. *
  97. * On next invocation of {@link FileUploader} uploaded files which previously failed will be uploaded again until either
  98. * upload succeeded or a fatal error occurred.
  99. *
  100. * Every file passed to this service is uploaded. No filtering is performed. However, Intent keys (e.g., KEY_WIFI_ONLY)
  101. * are obeyed.
  102. */
  103. public class FileUploader extends Service
  104. implements OnDatatransferProgressListener, OnAccountsUpdateListener, UploadFileOperation.OnRenameListener {
  105. private static final String TAG = FileUploader.class.getSimpleName();
  106. private static final String UPLOADS_ADDED_MESSAGE = "UPLOADS_ADDED";
  107. private static final String UPLOAD_START_MESSAGE = "UPLOAD_START";
  108. private static final String UPLOAD_FINISH_MESSAGE = "UPLOAD_FINISH";
  109. public static final String EXTRA_UPLOAD_RESULT = "RESULT";
  110. public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH";
  111. public static final String EXTRA_OLD_REMOTE_PATH = "OLD_REMOTE_PATH";
  112. public static final String EXTRA_OLD_FILE_PATH = "OLD_FILE_PATH";
  113. public static final String EXTRA_LINKED_TO_PATH = "LINKED_TO";
  114. public static final String ACCOUNT_NAME = "ACCOUNT_NAME";
  115. private static final int FOREGROUND_SERVICE_ID = 411;
  116. public static final String KEY_FILE = "FILE";
  117. public static final String KEY_LOCAL_FILE = "LOCAL_FILE";
  118. public static final String KEY_REMOTE_FILE = "REMOTE_FILE";
  119. public static final String KEY_MIME_TYPE = "MIME_TYPE";
  120. /**
  121. * Call this Service with only this Intent key if all pending uploads are to be retried.
  122. */
  123. private static final String KEY_RETRY = "KEY_RETRY";
  124. // /**
  125. // * Call this Service with KEY_RETRY and KEY_RETRY_REMOTE_PATH to retry
  126. // * upload of file identified by KEY_RETRY_REMOTE_PATH.
  127. // */
  128. // private static final String KEY_RETRY_REMOTE_PATH = "KEY_RETRY_REMOTE_PATH";
  129. /**
  130. * Call this Service with KEY_RETRY and KEY_RETRY_UPLOAD to retry upload of file identified by KEY_RETRY_UPLOAD.
  131. */
  132. private static final String KEY_RETRY_UPLOAD = "KEY_RETRY_UPLOAD";
  133. /**
  134. * {@link Account} to which file is to be uploaded.
  135. */
  136. public static final String KEY_ACCOUNT = "ACCOUNT";
  137. /**
  138. * What {@link NameCollisionPolicy} to do when the file already exists on the remote.
  139. */
  140. public static final String KEY_NAME_COLLISION_POLICY = "KEY_NAME_COLLISION_POLICY";
  141. /**
  142. * Set to true if remote folder is to be created if it does not exist.
  143. */
  144. public static final String KEY_CREATE_REMOTE_FOLDER = "CREATE_REMOTE_FOLDER";
  145. /**
  146. * Key to signal what is the origin of the upload request
  147. */
  148. public static final String KEY_CREATED_BY = "CREATED_BY";
  149. public static final String KEY_WHILE_ON_WIFI_ONLY = "KEY_ON_WIFI_ONLY";
  150. /**
  151. * Set to true if upload is to performed only when phone is being charged.
  152. */
  153. public static final String KEY_WHILE_CHARGING_ONLY = "KEY_WHILE_CHARGING_ONLY";
  154. public static final String KEY_LOCAL_BEHAVIOUR = "BEHAVIOUR";
  155. /**
  156. * Set to true if the HTTP library should disable automatic retries of uploads.
  157. */
  158. public static final String KEY_DISABLE_RETRIES = "DISABLE_RETRIES";
  159. public static final int LOCAL_BEHAVIOUR_COPY = 0;
  160. public static final int LOCAL_BEHAVIOUR_MOVE = 1;
  161. public static final int LOCAL_BEHAVIOUR_FORGET = 2;
  162. public static final int LOCAL_BEHAVIOUR_DELETE = 3;
  163. private Notification mNotification;
  164. private Looper mServiceLooper;
  165. private ServiceHandler mServiceHandler;
  166. private IBinder mBinder;
  167. private OwnCloudClient mUploadClient;
  168. private Account mCurrentAccount;
  169. private FileDataStorageManager mStorageManager;
  170. @Inject UserAccountManager accountManager;
  171. @Inject UploadsStorageManager mUploadsStorageManager;
  172. @Inject ConnectivityService connectivityService;
  173. @Inject PowerManagementService powerManagementService;
  174. @Inject LocalBroadcastManager localBroadcastManager;
  175. private IndexedForest<UploadFileOperation> mPendingUploads = new IndexedForest<>();
  176. /**
  177. * {@link UploadFileOperation} object of ongoing upload. Can be null. Note: There can only be one concurrent
  178. * upload!
  179. */
  180. private UploadFileOperation mCurrentUpload;
  181. private NotificationManager mNotificationManager;
  182. private NotificationCompat.Builder mNotificationBuilder;
  183. private int mLastPercent;
  184. @Override
  185. public void onRenameUpload() {
  186. mUploadsStorageManager.updateDatabaseUploadStart(mCurrentUpload);
  187. sendBroadcastUploadStarted(mCurrentUpload);
  188. }
  189. /**
  190. * Service initialization
  191. */
  192. @Override
  193. public void onCreate() {
  194. super.onCreate();
  195. AndroidInjection.inject(this);
  196. Log_OC.d(TAG, "Creating service");
  197. mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
  198. HandlerThread thread = new HandlerThread("FileUploaderThread", Process.THREAD_PRIORITY_BACKGROUND);
  199. thread.start();
  200. mServiceLooper = thread.getLooper();
  201. mServiceHandler = new ServiceHandler(mServiceLooper, this);
  202. mBinder = new FileUploaderBinder();
  203. NotificationCompat.Builder builder = new NotificationCompat.Builder(this).setContentTitle(
  204. getApplicationContext().getResources().getString(R.string.app_name))
  205. .setContentText(getApplicationContext().getResources().getString(R.string.foreground_service_upload))
  206. .setSmallIcon(R.drawable.notification_icon)
  207. .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.notification_icon))
  208. .setColor(ThemeColorUtils.primaryColor(getApplicationContext(), true));
  209. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
  210. builder.setChannelId(NotificationUtils.NOTIFICATION_CHANNEL_UPLOAD);
  211. }
  212. mNotification = builder.build();
  213. // TODO Add UploadResult.KILLED?
  214. int failedCounter = mUploadsStorageManager.failInProgressUploads(UploadResult.SERVICE_INTERRUPTED);
  215. if (failedCounter > 0) {
  216. resurrection();
  217. }
  218. // add AccountsUpdatedListener
  219. AccountManager am = AccountManager.get(getApplicationContext());
  220. am.addOnAccountsUpdatedListener(this, null, false);
  221. }
  222. /**
  223. * Service clean-up when restarted after being killed
  224. */
  225. private void resurrection() {
  226. // remove stucked notification
  227. mNotificationManager.cancel(FOREGROUND_SERVICE_ID);
  228. }
  229. /**
  230. * Service clean up
  231. */
  232. @Override
  233. public void onDestroy() {
  234. Log_OC.v(TAG, "Destroying service");
  235. mBinder = null;
  236. mServiceHandler = null;
  237. mServiceLooper.quit();
  238. mServiceLooper = null;
  239. if (mNotificationManager != null) {
  240. mNotificationManager.cancel(FOREGROUND_SERVICE_ID);
  241. }
  242. // remove AccountsUpdatedListener
  243. AccountManager am = AccountManager.get(getApplicationContext());
  244. am.removeOnAccountsUpdatedListener(this);
  245. super.onDestroy();
  246. }
  247. /**
  248. * Entry point to add one or several files to the queue of uploads.
  249. *
  250. * New uploads are added calling to startService(), resulting in a call to this method. This ensures the service
  251. * will keep on working although the caller activity goes away.
  252. */
  253. @Override
  254. public int onStartCommand(Intent intent, int flags, int startId) {
  255. Log_OC.d(TAG, "Starting command with id " + startId);
  256. startForeground(FOREGROUND_SERVICE_ID, mNotification);
  257. if (intent == null) {
  258. Log_OC.e(TAG, "Intent is null");
  259. return Service.START_NOT_STICKY;
  260. }
  261. if (!intent.hasExtra(KEY_ACCOUNT)) {
  262. Log_OC.e(TAG, "Not enough information provided in intent");
  263. return Service.START_NOT_STICKY;
  264. }
  265. final Account account = intent.getParcelableExtra(KEY_ACCOUNT);
  266. if (account == null) {
  267. return Service.START_NOT_STICKY;
  268. }
  269. Optional<User> optionalUser = accountManager.getUser(account.name);
  270. if (!optionalUser.isPresent()) {
  271. return Service.START_NOT_STICKY;
  272. }
  273. final User user = optionalUser.get();
  274. boolean retry = intent.getBooleanExtra(KEY_RETRY, false);
  275. List<String> requestedUploads = new ArrayList<>();
  276. boolean onWifiOnly = intent.getBooleanExtra(KEY_WHILE_ON_WIFI_ONLY, false);
  277. boolean whileChargingOnly = intent.getBooleanExtra(KEY_WHILE_CHARGING_ONLY, false);
  278. if (!retry) { // Start new uploads
  279. if (!(intent.hasExtra(KEY_LOCAL_FILE) || intent.hasExtra(KEY_FILE))) {
  280. Log_OC.e(TAG, "Not enough information provided in intent");
  281. return Service.START_NOT_STICKY;
  282. }
  283. Integer error = gatherAndStartNewUploads(intent, user, requestedUploads, onWifiOnly, whileChargingOnly);
  284. if (error != null) {
  285. return error;
  286. }
  287. } else { // Retry uploads
  288. if (!intent.hasExtra(KEY_ACCOUNT) || !intent.hasExtra(KEY_RETRY_UPLOAD)) {
  289. Log_OC.e(TAG, "Not enough information provided in intent: no KEY_RETRY_UPLOAD_KEY");
  290. return START_NOT_STICKY;
  291. }
  292. retryUploads(intent, user, requestedUploads);
  293. }
  294. if (requestedUploads.size() > 0) {
  295. Message msg = mServiceHandler.obtainMessage();
  296. msg.arg1 = startId;
  297. msg.obj = requestedUploads;
  298. mServiceHandler.sendMessage(msg);
  299. sendBroadcastUploadsAdded();
  300. }
  301. return Service.START_NOT_STICKY;
  302. }
  303. /**
  304. * Gather and start new uploads.
  305. *
  306. * @return A {@link Service} constant in case of error, {@code null} otherwise.
  307. */
  308. @Nullable
  309. private Integer gatherAndStartNewUploads(
  310. Intent intent,
  311. User user,
  312. List<String> requestedUploads,
  313. boolean onWifiOnly,
  314. boolean whileChargingOnly
  315. ) {
  316. String[] localPaths = null;
  317. String[] remotePaths = null;
  318. String[] mimeTypes = null;
  319. OCFile[] files = null;
  320. if (intent.hasExtra(KEY_FILE)) {
  321. Parcelable[] files_temp = intent.getParcelableArrayExtra(KEY_FILE);
  322. files = new OCFile[files_temp.length];
  323. System.arraycopy(files_temp, 0, files, 0, files_temp.length);
  324. } else {
  325. localPaths = intent.getStringArrayExtra(KEY_LOCAL_FILE);
  326. remotePaths = intent.getStringArrayExtra(KEY_REMOTE_FILE);
  327. mimeTypes = intent.getStringArrayExtra(KEY_MIME_TYPE);
  328. }
  329. if (intent.hasExtra(KEY_FILE) && files == null) {
  330. Log_OC.e(TAG, "Incorrect array for OCFiles provided in upload intent");
  331. return Service.START_NOT_STICKY;
  332. } else if (!intent.hasExtra(KEY_FILE)) {
  333. if (localPaths == null) {
  334. Log_OC.e(TAG, "Incorrect array for local paths provided in upload intent");
  335. return Service.START_NOT_STICKY;
  336. }
  337. if (remotePaths == null) {
  338. Log_OC.e(TAG, "Incorrect array for remote paths provided in upload intent");
  339. return Service.START_NOT_STICKY;
  340. }
  341. if (localPaths.length != remotePaths.length) {
  342. Log_OC.e(TAG, "Different number of remote paths and local paths!");
  343. return Service.START_NOT_STICKY;
  344. }
  345. files = new OCFile[localPaths.length];
  346. for (int i = 0; i < localPaths.length; i++) {
  347. files[i] = UploadFileOperation.obtainNewOCFileToUpload(
  348. remotePaths[i],
  349. localPaths[i],
  350. mimeTypes != null ? mimeTypes[i] : null
  351. );
  352. if (files[i] == null) {
  353. Log_OC.e(TAG, "obtainNewOCFileToUpload() returned null for remotePaths[i]:" + remotePaths[i]
  354. + " and localPaths[i]:" + localPaths[i]);
  355. return Service.START_NOT_STICKY;
  356. }
  357. }
  358. }
  359. // at this point variable "OCFile[] files" is loaded correctly.
  360. NameCollisionPolicy nameCollisionPolicy = (NameCollisionPolicy) intent.getSerializableExtra(KEY_NAME_COLLISION_POLICY);
  361. if (nameCollisionPolicy == null) {
  362. nameCollisionPolicy = NameCollisionPolicy.DEFAULT;
  363. }
  364. int localAction = intent.getIntExtra(KEY_LOCAL_BEHAVIOUR, LOCAL_BEHAVIOUR_FORGET);
  365. boolean isCreateRemoteFolder = intent.getBooleanExtra(KEY_CREATE_REMOTE_FOLDER, false);
  366. int createdBy = intent.getIntExtra(KEY_CREATED_BY, UploadFileOperation.CREATED_BY_USER);
  367. boolean disableRetries = intent.getBooleanExtra(KEY_DISABLE_RETRIES, true);
  368. try {
  369. for (OCFile file : files) {
  370. startNewUpload(
  371. user,
  372. requestedUploads,
  373. onWifiOnly,
  374. whileChargingOnly,
  375. nameCollisionPolicy,
  376. localAction,
  377. isCreateRemoteFolder,
  378. createdBy,
  379. file,
  380. disableRetries
  381. );
  382. }
  383. } catch (IllegalArgumentException e) {
  384. Log_OC.e(TAG, "Not enough information provided in intent: " + e.getMessage());
  385. return START_NOT_STICKY;
  386. } catch (IllegalStateException e) {
  387. Log_OC.e(TAG, "Bad information provided in intent: " + e.getMessage());
  388. return START_NOT_STICKY;
  389. } catch (Exception e) {
  390. Log_OC.e(TAG, "Unexpected exception while processing upload intent", e);
  391. return START_NOT_STICKY;
  392. }
  393. return null;
  394. }
  395. /**
  396. * Start a new {@link UploadFileOperation}.
  397. */
  398. @SuppressLint("SdCardPath")
  399. private void startNewUpload(
  400. User user,
  401. List<String> requestedUploads,
  402. boolean onWifiOnly,
  403. boolean whileChargingOnly,
  404. NameCollisionPolicy nameCollisionPolicy,
  405. int localAction,
  406. boolean isCreateRemoteFolder,
  407. int createdBy,
  408. OCFile file,
  409. boolean disableRetries
  410. ) {
  411. if (file.getStoragePath().startsWith("/data/data/")) {
  412. Log_OC.d(TAG, "Upload from sensitive path is not allowed");
  413. return;
  414. }
  415. OCUpload ocUpload = new OCUpload(file, user);
  416. ocUpload.setFileSize(file.getFileLength());
  417. ocUpload.setNameCollisionPolicy(nameCollisionPolicy);
  418. ocUpload.setCreateRemoteFolder(isCreateRemoteFolder);
  419. ocUpload.setCreatedBy(createdBy);
  420. ocUpload.setLocalAction(localAction);
  421. ocUpload.setUseWifiOnly(onWifiOnly);
  422. ocUpload.setWhileChargingOnly(whileChargingOnly);
  423. ocUpload.setUploadStatus(UploadStatus.UPLOAD_IN_PROGRESS);
  424. UploadFileOperation newUpload = new UploadFileOperation(
  425. mUploadsStorageManager,
  426. connectivityService,
  427. powerManagementService,
  428. user,
  429. file,
  430. ocUpload,
  431. nameCollisionPolicy,
  432. localAction,
  433. this,
  434. onWifiOnly,
  435. whileChargingOnly,
  436. disableRetries,
  437. new FileDataStorageManager(user, getContentResolver())
  438. );
  439. newUpload.setCreatedBy(createdBy);
  440. if (isCreateRemoteFolder) {
  441. newUpload.setRemoteFolderToBeCreated();
  442. }
  443. newUpload.addDataTransferProgressListener(this);
  444. newUpload.addDataTransferProgressListener((FileUploaderBinder) mBinder);
  445. newUpload.addRenameUploadListener(this);
  446. Pair<String, String> putResult = mPendingUploads.putIfAbsent(
  447. user.getAccountName(),
  448. file.getRemotePath(),
  449. newUpload
  450. );
  451. if (putResult != null) {
  452. requestedUploads.add(putResult.first);
  453. // Save upload in database
  454. long id = mUploadsStorageManager.storeUpload(ocUpload);
  455. newUpload.setOCUploadId(id);
  456. }
  457. }
  458. /**
  459. * Retries a list of uploads.
  460. */
  461. private void retryUploads(Intent intent, User user, List<String> requestedUploads) {
  462. boolean onWifiOnly;
  463. boolean whileChargingOnly;
  464. OCUpload upload = intent.getParcelableExtra(KEY_RETRY_UPLOAD);
  465. onWifiOnly = upload.isUseWifiOnly();
  466. whileChargingOnly = upload.isWhileChargingOnly();
  467. UploadFileOperation newUpload = new UploadFileOperation(
  468. mUploadsStorageManager,
  469. connectivityService,
  470. powerManagementService,
  471. user,
  472. null,
  473. upload,
  474. upload.getNameCollisionPolicy(),
  475. upload.getLocalAction(),
  476. this,
  477. onWifiOnly,
  478. whileChargingOnly,
  479. true,
  480. new FileDataStorageManager(user, getContentResolver())
  481. );
  482. newUpload.addDataTransferProgressListener(this);
  483. newUpload.addDataTransferProgressListener((FileUploaderBinder) mBinder);
  484. newUpload.addRenameUploadListener(this);
  485. Pair<String, String> putResult = mPendingUploads.putIfAbsent(
  486. user.getAccountName(),
  487. upload.getRemotePath(),
  488. newUpload
  489. );
  490. if (putResult != null) {
  491. String uploadKey = putResult.first;
  492. requestedUploads.add(uploadKey);
  493. // Update upload in database
  494. upload.setUploadStatus(UploadStatus.UPLOAD_IN_PROGRESS);
  495. mUploadsStorageManager.updateUpload(upload);
  496. }
  497. }
  498. /**
  499. * Provides a binder object that clients can use to perform operations on the queue of uploads, excepting the
  500. * addition of new files.
  501. *
  502. * Implemented to perform cancellation, pause and resume of existing uploads.
  503. */
  504. @Override
  505. public IBinder onBind(Intent intent) {
  506. return mBinder;
  507. }
  508. /**
  509. * Called when ALL the bound clients were onbound.
  510. */
  511. @Override
  512. public boolean onUnbind(Intent intent) {
  513. ((FileUploaderBinder) mBinder).clearListeners();
  514. return false; // not accepting rebinding (default behaviour)
  515. }
  516. @Override
  517. public void onAccountsUpdated(Account[] accounts) {
  518. // Review current upload, and cancel it if its account doesn't exist
  519. if (mCurrentUpload != null && !accountManager.exists(mCurrentUpload.getAccount())) {
  520. mCurrentUpload.cancel(ResultCode.ACCOUNT_NOT_FOUND);
  521. }
  522. // The rest of uploads are cancelled when they try to start
  523. }
  524. /**
  525. * Core upload method: sends the file(s) to upload
  526. *
  527. * @param uploadKey Key to access the upload to perform, contained in mPendingUploads
  528. */
  529. public void uploadFile(String uploadKey) {
  530. mCurrentUpload = mPendingUploads.get(uploadKey);
  531. if (mCurrentUpload != null) {
  532. /// Check account existence
  533. if (!accountManager.exists(mCurrentUpload.getAccount())) {
  534. Log_OC.w(TAG, "Account " + mCurrentUpload.getAccount().name +
  535. " does not exist anymore -> cancelling all its uploads");
  536. cancelUploadsForAccount(mCurrentUpload.getAccount());
  537. return;
  538. }
  539. /// OK, let's upload
  540. mUploadsStorageManager.updateDatabaseUploadStart(mCurrentUpload);
  541. notifyUploadStart(mCurrentUpload);
  542. sendBroadcastUploadStarted(mCurrentUpload);
  543. RemoteOperationResult uploadResult = null;
  544. try {
  545. /// prepare client object to send the request to the ownCloud server
  546. if (mCurrentAccount == null || !mCurrentAccount.equals(mCurrentUpload.getAccount())) {
  547. mCurrentAccount = mCurrentUpload.getAccount();
  548. mStorageManager = new FileDataStorageManager(getCurrentUser().get(), getContentResolver());
  549. } // else, reuse storage manager from previous operation
  550. // always get client from client manager, to get fresh credentials in case of update
  551. OwnCloudAccount ocAccount = new OwnCloudAccount(mCurrentAccount, this);
  552. mUploadClient = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(ocAccount, this);
  553. // // If parent folder is encrypted, upload file encrypted
  554. // OCFile parent = mStorageManager.getFileByPath(mCurrentUpload.getFile().getParentRemotePath());
  555. // if (parent.isEncrypted()) {
  556. // UploadEncryptedFileOperation uploadEncryptedFileOperation =
  557. // new UploadEncryptedFileOperation(parent, mCurrentUpload);
  558. //
  559. // uploadResult = uploadEncryptedFileOperation.execute(mUploadClient, mStorageManager);
  560. // } else {
  561. /// perform the regular upload
  562. uploadResult = mCurrentUpload.execute(mUploadClient);
  563. // }
  564. } catch (Exception e) {
  565. Log_OC.e(TAG, "Error uploading", e);
  566. uploadResult = new RemoteOperationResult(e);
  567. } finally {
  568. Pair<UploadFileOperation, String> removeResult;
  569. if (mCurrentUpload.wasRenamed()) {
  570. removeResult = mPendingUploads.removePayload(
  571. mCurrentAccount.name,
  572. mCurrentUpload.getOldFile().getRemotePath()
  573. );
  574. // TODO: grant that name is also updated for mCurrentUpload.getOCUploadId
  575. } else {
  576. removeResult = mPendingUploads.removePayload(mCurrentAccount.name,
  577. mCurrentUpload.getDecryptedRemotePath());
  578. }
  579. mUploadsStorageManager.updateDatabaseUploadResult(uploadResult, mCurrentUpload);
  580. /// notify result
  581. notifyUploadResult(mCurrentUpload, uploadResult);
  582. sendBroadcastUploadFinished(mCurrentUpload, uploadResult, removeResult.second);
  583. }
  584. // generate new Thumbnail
  585. Optional<User> user = getCurrentUser();
  586. if (user.isPresent()) {
  587. final ThumbnailsCacheManager.ThumbnailGenerationTask task =
  588. new ThumbnailsCacheManager.ThumbnailGenerationTask(mStorageManager, user.get());
  589. File file = new File(mCurrentUpload.getOriginalStoragePath());
  590. String remoteId = mCurrentUpload.getFile().getRemoteId();
  591. task.execute(new ThumbnailsCacheManager.ThumbnailGenerationTaskObject(file, remoteId));
  592. }
  593. }
  594. }
  595. /**
  596. * Convert current account to user. This is a temporary workaround until
  597. * service is migrated to new user model.
  598. *
  599. * @return Optional {@link User}
  600. */
  601. private Optional<User> getCurrentUser() {
  602. if (mCurrentAccount == null) {
  603. return Optional.empty();
  604. } else {
  605. return accountManager.getUser(mCurrentAccount.name);
  606. }
  607. }
  608. /**
  609. * Creates a status notification to show the upload progress
  610. *
  611. * @param upload Upload operation starting.
  612. */
  613. private void notifyUploadStart(UploadFileOperation upload) {
  614. // / create status notification with a progress bar
  615. mLastPercent = 0;
  616. mNotificationBuilder = NotificationUtils.newNotificationBuilder(this);
  617. mNotificationBuilder
  618. .setOngoing(true)
  619. .setSmallIcon(R.drawable.notification_icon)
  620. .setTicker(getString(R.string.uploader_upload_in_progress_ticker))
  621. .setContentTitle(getString(R.string.uploader_upload_in_progress_ticker))
  622. .setProgress(100, 0, false)
  623. .setContentText(
  624. String.format(getString(R.string.uploader_upload_in_progress_content), 0, upload.getFileName())
  625. );
  626. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
  627. mNotificationBuilder.setChannelId(NotificationUtils.NOTIFICATION_CHANNEL_UPLOAD);
  628. }
  629. /// includes a pending intent in the notification showing the details
  630. Intent intent = UploadListActivity.createIntent(upload.getFile(),
  631. upload.getAccount(),
  632. Intent.FLAG_ACTIVITY_CLEAR_TOP,
  633. this);
  634. mNotificationBuilder.setContentIntent(PendingIntent.getActivity(this,
  635. (int) System.currentTimeMillis(),
  636. intent,
  637. 0)
  638. );
  639. if (!upload.isInstantPicture() && !upload.isInstantVideo()) {
  640. if (mNotificationManager == null) {
  641. mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
  642. }
  643. mNotificationManager.notify(FOREGROUND_SERVICE_ID, mNotificationBuilder.build());
  644. } // else wait until the upload really start (onTransferProgress is called), so that if it's discarded
  645. // due to lack of Wifi, no notification is shown
  646. // TODO generalize for automated uploads
  647. }
  648. /**
  649. * Callback method to update the progress bar in the status notification
  650. */
  651. @Override
  652. public void onTransferProgress(
  653. long progressRate,
  654. long totalTransferredSoFar,
  655. long totalToTransfer,
  656. String filePath
  657. ) {
  658. int percent = (int) (100.0 * ((double) totalTransferredSoFar) / ((double) totalToTransfer));
  659. if (percent != mLastPercent) {
  660. mNotificationBuilder.setProgress(100, percent, false);
  661. String fileName = filePath.substring(filePath.lastIndexOf(FileUtils.PATH_SEPARATOR) + 1);
  662. String text = String.format(getString(R.string.uploader_upload_in_progress_content), percent, fileName);
  663. mNotificationBuilder.setContentText(text);
  664. if (mNotificationManager == null) {
  665. mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
  666. }
  667. mNotificationManager.notify(FOREGROUND_SERVICE_ID, mNotificationBuilder.build());
  668. }
  669. mLastPercent = percent;
  670. }
  671. /**
  672. * Updates the status notification with the result of an upload operation.
  673. *
  674. * @param uploadResult Result of the upload operation.
  675. * @param upload Finished upload operation
  676. */
  677. @SuppressFBWarnings("DMI")
  678. private void notifyUploadResult(UploadFileOperation upload, RemoteOperationResult uploadResult) {
  679. Log_OC.d(TAG, "NotifyUploadResult with resultCode: " + uploadResult.getCode());
  680. // cancelled operation or success -> silent removal of progress notification
  681. if (mNotificationManager == null) {
  682. mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
  683. }
  684. // Only notify if the upload fails
  685. if (!uploadResult.isCancelled() &&
  686. !uploadResult.isSuccess() &&
  687. !ResultCode.LOCAL_FILE_NOT_FOUND.equals(uploadResult.getCode()) &&
  688. !uploadResult.getCode().equals(ResultCode.DELAYED_FOR_WIFI) &&
  689. !uploadResult.getCode().equals(ResultCode.DELAYED_FOR_CHARGING) &&
  690. !uploadResult.getCode().equals(ResultCode.DELAYED_IN_POWER_SAVE_MODE) &&
  691. !uploadResult.getCode().equals(ResultCode.LOCK_FAILED)) {
  692. int tickerId = R.string.uploader_upload_failed_ticker;
  693. String content;
  694. // check credentials error
  695. boolean needsToUpdateCredentials = uploadResult.getCode() == ResultCode.UNAUTHORIZED;
  696. if (needsToUpdateCredentials) {
  697. tickerId = R.string.uploader_upload_failed_credentials_error;
  698. } else if (uploadResult.getCode() == ResultCode.SYNC_CONFLICT) { // check file conflict
  699. tickerId = R.string.uploader_upload_failed_sync_conflict_error;
  700. }
  701. mNotificationBuilder
  702. .setTicker(getString(tickerId))
  703. .setContentTitle(getString(tickerId))
  704. .setAutoCancel(true)
  705. .setOngoing(false)
  706. .setProgress(0, 0, false);
  707. content = ErrorMessageAdapter.getErrorCauseMessage(uploadResult, upload, getResources());
  708. if (needsToUpdateCredentials) {
  709. // let the user update credentials with one click
  710. Intent updateAccountCredentials = new Intent(this, AuthenticatorActivity.class);
  711. updateAccountCredentials.putExtra(
  712. AuthenticatorActivity.EXTRA_ACCOUNT, upload.getAccount()
  713. );
  714. updateAccountCredentials.putExtra(
  715. AuthenticatorActivity.EXTRA_ACTION,
  716. AuthenticatorActivity.ACTION_UPDATE_EXPIRED_TOKEN
  717. );
  718. updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  719. updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
  720. updateAccountCredentials.addFlags(Intent.FLAG_FROM_BACKGROUND);
  721. mNotificationBuilder.setContentIntent(PendingIntent.getActivity(
  722. this,
  723. (int) System.currentTimeMillis(),
  724. updateAccountCredentials,
  725. PendingIntent.FLAG_ONE_SHOT
  726. ));
  727. } else {
  728. Intent intent;
  729. if (uploadResult.getCode().equals(ResultCode.SYNC_CONFLICT)) {
  730. intent = ConflictsResolveActivity.createIntent(upload.getFile(),
  731. upload.getAccount(),
  732. upload.getOCUploadId(),
  733. Intent.FLAG_ACTIVITY_CLEAR_TOP,
  734. this);
  735. } else {
  736. intent = UploadListActivity.createIntent(upload.getFile(),
  737. upload.getAccount(),
  738. Intent.FLAG_ACTIVITY_CLEAR_TOP,
  739. this);
  740. }
  741. mNotificationBuilder.setContentIntent(PendingIntent.getActivity(this,
  742. (int) System.currentTimeMillis(),
  743. intent,
  744. 0)
  745. );
  746. }
  747. mNotificationBuilder.setContentText(content);
  748. if (!uploadResult.isSuccess()) {
  749. mNotificationManager.notify((new SecureRandom()).nextInt(), mNotificationBuilder.build());
  750. }
  751. }
  752. }
  753. /**
  754. * Sends a broadcast in order to the interested activities can update their view
  755. *
  756. * TODO - no more broadcasts, replace with a callback to subscribed listeners
  757. */
  758. private void sendBroadcastUploadsAdded() {
  759. Intent start = new Intent(getUploadsAddedMessage());
  760. // nothing else needed right now
  761. start.setPackage(getPackageName());
  762. localBroadcastManager.sendBroadcast(start);
  763. }
  764. /**
  765. * Sends a broadcast in order to the interested activities can update their view
  766. *
  767. * TODO - no more broadcasts, replace with a callback to subscribed listeners
  768. *
  769. * @param upload Finished upload operation
  770. */
  771. private void sendBroadcastUploadStarted(UploadFileOperation upload) {
  772. Intent start = new Intent(getUploadStartMessage());
  773. start.putExtra(EXTRA_REMOTE_PATH, upload.getRemotePath()); // real remote
  774. start.putExtra(EXTRA_OLD_FILE_PATH, upload.getOriginalStoragePath());
  775. start.putExtra(ACCOUNT_NAME, upload.getAccount().name);
  776. start.setPackage(getPackageName());
  777. localBroadcastManager.sendBroadcast(start);
  778. }
  779. /**
  780. * Sends a broadcast in order to the interested activities can update their view
  781. *
  782. * TODO - no more broadcasts, replace with a callback to subscribed listeners
  783. *
  784. * @param upload Finished upload operation
  785. * @param uploadResult Result of the upload operation
  786. * @param unlinkedFromRemotePath Path in the uploads tree where the upload was unlinked from
  787. */
  788. private void sendBroadcastUploadFinished(
  789. UploadFileOperation upload,
  790. RemoteOperationResult uploadResult,
  791. String unlinkedFromRemotePath
  792. ) {
  793. Intent end = new Intent(getUploadFinishMessage());
  794. end.putExtra(EXTRA_REMOTE_PATH, upload.getRemotePath()); // real remote
  795. // path, after
  796. // possible
  797. // automatic
  798. // renaming
  799. if (upload.wasRenamed()) {
  800. end.putExtra(EXTRA_OLD_REMOTE_PATH, upload.getOldFile().getRemotePath());
  801. }
  802. end.putExtra(EXTRA_OLD_FILE_PATH, upload.getOriginalStoragePath());
  803. end.putExtra(ACCOUNT_NAME, upload.getAccount().name);
  804. end.putExtra(EXTRA_UPLOAD_RESULT, uploadResult.isSuccess());
  805. if (unlinkedFromRemotePath != null) {
  806. end.putExtra(EXTRA_LINKED_TO_PATH, unlinkedFromRemotePath);
  807. }
  808. end.setPackage(getPackageName());
  809. localBroadcastManager.sendBroadcast(end);
  810. }
  811. /**
  812. * Remove and 'forgets' pending uploads of an account.
  813. *
  814. * @param account Account which uploads will be cancelled
  815. */
  816. private void cancelUploadsForAccount(Account account) {
  817. mPendingUploads.remove(account.name);
  818. mUploadsStorageManager.removeUploads(account.name);
  819. }
  820. /**
  821. * Upload a new file
  822. */
  823. public static void uploadNewFile(
  824. Context context,
  825. Account account,
  826. String localPath,
  827. String remotePath,
  828. int behaviour,
  829. String mimeType,
  830. boolean createRemoteFile,
  831. int createdBy,
  832. boolean requiresWifi,
  833. boolean requiresCharging,
  834. NameCollisionPolicy nameCollisionPolicy
  835. ) {
  836. uploadNewFile(
  837. context,
  838. account,
  839. new String[]{localPath},
  840. new String[]{remotePath},
  841. new String[]{mimeType},
  842. behaviour,
  843. createRemoteFile,
  844. createdBy,
  845. requiresWifi,
  846. requiresCharging,
  847. nameCollisionPolicy
  848. );
  849. }
  850. /**
  851. * Upload multiple new files
  852. */
  853. public static void uploadNewFile(
  854. Context context,
  855. Account account,
  856. String[] localPaths,
  857. String[] remotePaths,
  858. String[] mimeTypes,
  859. Integer behaviour,
  860. Boolean createRemoteFolder,
  861. int createdBy,
  862. boolean requiresWifi,
  863. boolean requiresCharging,
  864. NameCollisionPolicy nameCollisionPolicy
  865. ) {
  866. Intent intent = new Intent(context, FileUploader.class);
  867. intent.putExtra(FileUploader.KEY_ACCOUNT, account);
  868. intent.putExtra(FileUploader.KEY_LOCAL_FILE, localPaths);
  869. intent.putExtra(FileUploader.KEY_REMOTE_FILE, remotePaths);
  870. intent.putExtra(FileUploader.KEY_MIME_TYPE, mimeTypes);
  871. intent.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, behaviour);
  872. intent.putExtra(FileUploader.KEY_CREATE_REMOTE_FOLDER, createRemoteFolder);
  873. intent.putExtra(FileUploader.KEY_CREATED_BY, createdBy);
  874. intent.putExtra(FileUploader.KEY_WHILE_ON_WIFI_ONLY, requiresWifi);
  875. intent.putExtra(FileUploader.KEY_WHILE_CHARGING_ONLY, requiresCharging);
  876. intent.putExtra(FileUploader.KEY_NAME_COLLISION_POLICY, nameCollisionPolicy);
  877. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
  878. context.startForegroundService(intent);
  879. } else {
  880. context.startService(intent);
  881. }
  882. }
  883. /**
  884. * Upload and overwrite an already uploaded file with disabled retries
  885. */
  886. public static void uploadUpdateFile(
  887. Context context,
  888. Account account,
  889. OCFile existingFile,
  890. Integer behaviour,
  891. NameCollisionPolicy nameCollisionPolicy
  892. ) {
  893. uploadUpdateFile(context, account, new OCFile[]{existingFile}, behaviour, nameCollisionPolicy, true);
  894. }
  895. /**
  896. * Upload and overwrite an already uploaded file
  897. */
  898. public static void uploadUpdateFile(
  899. Context context,
  900. Account account,
  901. OCFile existingFile,
  902. Integer behaviour,
  903. NameCollisionPolicy nameCollisionPolicy,
  904. boolean disableRetries
  905. ) {
  906. uploadUpdateFile(context, account, new OCFile[]{existingFile}, behaviour, nameCollisionPolicy, disableRetries);
  907. }
  908. /**
  909. * Upload and overwrite already uploaded files
  910. */
  911. public static void uploadUpdateFile(
  912. Context context,
  913. Account account,
  914. OCFile[] existingFiles,
  915. Integer behaviour,
  916. NameCollisionPolicy nameCollisionPolicy,
  917. boolean disableRetries
  918. ) {
  919. Intent intent = new Intent(context, FileUploader.class);
  920. intent.putExtra(FileUploader.KEY_ACCOUNT, account);
  921. intent.putExtra(FileUploader.KEY_FILE, existingFiles);
  922. intent.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, behaviour);
  923. intent.putExtra(FileUploader.KEY_NAME_COLLISION_POLICY, nameCollisionPolicy);
  924. intent.putExtra(FileUploader.KEY_DISABLE_RETRIES, disableRetries);
  925. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
  926. context.startForegroundService(intent);
  927. } else {
  928. context.startService(intent);
  929. }
  930. }
  931. /**
  932. * Retry a failed {@link OCUpload} identified by {@link OCUpload#getRemotePath()}
  933. */
  934. public static void retryUpload(@NonNull Context context, @NonNull User user, @NonNull OCUpload upload) {
  935. Intent i = new Intent(context, FileUploader.class);
  936. i.putExtra(FileUploader.KEY_RETRY, true);
  937. i.putExtra(FileUploader.KEY_ACCOUNT, user.toPlatformAccount());
  938. i.putExtra(FileUploader.KEY_RETRY_UPLOAD, upload);
  939. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
  940. context.startForegroundService(i);
  941. } else {
  942. context.startService(i);
  943. }
  944. }
  945. /**
  946. * Retry a subset of all the stored failed uploads.
  947. */
  948. public static void retryFailedUploads(
  949. @NonNull final Context context,
  950. @NonNull final UploadsStorageManager uploadsStorageManager,
  951. @NonNull final ConnectivityService connectivityService,
  952. @NonNull final UserAccountManager accountManager,
  953. @NonNull final PowerManagementService powerManagementService
  954. ) {
  955. OCUpload[] failedUploads = uploadsStorageManager.getFailedUploads();
  956. if(failedUploads == null || failedUploads.length == 0) {
  957. return; //nothing to do
  958. }
  959. final Connectivity connectivity = connectivityService.getConnectivity();
  960. final boolean gotNetwork = connectivity.isConnected() && !connectivityService.isInternetWalled();
  961. final boolean gotWifi = connectivity.isWifi();
  962. final BatteryStatus batteryStatus = powerManagementService.getBattery();
  963. final boolean charging = batteryStatus.isCharging() || batteryStatus.isFull();
  964. final boolean isPowerSaving = powerManagementService.isPowerSavingEnabled();
  965. Optional<User> uploadUser = Optional.empty();
  966. for (OCUpload failedUpload : failedUploads) {
  967. // 1. extract failed upload owner account and cache it between loops (expensive query)
  968. if (!uploadUser.isPresent() || !uploadUser.get().nameEquals(failedUpload.getAccountName())) {
  969. uploadUser = accountManager.getUser(failedUpload.getAccountName());
  970. }
  971. final boolean isDeleted = !new File(failedUpload.getLocalPath()).exists();
  972. if (isDeleted) {
  973. // 2A. for deleted files, mark as permanently failed
  974. if (failedUpload.getLastResult() != UploadResult.FILE_NOT_FOUND) {
  975. failedUpload.setLastResult(UploadResult.FILE_NOT_FOUND);
  976. uploadsStorageManager.updateUpload(failedUpload);
  977. }
  978. } else if (!isPowerSaving && gotNetwork && canUploadBeRetried(failedUpload, gotWifi, charging)) {
  979. // 2B. for existing local files, try restarting it if possible
  980. retryUpload(context, uploadUser.get(), failedUpload);
  981. }
  982. }
  983. }
  984. private static boolean canUploadBeRetried(OCUpload upload, boolean gotWifi, boolean isCharging) {
  985. File file = new File(upload.getLocalPath());
  986. boolean needsWifi = upload.isUseWifiOnly();
  987. boolean needsCharging = upload.isWhileChargingOnly();
  988. return file.exists() && (!needsWifi || gotWifi) && (!needsCharging || isCharging);
  989. }
  990. public static String getUploadsAddedMessage() {
  991. return FileUploader.class.getName() + UPLOADS_ADDED_MESSAGE;
  992. }
  993. public static String getUploadStartMessage() {
  994. return FileUploader.class.getName() + UPLOAD_START_MESSAGE;
  995. }
  996. public static String getUploadFinishMessage() {
  997. return FileUploader.class.getName() + UPLOAD_FINISH_MESSAGE;
  998. }
  999. /**
  1000. * Binder to let client components to perform operations on the queue of uploads.
  1001. *
  1002. * It provides by itself the available operations.
  1003. */
  1004. public class FileUploaderBinder extends Binder implements OnDatatransferProgressListener {
  1005. /**
  1006. * Map of listeners that will be reported about progress of uploads from a {@link FileUploaderBinder} instance
  1007. */
  1008. private Map<String, OnDatatransferProgressListener> mBoundListeners = new HashMap<>();
  1009. /**
  1010. * Cancels a pending or current upload of a remote file.
  1011. *
  1012. * @param account ownCloud account where the remote file will be stored.
  1013. * @param file A file in the queue of pending uploads
  1014. */
  1015. public void cancel(Account account, OCFile file) {
  1016. cancel(account.name, file.getRemotePath(), null);
  1017. }
  1018. /**
  1019. * Cancels a pending or current upload that was persisted.
  1020. *
  1021. * @param storedUpload Upload operation persisted
  1022. */
  1023. public void cancel(OCUpload storedUpload) {
  1024. cancel(storedUpload.getAccountName(), storedUpload.getRemotePath(), null);
  1025. }
  1026. /**
  1027. * Cancels a pending or current upload of a remote file.
  1028. *
  1029. * @param accountName Local name of an ownCloud account where the remote file will be stored.
  1030. * @param remotePath Remote target of the upload
  1031. * @param resultCode Setting result code will pause rather than cancel the job
  1032. */
  1033. private void cancel(String accountName, String remotePath, @Nullable ResultCode resultCode) {
  1034. Pair<UploadFileOperation, String> removeResult = mPendingUploads.remove(accountName, remotePath);
  1035. UploadFileOperation upload = removeResult.first;
  1036. if (upload == null && mCurrentUpload != null && mCurrentAccount != null &&
  1037. mCurrentUpload.getRemotePath().startsWith(remotePath) && accountName.equals(mCurrentAccount.name)) {
  1038. upload = mCurrentUpload;
  1039. }
  1040. if (upload != null) {
  1041. upload.cancel(resultCode);
  1042. // need to update now table in mUploadsStorageManager,
  1043. // since the operation will not get to be run by FileUploader#uploadFile
  1044. if (resultCode != null) {
  1045. mUploadsStorageManager.updateDatabaseUploadResult(new RemoteOperationResult(resultCode), upload);
  1046. notifyUploadResult(upload, new RemoteOperationResult(resultCode));
  1047. } else {
  1048. mUploadsStorageManager.removeUpload(accountName, remotePath);
  1049. }
  1050. }
  1051. }
  1052. /**
  1053. * Cancels all the uploads for an account.
  1054. *
  1055. * @param account ownCloud account.
  1056. */
  1057. public void cancel(Account account) {
  1058. Log_OC.d(TAG, "Account= " + account.name);
  1059. if (mCurrentUpload != null) {
  1060. Log_OC.d(TAG, "Current Upload Account= " + mCurrentUpload.getAccount().name);
  1061. if (mCurrentUpload.getAccount().name.equals(account.name)) {
  1062. mCurrentUpload.cancel(ResultCode.CANCELLED);
  1063. }
  1064. }
  1065. // Cancel pending uploads
  1066. cancelUploadsForAccount(account);
  1067. }
  1068. public void clearListeners() {
  1069. mBoundListeners.clear();
  1070. }
  1071. /**
  1072. * Returns True when the file described by 'file' is being uploaded to the ownCloud account 'account' or waiting
  1073. * for it
  1074. *
  1075. * If 'file' is a directory, returns 'true' if some of its descendant files is uploading or waiting to upload.
  1076. *
  1077. * Warning: If remote file exists and target was renamed the original file is being returned here. That is, it
  1078. * seems as if the original file is being updated when actually a new file is being uploaded.
  1079. *
  1080. * @param user user where the remote file will be stored.
  1081. * @param file A file that could be in the queue of pending uploads
  1082. */
  1083. public boolean isUploading(User user, OCFile file) {
  1084. if (user == null || file == null) {
  1085. return false;
  1086. }
  1087. return mPendingUploads.contains(user.getAccountName(), file.getRemotePath());
  1088. }
  1089. public boolean isUploadingNow(OCUpload upload) {
  1090. return upload != null &&
  1091. mCurrentAccount != null &&
  1092. mCurrentUpload != null &&
  1093. upload.getAccountName().equals(mCurrentAccount.name) &&
  1094. upload.getRemotePath().equals(mCurrentUpload.getRemotePath());
  1095. }
  1096. /**
  1097. * Adds a listener interested in the progress of the upload for a concrete file.
  1098. *
  1099. * @param listener Object to notify about progress of transfer.
  1100. * @param user user owning the file of interest.
  1101. * @param file {@link OCFile} of interest for listener.
  1102. */
  1103. public void addDatatransferProgressListener(
  1104. OnDatatransferProgressListener listener,
  1105. User user,
  1106. OCFile file
  1107. ) {
  1108. if (user == null || file == null || listener == null) {
  1109. return;
  1110. }
  1111. String targetKey = buildRemoteName(user.getAccountName(), file.getRemotePath());
  1112. mBoundListeners.put(targetKey, listener);
  1113. }
  1114. /**
  1115. * Adds a listener interested in the progress of the upload for a concrete file.
  1116. *
  1117. * @param listener Object to notify about progress of transfer.
  1118. * @param ocUpload {@link OCUpload} of interest for listener.
  1119. */
  1120. public void addDatatransferProgressListener(
  1121. OnDatatransferProgressListener listener,
  1122. OCUpload ocUpload
  1123. ) {
  1124. if (ocUpload == null || listener == null) {
  1125. return;
  1126. }
  1127. String targetKey = buildRemoteName(ocUpload.getAccountName(), ocUpload.getRemotePath());
  1128. mBoundListeners.put(targetKey, listener);
  1129. }
  1130. /**
  1131. * Removes a listener interested in the progress of the upload for a concrete file.
  1132. *
  1133. * @param listener Object to notify about progress of transfer.
  1134. * @param user user owning the file of interest.
  1135. * @param file {@link OCFile} of interest for listener.
  1136. */
  1137. public void removeDatatransferProgressListener(
  1138. OnDatatransferProgressListener listener,
  1139. User user,
  1140. OCFile file
  1141. ) {
  1142. if (user == null || file == null || listener == null) {
  1143. return;
  1144. }
  1145. String targetKey = buildRemoteName(user.getAccountName(), file.getRemotePath());
  1146. if (mBoundListeners.get(targetKey) == listener) {
  1147. mBoundListeners.remove(targetKey);
  1148. }
  1149. }
  1150. /**
  1151. * Removes a listener interested in the progress of the upload for a concrete file.
  1152. *
  1153. * @param listener Object to notify about progress of transfer.
  1154. * @param ocUpload Stored upload of interest
  1155. */
  1156. public void removeDatatransferProgressListener(
  1157. OnDatatransferProgressListener listener,
  1158. OCUpload ocUpload
  1159. ) {
  1160. if (ocUpload == null || listener == null) {
  1161. return;
  1162. }
  1163. String targetKey = buildRemoteName(ocUpload.getAccountName(), ocUpload.getRemotePath());
  1164. if (mBoundListeners.get(targetKey) == listener) {
  1165. mBoundListeners.remove(targetKey);
  1166. }
  1167. }
  1168. @Override
  1169. public void onTransferProgress(
  1170. long progressRate,
  1171. long totalTransferredSoFar,
  1172. long totalToTransfer,
  1173. String fileName
  1174. ) {
  1175. String key = buildRemoteName(mCurrentUpload.getAccount().name, mCurrentUpload.getFile().getRemotePath());
  1176. OnDatatransferProgressListener boundListener = mBoundListeners.get(key);
  1177. if (boundListener != null) {
  1178. boundListener.onTransferProgress(progressRate, totalTransferredSoFar, totalToTransfer, fileName);
  1179. }
  1180. Context context = MainApp.getAppContext();
  1181. if (context != null) {
  1182. ResultCode cancelReason = null;
  1183. Connectivity connectivity = connectivityService.getConnectivity();
  1184. if (mCurrentUpload.isWifiRequired() && !connectivity.isWifi()) {
  1185. cancelReason = ResultCode.DELAYED_FOR_WIFI;
  1186. } else if (mCurrentUpload.isChargingRequired() && !powerManagementService.getBattery().isCharging()) {
  1187. cancelReason = ResultCode.DELAYED_FOR_CHARGING;
  1188. } else if (!mCurrentUpload.isIgnoringPowerSaveMode() && powerManagementService.isPowerSavingEnabled()) {
  1189. cancelReason = ResultCode.DELAYED_IN_POWER_SAVE_MODE;
  1190. }
  1191. if (cancelReason != null) {
  1192. cancel(
  1193. mCurrentUpload.getAccount().name,
  1194. mCurrentUpload.getFile().getRemotePath(),
  1195. cancelReason
  1196. );
  1197. }
  1198. }
  1199. }
  1200. /**
  1201. * Builds a key for the map of listeners.
  1202. *
  1203. * TODO use method in IndexedForest, or refactor both to a common place add to local database) to better policy
  1204. * (add to local database, then upload)
  1205. *
  1206. * @param accountName Local name of the ownCloud account where the file to upload belongs.
  1207. * @param remotePath Remote path to upload the file to.
  1208. * @return Key
  1209. */
  1210. private String buildRemoteName(String accountName, String remotePath) {
  1211. return accountName + remotePath;
  1212. }
  1213. }
  1214. /**
  1215. * Upload worker. Performs the pending uploads in the order they were requested.
  1216. *
  1217. * Created with the Looper of a new thread, started in {@link FileUploader#onCreate()}.
  1218. */
  1219. private static class ServiceHandler extends Handler {
  1220. // don't make it a final class, and don't remove the static ; lint will
  1221. // warn about a possible memory leak
  1222. private FileUploader mService;
  1223. public ServiceHandler(Looper looper, FileUploader service) {
  1224. super(looper);
  1225. if (service == null) {
  1226. throw new IllegalArgumentException("Received invalid NULL in parameter 'service'");
  1227. }
  1228. mService = service;
  1229. }
  1230. @Override
  1231. public void handleMessage(Message msg) {
  1232. @SuppressWarnings("unchecked")
  1233. List<String> requestedUploads = (List<String>) msg.obj;
  1234. if (msg.obj != null) {
  1235. for (String requestedUpload : requestedUploads) {
  1236. mService.uploadFile(requestedUpload);
  1237. }
  1238. }
  1239. Log_OC.d(TAG, "Stopping command after id " + msg.arg1);
  1240. mService.mNotificationManager.cancel(FOREGROUND_SERVICE_ID);
  1241. mService.stopForeground(true);
  1242. mService.stopSelf(msg.arg1);
  1243. }
  1244. }
  1245. }