DocumentsStorageProvider.java 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. /*
  2. * Nextcloud Android client application
  3. *
  4. * @author Bartosz Przybylski
  5. * Copyright (C) 2016 Bartosz Przybylski <bart.p.pl@gmail.com>
  6. *
  7. * This program is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License version 2,
  9. * as published by the Free Software Foundation.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. *
  19. */
  20. package com.owncloud.android.providers;
  21. import android.accounts.Account;
  22. import android.accounts.AuthenticatorException;
  23. import android.accounts.OperationCanceledException;
  24. import android.annotation.SuppressLint;
  25. import android.annotation.TargetApi;
  26. import android.content.ContentResolver;
  27. import android.content.Context;
  28. import android.content.Intent;
  29. import android.content.SharedPreferences;
  30. import android.content.res.AssetFileDescriptor;
  31. import android.database.Cursor;
  32. import android.graphics.Point;
  33. import android.os.Build;
  34. import android.os.CancellationSignal;
  35. import android.os.Handler;
  36. import android.os.Looper;
  37. import android.os.ParcelFileDescriptor;
  38. import android.preference.PreferenceManager;
  39. import android.provider.DocumentsProvider;
  40. import android.util.Log;
  41. import android.widget.Toast;
  42. import com.owncloud.android.MainApp;
  43. import com.owncloud.android.R;
  44. import com.owncloud.android.authentication.AccountUtils;
  45. import com.owncloud.android.datamodel.FileDataStorageManager;
  46. import com.owncloud.android.datamodel.OCFile;
  47. import com.owncloud.android.files.services.FileDownloader;
  48. import com.owncloud.android.lib.common.OwnCloudAccount;
  49. import com.owncloud.android.lib.common.OwnCloudClient;
  50. import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
  51. import com.owncloud.android.lib.common.operations.RemoteOperationResult;
  52. import com.owncloud.android.lib.common.utils.Log_OC;
  53. import com.owncloud.android.operations.CreateFolderOperation;
  54. import com.owncloud.android.operations.SynchronizeFileOperation;
  55. import com.owncloud.android.ui.activity.ConflictsResolveActivity;
  56. import com.owncloud.android.ui.activity.Preferences;
  57. import com.owncloud.android.utils.FileStorageUtils;
  58. import org.nextcloud.providers.cursors.FileCursor;
  59. import org.nextcloud.providers.cursors.RootCursor;
  60. import java.io.File;
  61. import java.io.FileNotFoundException;
  62. import java.io.IOException;
  63. import java.util.ArrayList;
  64. import java.util.HashMap;
  65. import java.util.List;
  66. import java.util.Map;
  67. @TargetApi(Build.VERSION_CODES.KITKAT)
  68. public class DocumentsStorageProvider extends DocumentsProvider {
  69. private static final String TAG = "DocumentsStorageProvider";
  70. private FileDataStorageManager currentStorageManager;
  71. private Map<Long, FileDataStorageManager> rootIdToStorageManager;
  72. private OwnCloudClient client;
  73. @Override
  74. public Cursor queryRoots(String[] projection) throws FileNotFoundException {
  75. SharedPreferences appPrefs = PreferenceManager.getDefaultSharedPreferences(MainApp.getAppContext());
  76. if (Preferences.LOCK_PASSCODE.equals(appPrefs.getString(Preferences.PREFERENCE_LOCK, "")) ||
  77. Preferences.LOCK_DEVICE_CREDENTIALS.equals(appPrefs.getString(Preferences.PREFERENCE_LOCK, ""))) {
  78. return new FileCursor();
  79. }
  80. initiateStorageMap();
  81. final RootCursor result = new RootCursor(projection);
  82. for (Account account : AccountUtils.getAccounts(getContext())) {
  83. result.addRoot(account, getContext());
  84. }
  85. return result;
  86. }
  87. @Override
  88. public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
  89. final long docId = Long.parseLong(documentId);
  90. updateCurrentStorageManagerIfNeeded(docId);
  91. final FileCursor result = new FileCursor(projection);
  92. if (currentStorageManager == null) {
  93. for (Map.Entry<Long, FileDataStorageManager> entry : rootIdToStorageManager.entrySet()) {
  94. if (entry.getValue().getFileById(docId) != null) {
  95. currentStorageManager = entry.getValue();
  96. break;
  97. }
  98. }
  99. }
  100. if (currentStorageManager == null) {
  101. throw new FileNotFoundException("File with " + documentId + " not found");
  102. }
  103. OCFile file = currentStorageManager.getFileById(docId);
  104. if (file != null) {
  105. result.addFile(file);
  106. }
  107. return result;
  108. }
  109. @Override
  110. public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) {
  111. final long folderId = Long.parseLong(parentDocumentId);
  112. updateCurrentStorageManagerIfNeeded(folderId);
  113. final FileCursor result = new FileCursor(projection);
  114. final OCFile browsedDir = currentStorageManager.getFileById(folderId);
  115. for (OCFile file : currentStorageManager.getFolderContent(browsedDir, false)) {
  116. result.addFile(file);
  117. }
  118. return result;
  119. }
  120. @SuppressLint("LongLogTag")
  121. @Override
  122. public ParcelFileDescriptor openDocument(String documentId, String mode, CancellationSignal cancellationSignal)
  123. throws FileNotFoundException {
  124. final long docId = Long.parseLong(documentId);
  125. updateCurrentStorageManagerIfNeeded(docId);
  126. OCFile file = currentStorageManager.getFileById(docId);
  127. if (file == null) {
  128. throw new FileNotFoundException("File with id " + documentId + " not found!");
  129. }
  130. Account account = currentStorageManager.getAccount();
  131. Context context = getContext();
  132. if (context == null) {
  133. throw new FileNotFoundException("Context may not be null!");
  134. }
  135. if (!file.isDown()) {
  136. Intent i = new Intent(getContext(), FileDownloader.class);
  137. i.putExtra(FileDownloader.EXTRA_ACCOUNT, account);
  138. i.putExtra(FileDownloader.EXTRA_FILE, file);
  139. if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
  140. context.startForegroundService(i);
  141. } else {
  142. context.startService(i);
  143. }
  144. do {
  145. if (!waitOrGetCancelled(cancellationSignal)) {
  146. throw new FileNotFoundException("File with id " + documentId + " not found!");
  147. }
  148. file = currentStorageManager.getFileById(docId);
  149. if (file == null) {
  150. throw new FileNotFoundException("File with id " + documentId + " not found!");
  151. }
  152. } while (!file.isDown());
  153. } else {
  154. OCFile finalFile = file;
  155. Thread syncThread = new Thread(() -> {
  156. try {
  157. FileDataStorageManager storageManager =
  158. new FileDataStorageManager(account, context.getContentResolver());
  159. SynchronizeFileOperation sfo =
  160. new SynchronizeFileOperation(finalFile, null, account, true, context);
  161. RemoteOperationResult result = sfo.execute(storageManager, context);
  162. if (result.getCode() == RemoteOperationResult.ResultCode.SYNC_CONFLICT) {
  163. // ISSUE 5: if the user is not running the app (this is a service!),
  164. // this can be very intrusive; a notification should be preferred
  165. Intent i = new Intent(context, ConflictsResolveActivity.class);
  166. i.setFlags(i.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
  167. i.putExtra(ConflictsResolveActivity.EXTRA_FILE, finalFile);
  168. i.putExtra(ConflictsResolveActivity.EXTRA_ACCOUNT, account);
  169. context.startActivity(i);
  170. } else {
  171. FileStorageUtils.checkIfFileFinishedSaving(finalFile);
  172. if (!result.isSuccess()) {
  173. showToast();
  174. }
  175. }
  176. } catch (Exception exception) {
  177. showToast();
  178. }
  179. });
  180. syncThread.start();
  181. try {
  182. syncThread.join();
  183. } catch (InterruptedException e) {
  184. Log.e(TAG, "Failed to wait for thread to finish");
  185. }
  186. }
  187. return ParcelFileDescriptor.open(new File(file.getStoragePath()), ParcelFileDescriptor.parseMode(mode));
  188. }
  189. private void showToast() {
  190. Handler handler = new Handler(Looper.getMainLooper());
  191. handler.post(() -> Toast.makeText(MainApp.getAppContext(),
  192. R.string.file_not_synced,
  193. Toast.LENGTH_SHORT).show());
  194. }
  195. @Override
  196. public boolean onCreate() {
  197. return true;
  198. }
  199. @Override
  200. public AssetFileDescriptor openDocumentThumbnail(String documentId,
  201. Point sizeHint,
  202. CancellationSignal signal)
  203. throws FileNotFoundException {
  204. long docId = Long.parseLong(documentId);
  205. updateCurrentStorageManagerIfNeeded(docId);
  206. OCFile file = currentStorageManager.getFileById(docId);
  207. if (file == null) {
  208. throw new FileNotFoundException("File with id " + documentId + " not found!");
  209. }
  210. File realFile = new File(file.getStoragePath());
  211. return new AssetFileDescriptor(
  212. ParcelFileDescriptor.open(realFile, ParcelFileDescriptor.MODE_READ_ONLY),
  213. 0,
  214. AssetFileDescriptor.UNKNOWN_LENGTH);
  215. }
  216. @Override
  217. public Cursor querySearchDocuments(String rootId, String query, String[] projection) {
  218. updateCurrentStorageManagerIfNeeded(rootId);
  219. OCFile root = currentStorageManager.getFileByPath("/");
  220. FileCursor result = new FileCursor(projection);
  221. for (OCFile f : findFiles(root, query)) {
  222. result.addFile(f);
  223. }
  224. return result;
  225. }
  226. @Override
  227. public String createDocument(String documentId, String mimeType, String displayName) throws FileNotFoundException {
  228. long docId = Long.parseLong(documentId);
  229. updateCurrentStorageManagerIfNeeded(docId);
  230. OCFile parent = currentStorageManager.getFileById(docId);
  231. if (parent == null) {
  232. throw new FileNotFoundException("Parent file not found");
  233. }
  234. CreateFolderOperation createFolderOperation = new CreateFolderOperation(parent.getRemotePath() + displayName
  235. + "/", true);
  236. RemoteOperationResult result = createFolderOperation.execute(client, currentStorageManager);
  237. if (!result.isSuccess()) {
  238. throw new FileNotFoundException("Failed to create document with name " +
  239. displayName + " and documentId " + documentId);
  240. }
  241. String newDirPath = parent.getRemotePath() + displayName + "/";
  242. OCFile newFolder = currentStorageManager.getFileByPath(newDirPath);
  243. return String.valueOf(newFolder.getFileId());
  244. }
  245. @SuppressLint("LongLogTag")
  246. private void updateCurrentStorageManagerIfNeeded(long docId) {
  247. if (rootIdToStorageManager == null) {
  248. try {
  249. queryRoots(FileCursor.DEFAULT_DOCUMENT_PROJECTION);
  250. } catch (FileNotFoundException e) {
  251. Log.e(TAG, "Failed to query roots");
  252. }
  253. }
  254. if (currentStorageManager == null ||
  255. rootIdToStorageManager.containsKey(docId) && currentStorageManager != rootIdToStorageManager.get(docId)) {
  256. currentStorageManager = rootIdToStorageManager.get(docId);
  257. }
  258. try {
  259. Account account = currentStorageManager.getAccount();
  260. OwnCloudAccount ocAccount = new OwnCloudAccount(account, MainApp.getAppContext());
  261. client = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(ocAccount, getContext());
  262. } catch (OperationCanceledException | IOException | AuthenticatorException |
  263. com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException e) {
  264. Log_OC.e(TAG, "Failed to set client", e);
  265. }
  266. }
  267. private void updateCurrentStorageManagerIfNeeded(String rootId) {
  268. for (FileDataStorageManager data : rootIdToStorageManager.values()) {
  269. if (data.getAccount().name.equals(rootId)) {
  270. currentStorageManager = data;
  271. }
  272. }
  273. }
  274. @SuppressLint("UseSparseArrays")
  275. private void initiateStorageMap() throws FileNotFoundException {
  276. rootIdToStorageManager = new HashMap<>();
  277. Context context = getContext();
  278. if (context == null) {
  279. throw new FileNotFoundException("Context may not be null!");
  280. }
  281. ContentResolver contentResolver = context.getContentResolver();
  282. for (Account account : AccountUtils.getAccounts(getContext())) {
  283. final FileDataStorageManager storageManager = new FileDataStorageManager(account, contentResolver);
  284. final OCFile rootDir = storageManager.getFileByPath("/");
  285. rootIdToStorageManager.put(rootDir.getFileId(), storageManager);
  286. }
  287. }
  288. private boolean waitOrGetCancelled(CancellationSignal cancellationSignal) {
  289. try {
  290. Thread.sleep(1000);
  291. } catch (InterruptedException e) {
  292. return false;
  293. }
  294. return !(cancellationSignal != null && cancellationSignal.isCanceled());
  295. }
  296. List<OCFile> findFiles(OCFile root, String query) {
  297. List<OCFile> result = new ArrayList<>();
  298. for (OCFile f : currentStorageManager.getFolderContent(root, false)) {
  299. if (f.isFolder()) {
  300. result.addAll(findFiles(f, query));
  301. } else if (f.getFileName().contains(query)) {
  302. result.add(f);
  303. }
  304. }
  305. return result;
  306. }
  307. }