FilesSyncHelper.java 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  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.utils;
  22. import android.accounts.Account;
  23. import android.content.ContentResolver;
  24. import android.content.Context;
  25. import android.database.Cursor;
  26. import android.net.Uri;
  27. import android.provider.MediaStore;
  28. import android.text.TextUtils;
  29. import android.util.Log;
  30. import com.evernote.android.job.JobManager;
  31. import com.evernote.android.job.JobRequest;
  32. import com.evernote.android.job.util.Device;
  33. import com.evernote.android.job.util.support.PersistableBundleCompat;
  34. import com.owncloud.android.MainApp;
  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.MediaFolder;
  39. import com.owncloud.android.datamodel.SyncedFolder;
  40. import com.owncloud.android.datamodel.SyncedFolderProvider;
  41. import com.owncloud.android.datamodel.UploadsStorageManager;
  42. import com.owncloud.android.db.OCUpload;
  43. import com.owncloud.android.db.UploadResult;
  44. import com.owncloud.android.jobs.AutoUploadJob;
  45. import org.lukhnos.nnio.file.FileVisitResult;
  46. import org.lukhnos.nnio.file.Files;
  47. import org.lukhnos.nnio.file.Path;
  48. import org.lukhnos.nnio.file.Paths;
  49. import org.lukhnos.nnio.file.SimpleFileVisitor;
  50. import org.lukhnos.nnio.file.attribute.BasicFileAttributes;
  51. import java.io.File;
  52. import java.io.IOException;
  53. import java.io.RandomAccessFile;
  54. import java.nio.channels.FileChannel;
  55. import java.nio.channels.FileLock;
  56. import java.nio.channels.OverlappingFileLockException;
  57. public class FilesSyncHelper {
  58. public static final String TAG = "FileSyncHelper";
  59. public static void insertAllDBEntriesForSyncedFolder(SyncedFolder syncedFolder) {
  60. final Context context = MainApp.getAppContext();
  61. final ContentResolver contentResolver = context.getContentResolver();
  62. ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(contentResolver);
  63. String syncedFolderInitiatedKey = "syncedFolderIntitiated_" + syncedFolder.getId();
  64. boolean dryRun = TextUtils.isEmpty(arbitraryDataProvider.getValue
  65. ("global", syncedFolderInitiatedKey));
  66. if (MediaFolder.IMAGE == syncedFolder.getType()) {
  67. FilesSyncHelper.insertContentIntoDB(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI
  68. , dryRun, syncedFolder);
  69. FilesSyncHelper.insertContentIntoDB(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, dryRun,
  70. syncedFolder);
  71. if (dryRun) {
  72. arbitraryDataProvider.storeOrUpdateKeyValue("global", syncedFolderInitiatedKey,
  73. "1");
  74. }
  75. } else if (MediaFolder.VIDEO == syncedFolder.getType()) {
  76. FilesSyncHelper.insertContentIntoDB(android.provider.MediaStore.Video.Media.INTERNAL_CONTENT_URI,
  77. dryRun, syncedFolder);
  78. FilesSyncHelper.insertContentIntoDB(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, dryRun,
  79. syncedFolder);
  80. if (dryRun) {
  81. arbitraryDataProvider.storeOrUpdateKeyValue("global", syncedFolderInitiatedKey,
  82. "1");
  83. }
  84. } else {
  85. try {
  86. FilesystemDataProvider filesystemDataProvider = new FilesystemDataProvider(contentResolver);
  87. Path path = Paths.get(syncedFolder.getLocalPath());
  88. Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
  89. @Override
  90. public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {
  91. File file = path.toFile();
  92. FileChannel channel = new RandomAccessFile(file, "rw").getChannel();
  93. FileLock lock = channel.lock();
  94. try {
  95. lock = channel.tryLock();
  96. filesystemDataProvider.storeOrUpdateFileValue(path.toAbsolutePath().toString(),
  97. attrs.lastModifiedTime().toMillis(), file.isDirectory(), syncedFolder, dryRun);
  98. } catch (OverlappingFileLockException e) {
  99. filesystemDataProvider.storeOrUpdateFileValue(path.toAbsolutePath().toString(),
  100. attrs.lastModifiedTime().toMillis(), file.isDirectory(), syncedFolder, dryRun);
  101. } finally {
  102. lock.release();
  103. }
  104. return FileVisitResult.CONTINUE;
  105. }
  106. @Override
  107. public FileVisitResult visitFileFailed(Path file, IOException exc) {
  108. return FileVisitResult.CONTINUE;
  109. }
  110. });
  111. if (dryRun) {
  112. arbitraryDataProvider.storeOrUpdateKeyValue("global", syncedFolderInitiatedKey,
  113. "1");
  114. }
  115. } catch (IOException e) {
  116. Log.d(TAG, "Something went wrong while indexing files for auto upload");
  117. }
  118. }
  119. }
  120. public static void insertAllDBEntries() {
  121. final Context context = MainApp.getAppContext();
  122. final ContentResolver contentResolver = context.getContentResolver();
  123. SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver);
  124. for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) {
  125. if (syncedFolder.isEnabled()) {
  126. insertAllDBEntriesForSyncedFolder(syncedFolder);
  127. }
  128. }
  129. }
  130. private static void insertContentIntoDB(Uri uri, boolean dryRun, SyncedFolder syncedFolder) {
  131. final Context context = MainApp.getAppContext();
  132. final ContentResolver contentResolver = context.getContentResolver();
  133. Cursor cursor;
  134. int column_index_data, column_index_date_modified;
  135. final FilesystemDataProvider filesystemDataProvider = new FilesystemDataProvider(contentResolver);
  136. String contentPath;
  137. boolean isFolder;
  138. String[] projection = {MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DATE_MODIFIED};
  139. String path = syncedFolder.getLocalPath();
  140. if (!path.endsWith("/")) {
  141. path = path + "/%";
  142. } else {
  143. path = path + "%";
  144. }
  145. cursor = context.getContentResolver().query(uri, projection, MediaStore.MediaColumns.DATA + " LIKE ?",
  146. new String[]{path}, null);
  147. if (cursor != null) {
  148. column_index_data = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA);
  149. column_index_date_modified = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_MODIFIED);
  150. while (cursor.moveToNext()) {
  151. contentPath = cursor.getString(column_index_data);
  152. isFolder = new File(contentPath).isDirectory();
  153. filesystemDataProvider.storeOrUpdateFileValue(cursor.getString(column_index_data),
  154. cursor.getLong(column_index_date_modified), isFolder, syncedFolder, dryRun);
  155. }
  156. cursor.close();
  157. }
  158. }
  159. public static void restartJobsIfNeeded() {
  160. final Context context = MainApp.getAppContext();
  161. boolean restartedInCurrentIteration = false;
  162. for (JobRequest jobRequest : JobManager.instance().getAllJobRequestsForTag(AutoUploadJob.TAG)) {
  163. restartedInCurrentIteration = false;
  164. // Handle case of charging
  165. if (jobRequest.requiresCharging() && Device.isCharging(context)) {
  166. if (jobRequest.requiredNetworkType().equals(JobRequest.NetworkType.CONNECTED) &&
  167. !Device.getNetworkType(context).equals(JobRequest.NetworkType.ANY)) {
  168. jobRequest.cancelAndEdit().build().schedule();
  169. restartedInCurrentIteration = true;
  170. } else if (jobRequest.requiredNetworkType().equals(JobRequest.NetworkType.UNMETERED) &&
  171. Device.getNetworkType(context).equals(JobRequest.NetworkType.UNMETERED)) {
  172. jobRequest.cancelAndEdit().build().schedule();
  173. restartedInCurrentIteration = true;
  174. }
  175. }
  176. // Handle case of wifi
  177. if (!restartedInCurrentIteration) {
  178. if (jobRequest.requiredNetworkType().equals(JobRequest.NetworkType.CONNECTED) &&
  179. !Device.getNetworkType(context).equals(JobRequest.NetworkType.ANY)) {
  180. jobRequest.cancelAndEdit().build().schedule();
  181. } else if (jobRequest.requiredNetworkType().equals(JobRequest.NetworkType.UNMETERED) &&
  182. Device.getNetworkType(context).equals(JobRequest.NetworkType.UNMETERED)) {
  183. jobRequest.cancelAndEdit().build().schedule();
  184. }
  185. }
  186. }
  187. UploadsStorageManager uploadsStorageManager = new UploadsStorageManager(context.getContentResolver(), context);
  188. OCUpload[] failedUploads = uploadsStorageManager.getFailedUploads();
  189. boolean accountExists;
  190. for (OCUpload failedUpload: failedUploads) {
  191. accountExists = false;
  192. if (!failedUpload.getLastResult().equals(UploadResult.UPLOADED)) {
  193. uploadsStorageManager.removeUpload(failedUpload);
  194. // check if accounts still exists
  195. for (Account account : AccountUtils.getAccounts(context)) {
  196. if (account.name.equals(failedUpload.getAccountName())) {
  197. accountExists = true;
  198. break;
  199. }
  200. }
  201. if (accountExists) {
  202. PersistableBundleCompat bundle = new PersistableBundleCompat();
  203. bundle.putString(AutoUploadJob.LOCAL_PATH, failedUpload.getLocalPath());
  204. bundle.putString(AutoUploadJob.REMOTE_PATH, failedUpload.getRemotePath());
  205. bundle.putString(AutoUploadJob.ACCOUNT, failedUpload.getAccountName());
  206. bundle.putInt(AutoUploadJob.UPLOAD_BEHAVIOUR, failedUpload.getLocalAction());
  207. new JobRequest.Builder(AutoUploadJob.TAG)
  208. .setExecutionWindow(30_000L, 80_000L)
  209. .setRequiresCharging(failedUpload.isWhileChargingOnly())
  210. .setRequiredNetworkType(failedUpload.isUseWifiOnly() ? JobRequest.NetworkType.UNMETERED :
  211. JobRequest.NetworkType.CONNECTED)
  212. .setExtras(bundle)
  213. .setRequirementsEnforced(true)
  214. .setUpdateCurrent(false)
  215. .build()
  216. .schedule();
  217. }
  218. }
  219. }
  220. }
  221. }