FilesSyncJob.java 9.8 KB

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