FilesSyncJob.java 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. /*
  2. * Nextcloud Android client application
  3. *
  4. * @author Mario Danic
  5. * @author Chris Narkiewicz
  6. * Copyright (C) 2017 Mario Danic
  7. * Copyright (C) 2017 Nextcloud
  8. * Copyright (C) 2919 Chris Narkiewicz
  9. *
  10. * This program is free software; you can redistribute it and/or
  11. * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
  12. * License as published by the Free Software Foundation; either
  13. * version 3 of the License, or any later version.
  14. *
  15. * This program is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
  19. *
  20. * You should have received a copy of the GNU Affero General Public
  21. * License along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. */
  23. package com.owncloud.android.jobs;
  24. import android.accounts.Account;
  25. import android.content.ContentResolver;
  26. import android.content.Context;
  27. import android.content.res.Resources;
  28. import android.os.Build;
  29. import android.os.PowerManager;
  30. import android.text.TextUtils;
  31. import com.evernote.android.job.Job;
  32. import com.evernote.android.job.util.support.PersistableBundleCompat;
  33. import com.nextcloud.client.account.UserAccountManager;
  34. import com.nextcloud.client.preferences.AppPreferences;
  35. import com.owncloud.android.MainApp;
  36. import com.owncloud.android.R;
  37. import com.owncloud.android.authentication.AccountUtils;
  38. import com.owncloud.android.datamodel.ArbitraryDataProvider;
  39. import com.owncloud.android.datamodel.FilesystemDataProvider;
  40. import com.owncloud.android.datamodel.MediaFolderType;
  41. import com.owncloud.android.datamodel.SyncedFolder;
  42. import com.owncloud.android.datamodel.SyncedFolderProvider;
  43. import com.owncloud.android.datamodel.UploadsStorageManager;
  44. import com.owncloud.android.files.services.FileUploader;
  45. import com.owncloud.android.lib.common.utils.Log_OC;
  46. import com.owncloud.android.operations.UploadFileOperation;
  47. import com.owncloud.android.ui.activity.SettingsActivity;
  48. import com.owncloud.android.utils.FileStorageUtils;
  49. import com.owncloud.android.utils.FilesSyncHelper;
  50. import com.owncloud.android.utils.MimeType;
  51. import com.owncloud.android.utils.MimeTypeUtil;
  52. import com.owncloud.android.utils.PowerUtils;
  53. import java.io.File;
  54. import java.text.ParsePosition;
  55. import java.text.SimpleDateFormat;
  56. import java.util.Date;
  57. import java.util.Locale;
  58. import java.util.TimeZone;
  59. import androidx.annotation.NonNull;
  60. import androidx.exifinterface.media.ExifInterface;
  61. /*
  62. Job that:
  63. - restarts existing jobs if required
  64. - finds new and modified files since we last run this
  65. - creates upload tasks
  66. */
  67. public class FilesSyncJob extends Job {
  68. public static final String TAG = "FilesSyncJob";
  69. public static final String SKIP_CUSTOM = "skipCustom";
  70. public static final String OVERRIDE_POWER_SAVING = "overridePowerSaving";
  71. private static final String WAKELOCK_TAG_SEPARATION = ":";
  72. private UserAccountManager userAccountManager;
  73. private AppPreferences preferences;
  74. private UploadsStorageManager uploadsStorageManager;
  75. public FilesSyncJob(
  76. final UserAccountManager userAccountManager,
  77. final AppPreferences preferences,
  78. final UploadsStorageManager uploadsStorageManager
  79. ) {
  80. this.userAccountManager = userAccountManager;
  81. this.preferences = preferences;
  82. this.uploadsStorageManager = uploadsStorageManager;
  83. }
  84. @NonNull
  85. @Override
  86. protected Result onRunJob(@NonNull Params params) {
  87. final Context context = MainApp.getAppContext();
  88. PowerManager.WakeLock wakeLock = null;
  89. if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
  90. PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
  91. wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, MainApp.getAuthority() +
  92. WAKELOCK_TAG_SEPARATION + TAG);
  93. wakeLock.acquire(10 * 60 * 1000);
  94. }
  95. PersistableBundleCompat bundle = params.getExtras();
  96. final boolean overridePowerSaving = bundle.getBoolean(OVERRIDE_POWER_SAVING, false);
  97. // If we are in power save mode, better to postpone upload
  98. if (PowerUtils.isPowerSaveMode(context) && !overridePowerSaving) {
  99. wakeLock.release();
  100. return Result.SUCCESS;
  101. }
  102. Resources resources = MainApp.getAppContext().getResources();
  103. boolean lightVersion = resources.getBoolean(R.bool.syncedFolder_light);
  104. final boolean skipCustom = bundle.getBoolean(SKIP_CUSTOM, false);
  105. FilesSyncHelper.restartJobsIfNeeded(uploadsStorageManager, userAccountManager);
  106. FilesSyncHelper.insertAllDBEntries(preferences, skipCustom);
  107. // Create all the providers we'll need
  108. final ContentResolver contentResolver = context.getContentResolver();
  109. final FilesystemDataProvider filesystemDataProvider = new FilesystemDataProvider(contentResolver);
  110. SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver,
  111. preferences);
  112. Locale currentLocale = context.getResources().getConfiguration().locale;
  113. SimpleDateFormat sFormatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss", currentLocale);
  114. sFormatter.setTimeZone(TimeZone.getTimeZone(TimeZone.getDefault().getID()));
  115. FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
  116. for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) {
  117. if ((syncedFolder.isEnabled()) && (!skipCustom || MediaFolderType.CUSTOM != syncedFolder.getType())) {
  118. syncFolder(context, resources, lightVersion, filesystemDataProvider, currentLocale, sFormatter,
  119. requester, syncedFolder);
  120. }
  121. }
  122. if (wakeLock != null) {
  123. wakeLock.release();
  124. }
  125. return Result.SUCCESS;
  126. }
  127. private void syncFolder(Context context, Resources resources, boolean lightVersion,
  128. FilesystemDataProvider filesystemDataProvider, Locale currentLocale,
  129. SimpleDateFormat sFormatter, FileUploader.UploadRequester requester,
  130. SyncedFolder syncedFolder) {
  131. String remotePath;
  132. boolean subfolderByDate;
  133. Integer uploadAction;
  134. boolean needsCharging;
  135. boolean needsWifi;
  136. File file;
  137. ArbitraryDataProvider arbitraryDataProvider;
  138. Account account = AccountUtils.getOwnCloudAccountByName(context, syncedFolder.getAccount());
  139. if (lightVersion) {
  140. arbitraryDataProvider = new ArbitraryDataProvider(context.getContentResolver());
  141. } else {
  142. arbitraryDataProvider = null;
  143. }
  144. for (String path : filesystemDataProvider.getFilesForUpload(syncedFolder.getLocalPath(),
  145. Long.toString(syncedFolder.getId()))) {
  146. file = new File(path);
  147. Long lastModificationTime = calculateLastModificationTime(file, syncedFolder, sFormatter);
  148. String mimeType = MimeTypeUtil.getBestMimeTypeByFilename(file.getAbsolutePath());
  149. if (lightVersion) {
  150. needsCharging = resources.getBoolean(R.bool.syncedFolder_light_on_charging);
  151. needsWifi = account == null || arbitraryDataProvider.getBooleanValue(account.name,
  152. SettingsActivity.SYNCED_FOLDER_LIGHT_UPLOAD_ON_WIFI);
  153. String uploadActionString = resources.getString(R.string.syncedFolder_light_upload_behaviour);
  154. uploadAction = getUploadAction(uploadActionString);
  155. subfolderByDate = resources.getBoolean(R.bool.syncedFolder_light_use_subfolders);
  156. remotePath = resources.getString(R.string.syncedFolder_remote_folder);
  157. } else {
  158. needsCharging = syncedFolder.getChargingOnly();
  159. needsWifi = syncedFolder.getWifiOnly();
  160. uploadAction = syncedFolder.getUploadAction();
  161. subfolderByDate = syncedFolder.getSubfolderByDate();
  162. remotePath = syncedFolder.getRemotePath();
  163. }
  164. if (!subfolderByDate) {
  165. String adaptedPath = file.getAbsolutePath()
  166. .replace(syncedFolder.getLocalPath(), "")
  167. .replace("/" + file.getName(), "");
  168. remotePath += adaptedPath;
  169. }
  170. requester.uploadFileWithOverwrite(
  171. context,
  172. account,
  173. file.getAbsolutePath(),
  174. FileStorageUtils.getInstantUploadFilePath(
  175. currentLocale,
  176. remotePath, file.getName(),
  177. lastModificationTime, subfolderByDate),
  178. uploadAction,
  179. mimeType,
  180. true, // create parent folder if not existent
  181. UploadFileOperation.CREATED_AS_INSTANT_PICTURE,
  182. needsWifi,
  183. needsCharging,
  184. true
  185. );
  186. filesystemDataProvider.updateFilesystemFileAsSentForUpload(path,
  187. Long.toString(syncedFolder.getId()));
  188. }
  189. }
  190. private Long calculateLastModificationTime(File file, SyncedFolder syncedFolder, SimpleDateFormat formatter) {
  191. Long lastModificationTime = file.lastModified();
  192. if (MediaFolderType.IMAGE == syncedFolder.getType()) {
  193. String mimeTypeString = FileStorageUtils.getMimeTypeFromName(file.getAbsolutePath());
  194. if (MimeType.JPEG.equalsIgnoreCase(mimeTypeString)
  195. || MimeType.TIFF.equalsIgnoreCase(mimeTypeString)) {
  196. try {
  197. ExifInterface exifInterface = new ExifInterface(file.getAbsolutePath());
  198. String exifDate = exifInterface.getAttribute(ExifInterface.TAG_DATETIME);
  199. if (!TextUtils.isEmpty(exifDate)) {
  200. ParsePosition pos = new ParsePosition(0);
  201. Date dateTime = formatter.parse(exifDate, pos);
  202. lastModificationTime = dateTime.getTime();
  203. }
  204. } catch (Exception e) {
  205. Log_OC.d(TAG, "Failed to get the proper time " + e.getLocalizedMessage());
  206. }
  207. }
  208. }
  209. return lastModificationTime;
  210. }
  211. private Integer getUploadAction(String action) {
  212. switch (action) {
  213. case "LOCAL_BEHAVIOUR_FORGET":
  214. return FileUploader.LOCAL_BEHAVIOUR_FORGET;
  215. case "LOCAL_BEHAVIOUR_MOVE":
  216. return FileUploader.LOCAL_BEHAVIOUR_MOVE;
  217. case "LOCAL_BEHAVIOUR_DELETE":
  218. return FileUploader.LOCAL_BEHAVIOUR_DELETE;
  219. default:
  220. return FileUploader.LOCAL_BEHAVIOUR_FORGET;
  221. }
  222. }
  223. }