FilesSyncHelper.java 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  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.owncloud.android.MainApp;
  34. import com.owncloud.android.authentication.AccountUtils;
  35. import com.owncloud.android.datamodel.ArbitraryDataProvider;
  36. import com.owncloud.android.datamodel.FilesystemDataProvider;
  37. import com.owncloud.android.datamodel.MediaFolder;
  38. import com.owncloud.android.datamodel.SyncedFolder;
  39. import com.owncloud.android.datamodel.SyncedFolderProvider;
  40. import com.owncloud.android.datamodel.UploadsStorageManager;
  41. import com.owncloud.android.db.OCUpload;
  42. import com.owncloud.android.db.UploadResult;
  43. import com.owncloud.android.files.services.FileUploader;
  44. import com.owncloud.android.jobs.AutoUploadJob;
  45. import com.owncloud.android.operations.UploadFileOperation;
  46. import org.lukhnos.nnio.file.FileVisitResult;
  47. import org.lukhnos.nnio.file.Files;
  48. import org.lukhnos.nnio.file.Path;
  49. import org.lukhnos.nnio.file.Paths;
  50. import org.lukhnos.nnio.file.SimpleFileVisitor;
  51. import org.lukhnos.nnio.file.attribute.BasicFileAttributes;
  52. import java.io.File;
  53. import java.io.IOException;
  54. import java.io.RandomAccessFile;
  55. import java.nio.channels.FileChannel;
  56. import java.nio.channels.FileLock;
  57. import java.nio.channels.OverlappingFileLockException;
  58. public class FilesSyncHelper {
  59. public static final String TAG = "FileSyncHelper";
  60. public static void insertAllDBEntriesForSyncedFolder(SyncedFolder syncedFolder) {
  61. final Context context = MainApp.getAppContext();
  62. final ContentResolver contentResolver = context.getContentResolver();
  63. ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(contentResolver);
  64. String syncedFolderInitiatedKey = "syncedFolderIntitiated_" + syncedFolder.getId();
  65. boolean dryRun = TextUtils.isEmpty(arbitraryDataProvider.getValue
  66. ("global", syncedFolderInitiatedKey));
  67. if (MediaFolder.IMAGE == syncedFolder.getType()) {
  68. FilesSyncHelper.insertContentIntoDB(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI
  69. , dryRun, syncedFolder);
  70. FilesSyncHelper.insertContentIntoDB(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, dryRun,
  71. syncedFolder);
  72. if (dryRun) {
  73. arbitraryDataProvider.storeOrUpdateKeyValue("global", syncedFolderInitiatedKey,
  74. "1");
  75. }
  76. } else if (MediaFolder.VIDEO == syncedFolder.getType()) {
  77. FilesSyncHelper.insertContentIntoDB(android.provider.MediaStore.Video.Media.INTERNAL_CONTENT_URI,
  78. dryRun, syncedFolder);
  79. FilesSyncHelper.insertContentIntoDB(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, dryRun,
  80. syncedFolder);
  81. if (dryRun) {
  82. arbitraryDataProvider.storeOrUpdateKeyValue("global", syncedFolderInitiatedKey,
  83. "1");
  84. }
  85. } else {
  86. try {
  87. FilesystemDataProvider filesystemDataProvider = new FilesystemDataProvider(contentResolver);
  88. Path path = Paths.get(syncedFolder.getLocalPath());
  89. Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
  90. @Override
  91. public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {
  92. File file = path.toFile();
  93. FileChannel channel = new RandomAccessFile(file, "rw").getChannel();
  94. FileLock lock = channel.lock();
  95. try {
  96. lock = channel.tryLock();
  97. filesystemDataProvider.storeOrUpdateFileValue(path.toAbsolutePath().toString(),
  98. attrs.lastModifiedTime().toMillis(), file.isDirectory(), syncedFolder, dryRun);
  99. } catch (OverlappingFileLockException e) {
  100. filesystemDataProvider.storeOrUpdateFileValue(path.toAbsolutePath().toString(),
  101. attrs.lastModifiedTime().toMillis(), file.isDirectory(), syncedFolder, dryRun);
  102. } finally {
  103. lock.release();
  104. }
  105. return FileVisitResult.CONTINUE;
  106. }
  107. @Override
  108. public FileVisitResult visitFileFailed(Path file, IOException exc) {
  109. return FileVisitResult.CONTINUE;
  110. }
  111. });
  112. if (dryRun) {
  113. arbitraryDataProvider.storeOrUpdateKeyValue("global", syncedFolderInitiatedKey,
  114. "1");
  115. }
  116. } catch (IOException e) {
  117. Log.d(TAG, "Something went wrong while indexing files for auto upload");
  118. }
  119. }
  120. }
  121. public static void insertAllDBEntries() {
  122. final Context context = MainApp.getAppContext();
  123. final ContentResolver contentResolver = context.getContentResolver();
  124. SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver);
  125. for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) {
  126. if (syncedFolder.isEnabled()) {
  127. insertAllDBEntriesForSyncedFolder(syncedFolder);
  128. }
  129. }
  130. }
  131. private static void insertContentIntoDB(Uri uri, boolean dryRun, SyncedFolder syncedFolder) {
  132. final Context context = MainApp.getAppContext();
  133. final ContentResolver contentResolver = context.getContentResolver();
  134. Cursor cursor;
  135. int column_index_data;
  136. int column_index_date_modified;
  137. final FilesystemDataProvider filesystemDataProvider = new FilesystemDataProvider(contentResolver);
  138. String contentPath;
  139. boolean isFolder;
  140. String[] projection = {MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DATE_MODIFIED};
  141. String path = syncedFolder.getLocalPath();
  142. if (!path.endsWith("/")) {
  143. path = path + "/%";
  144. } else {
  145. path = path + "%";
  146. }
  147. cursor = context.getContentResolver().query(uri, projection, MediaStore.MediaColumns.DATA + " LIKE ?",
  148. new String[]{path}, null);
  149. if (cursor != null) {
  150. column_index_data = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA);
  151. column_index_date_modified = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_MODIFIED);
  152. while (cursor.moveToNext()) {
  153. contentPath = cursor.getString(column_index_data);
  154. isFolder = new File(contentPath).isDirectory();
  155. filesystemDataProvider.storeOrUpdateFileValue(cursor.getString(column_index_data),
  156. cursor.getLong(column_index_date_modified), isFolder, syncedFolder, dryRun);
  157. }
  158. cursor.close();
  159. }
  160. }
  161. public static void restartJobsIfNeeded() {
  162. final Context context = MainApp.getAppContext();
  163. boolean restartedInCurrentIteration;
  164. int countRestartedJobs = 0;
  165. FileUploader.UploadRequester uploadRequester = new FileUploader.UploadRequester();
  166. boolean accountExists;
  167. boolean fileExists;
  168. for (JobRequest jobRequest : JobManager.instance().getAllJobRequestsForTag(AutoUploadJob.TAG)) {
  169. restartedInCurrentIteration = false;
  170. accountExists = false;
  171. fileExists = new File(jobRequest.getExtras().getString(AutoUploadJob.LOCAL_PATH, "")).exists();
  172. // check if accounts still exists
  173. for (Account account : AccountUtils.getAccounts(context)) {
  174. if (account.name.equals(jobRequest.getExtras().getString(AutoUploadJob.ACCOUNT, ""))) {
  175. accountExists = true;
  176. break;
  177. }
  178. }
  179. if (accountExists && fileExists) {
  180. if (countRestartedJobs < 5) {
  181. // Handle case of charging
  182. if (jobRequest.requiresCharging() && Device.isCharging(context)) {
  183. if (jobRequest.requiredNetworkType().equals(JobRequest.NetworkType.CONNECTED) &&
  184. !Device.getNetworkType(context).equals(JobRequest.NetworkType.ANY)) {
  185. jobRequest.cancelAndEdit().build().schedule();
  186. countRestartedJobs++;
  187. restartedInCurrentIteration = true;
  188. } else if (jobRequest.requiredNetworkType().equals(JobRequest.NetworkType.UNMETERED) &&
  189. Device.getNetworkType(context).equals(JobRequest.NetworkType.UNMETERED)) {
  190. jobRequest.cancelAndEdit().build().schedule();
  191. countRestartedJobs++;
  192. restartedInCurrentIteration = true;
  193. }
  194. }
  195. // Handle case of wifi
  196. if (!restartedInCurrentIteration) {
  197. if (jobRequest.requiredNetworkType().equals(JobRequest.NetworkType.CONNECTED) &&
  198. !Device.getNetworkType(context).equals(JobRequest.NetworkType.ANY)) {
  199. jobRequest.cancelAndEdit().build().schedule();
  200. countRestartedJobs++;
  201. } else if (jobRequest.requiredNetworkType().equals(JobRequest.NetworkType.UNMETERED) &&
  202. Device.getNetworkType(context).equals(JobRequest.NetworkType.UNMETERED)) {
  203. jobRequest.cancelAndEdit().build().schedule();
  204. countRestartedJobs++;
  205. }
  206. }
  207. }
  208. } else {
  209. JobManager.instance().cancel(jobRequest.getJobId());
  210. }
  211. }
  212. UploadsStorageManager uploadsStorageManager = new UploadsStorageManager(context.getContentResolver(), context);
  213. OCUpload[] failedUploads = uploadsStorageManager.getFailedUploads();
  214. for (OCUpload failedUpload: failedUploads) {
  215. accountExists = false;
  216. fileExists = new File(failedUpload.getLocalPath()).exists();
  217. restartedInCurrentIteration = false;
  218. // check if accounts still exists
  219. for (Account account : AccountUtils.getAccounts(context)) {
  220. if (account.name.equals(failedUpload.getAccountName())) {
  221. accountExists = true;
  222. break;
  223. }
  224. }
  225. if (!failedUpload.getLastResult().equals(UploadResult.UPLOADED)) {
  226. if (failedUpload.getCreadtedBy() == UploadFileOperation.CREATED_AS_INSTANT_PICTURE) {
  227. if (accountExists && fileExists) {
  228. if (countRestartedJobs < 5) {
  229. // Handle case of charging
  230. if (failedUpload.isWhileChargingOnly() && Device.isCharging(context)) {
  231. if (failedUpload.isUseWifiOnly() &&
  232. Device.getNetworkType(context).equals(JobRequest.NetworkType.UNMETERED)) {
  233. uploadRequester.retry(context, failedUpload);
  234. restartedInCurrentIteration = true;
  235. countRestartedJobs++;
  236. } else if (!failedUpload.isUseWifiOnly() &&
  237. !Device.getNetworkType(context).equals(JobRequest.NetworkType.ANY)) {
  238. uploadRequester.retry(context, failedUpload);
  239. restartedInCurrentIteration = true;
  240. countRestartedJobs++;
  241. }
  242. }
  243. // Handle case of wifi
  244. if (!restartedInCurrentIteration) {
  245. if (failedUpload.isUseWifiOnly() &&
  246. Device.getNetworkType(context).equals(JobRequest.NetworkType.UNMETERED)) {
  247. uploadRequester.retry(context, failedUpload);
  248. countRestartedJobs++;
  249. } else if (!failedUpload.isUseWifiOnly() &&
  250. !Device.getNetworkType(context).equals(JobRequest.NetworkType.ANY)) {
  251. uploadRequester.retry(context, failedUpload);
  252. countRestartedJobs++;
  253. }
  254. }
  255. }
  256. } else {
  257. uploadsStorageManager.removeUpload(failedUpload);
  258. }
  259. } else {
  260. if (accountExists && fileExists) {
  261. if (countRestartedJobs < 5) {
  262. uploadRequester.retry(context, failedUpload);
  263. countRestartedJobs++;
  264. }
  265. } else {
  266. uploadsStorageManager.removeUpload(failedUpload);
  267. }
  268. }
  269. }
  270. }
  271. }
  272. }