SyncedFolderProvider.java 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. /*
  2. * Nextcloud Android client application
  3. *
  4. * @author Andy Scherzinger
  5. * Copyright (C) 2016 Andy Scherzinger
  6. * Copyright (C) 2016 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.datamodel;
  22. import android.accounts.Account;
  23. import android.content.ContentResolver;
  24. import android.content.ContentValues;
  25. import android.content.Context;
  26. import android.database.Cursor;
  27. import android.net.Uri;
  28. import com.nextcloud.client.account.User;
  29. import com.nextcloud.client.core.Clock;
  30. import com.nextcloud.client.preferences.AppPreferences;
  31. import com.nextcloud.client.preferences.AppPreferencesImpl;
  32. import com.nextcloud.client.preferences.SubFolderRule;
  33. import com.owncloud.android.db.ProviderMeta;
  34. import com.owncloud.android.lib.common.utils.Log_OC;
  35. import com.owncloud.android.lib.resources.files.model.ServerFileInterface;
  36. import java.io.File;
  37. import java.util.ArrayList;
  38. import java.util.List;
  39. import java.util.Observable;
  40. import androidx.annotation.NonNull;
  41. import static com.owncloud.android.datamodel.OCFile.PATH_SEPARATOR;
  42. /**
  43. * Database provider for handling the persistence aspects of {@link SyncedFolder}s.
  44. */
  45. public class SyncedFolderProvider extends Observable {
  46. static private final String TAG = SyncedFolderProvider.class.getSimpleName();
  47. private final ContentResolver mContentResolver;
  48. private final AppPreferences preferences;
  49. private final Clock clock;
  50. /**
  51. * constructor.
  52. *
  53. * @param contentResolver the ContentResolver to work with.
  54. */
  55. public SyncedFolderProvider(ContentResolver contentResolver, AppPreferences preferences, Clock clock) {
  56. if (contentResolver == null) {
  57. throw new IllegalArgumentException("Cannot create an instance with a NULL contentResolver");
  58. }
  59. mContentResolver = contentResolver;
  60. this.preferences = preferences;
  61. this.clock = clock;
  62. }
  63. /**
  64. * Stores a synced folder object in database.
  65. *
  66. * @param syncedFolder synced folder to store
  67. * @return synced folder id, -1 if the insert process fails.
  68. */
  69. public long storeSyncedFolder(SyncedFolder syncedFolder) {
  70. Log_OC.v(TAG, "Inserting " + syncedFolder.getLocalPath() + " with enabled=" + syncedFolder.isEnabled());
  71. ContentValues cv = createContentValuesFromSyncedFolder(syncedFolder);
  72. Uri result = mContentResolver.insert(ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, cv);
  73. if (result != null) {
  74. return Long.parseLong(result.getPathSegments().get(1));
  75. } else {
  76. Log_OC.e(TAG, "Failed to insert item " + syncedFolder.getLocalPath() + " into folder sync db.");
  77. return -1;
  78. }
  79. }
  80. public static boolean isAutoUploadFolder(SyncedFolderProvider syncedFolderProvider, ServerFileInterface file, User user) {
  81. return syncedFolderProvider != null && syncedFolderProvider.findByRemotePathAndAccount(file.getRemotePath(), user);
  82. }
  83. public int countEnabledSyncedFolders() {
  84. int count = 0;
  85. Cursor cursor = mContentResolver.query(
  86. ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
  87. null,
  88. ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED + " = ?",
  89. new String[]{"1"},
  90. null
  91. );
  92. if (cursor != null) {
  93. count = cursor.getCount();
  94. cursor.close();
  95. }
  96. return count;
  97. }
  98. /**
  99. * get all synced folder entries.
  100. *
  101. * @return all synced folder entries, empty if none have been found
  102. */
  103. public List<SyncedFolder> getSyncedFolders() {
  104. Cursor cursor = mContentResolver.query(
  105. ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
  106. null,
  107. null,
  108. null,
  109. null
  110. );
  111. if (cursor != null) {
  112. List<SyncedFolder> list = new ArrayList<>(cursor.getCount());
  113. if (cursor.moveToFirst()) {
  114. do {
  115. SyncedFolder syncedFolder = createSyncedFolderFromCursor(cursor);
  116. if (syncedFolder == null) {
  117. Log_OC.e(TAG, "SyncedFolder could not be created from cursor");
  118. } else {
  119. list.add(cursor.getPosition(), syncedFolder);
  120. }
  121. } while (cursor.moveToNext());
  122. }
  123. cursor.close();
  124. return list;
  125. } else {
  126. Log_OC.e(TAG, "DB error creating read all cursor for synced folders.");
  127. }
  128. return new ArrayList<>(0);
  129. }
  130. /**
  131. * Update upload status of file uniquely referenced by id.
  132. *
  133. * @param id synced folder id.
  134. * @param enabled new status.
  135. * @return the number of rows updated.
  136. */
  137. public int updateSyncedFolderEnabled(long id, Boolean enabled) {
  138. Log_OC.v(TAG, "Storing synced folder id" + id + " with enabled=" + enabled);
  139. int result = 0;
  140. Cursor cursor = mContentResolver.query(
  141. ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
  142. null,
  143. ProviderMeta.ProviderTableMeta._ID + "=?",
  144. new String[]{String.valueOf(id)},
  145. null
  146. );
  147. if (cursor != null && cursor.getCount() == 1) {
  148. while (cursor.moveToNext()) {
  149. // read sync folder object and update
  150. SyncedFolder syncedFolder = createSyncedFolderFromCursor(cursor);
  151. syncedFolder.setEnabled(enabled, clock.getCurrentTime());
  152. // update sync folder object in db
  153. result = updateSyncFolder(syncedFolder);
  154. }
  155. } else {
  156. if (cursor == null) {
  157. Log_OC.e(TAG, "Sync folder db cursor for ID=" + id + " in NULL.");
  158. } else {
  159. Log_OC.e(TAG, cursor.getCount() + " items for id=" + id + " available in sync folder database. " +
  160. "Expected 1. Failed to update sync folder db.");
  161. }
  162. }
  163. if (cursor != null) {
  164. cursor.close();
  165. }
  166. return result;
  167. }
  168. public SyncedFolder findByLocalPathAndAccount(String localPath, User user) {
  169. SyncedFolder result = null;
  170. Cursor cursor = mContentResolver.query(
  171. ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
  172. null,
  173. ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_LOCAL_PATH + " LIKE ? AND " +
  174. ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + " =? ",
  175. new String[]{localPath + "%", user.getAccountName()},
  176. null
  177. );
  178. if (cursor != null && cursor.getCount() == 1) {
  179. result = createSyncedFolderFromCursor(cursor);
  180. } else {
  181. if (cursor == null) {
  182. Log_OC.e(TAG, "Sync folder db cursor for local path=" + localPath + " in NULL.");
  183. } else {
  184. Log_OC.e(TAG, cursor.getCount() + " items for local path=" + localPath
  185. + " available in sync folder db. Expected 1. Failed to update sync folder db.");
  186. }
  187. }
  188. if (cursor != null) {
  189. cursor.close();
  190. }
  191. return result;
  192. }
  193. /**
  194. * Delete all synced folders for an account
  195. *
  196. * @param user whose synced folders should be deleted
  197. */
  198. public int deleteSyncFoldersForAccount(User user) {
  199. return mContentResolver.delete(
  200. ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
  201. ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + " = ?",
  202. new String[]{String.valueOf(user.getAccountName())}
  203. );
  204. }
  205. /**
  206. * Delete a synced folder from the db
  207. *
  208. * @param id for the synced folder.
  209. */
  210. private int deleteSyncFolderWithId(long id) {
  211. return mContentResolver.delete(
  212. ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
  213. ProviderMeta.ProviderTableMeta._ID + " = ?",
  214. new String[]{String.valueOf(id)}
  215. );
  216. }
  217. /**
  218. * Try to figure out if a path exists for synced folder, and if not, go one folder back
  219. * Otherwise, delete the entry
  220. *
  221. * @param context the context.
  222. */
  223. public void updateAutoUploadPaths(Context context) {
  224. List<SyncedFolder> syncedFolders = getSyncedFolders();
  225. for (SyncedFolder syncedFolder : syncedFolders) {
  226. if (!new File(syncedFolder.getLocalPath()).exists()) {
  227. String localPath = syncedFolder.getLocalPath();
  228. if (localPath.endsWith(PATH_SEPARATOR)) {
  229. localPath = localPath.substring(0, localPath.lastIndexOf('/'));
  230. }
  231. localPath = localPath.substring(0, localPath.lastIndexOf('/'));
  232. if (new File(localPath).exists()) {
  233. syncedFolder.setLocalPath(localPath);
  234. updateSyncFolder(syncedFolder);
  235. } else {
  236. deleteSyncFolderWithId(syncedFolder.getId());
  237. }
  238. }
  239. }
  240. if (context != null) {
  241. AppPreferences preferences = AppPreferencesImpl.fromContext(context);
  242. preferences.setAutoUploadPathsUpdateEnabled(true);
  243. }
  244. }
  245. /**
  246. * delete any records of synchronized folders that are not within the given list of ids.
  247. *
  248. * @param ids the list of ids to be excluded from deletion.
  249. * @return number of deleted records.
  250. */
  251. public int deleteSyncedFoldersNotInList(List<Long> ids) {
  252. int result = mContentResolver.delete(
  253. ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
  254. ProviderMeta.ProviderTableMeta._ID + " NOT IN (?)",
  255. new String[]{String.valueOf(ids)}
  256. );
  257. if(result > 0) {
  258. preferences.setLegacyClean(true);
  259. }
  260. return result;
  261. }
  262. /**
  263. * delete record of synchronized folder with the given id.
  264. */
  265. public int deleteSyncedFolder(long id) {
  266. return mContentResolver.delete(
  267. ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
  268. ProviderMeta.ProviderTableMeta._ID + " = ?",
  269. new String[]{String.valueOf(id)}
  270. );
  271. }
  272. public AppPreferences getPreferences() {
  273. return preferences;
  274. }
  275. /**
  276. * update given synced folder.
  277. *
  278. * @param syncedFolder the synced folder to be updated.
  279. * @return the number of rows updated.
  280. */
  281. public int updateSyncFolder(SyncedFolder syncedFolder) {
  282. Log_OC.v(TAG, "Updating " + syncedFolder.getLocalPath() + " with enabled=" + syncedFolder.isEnabled());
  283. ContentValues cv = createContentValuesFromSyncedFolder(syncedFolder);
  284. return mContentResolver.update(
  285. ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
  286. cv,
  287. ProviderMeta.ProviderTableMeta._ID + "=?",
  288. new String[]{String.valueOf(syncedFolder.getId())}
  289. );
  290. }
  291. /**
  292. * maps a cursor into a SyncedFolder object.
  293. *
  294. * @param cursor the db cursor
  295. * @return the mapped SyncedFolder, null if cursor is null
  296. */
  297. private SyncedFolder createSyncedFolderFromCursor(Cursor cursor) {
  298. SyncedFolder syncedFolder = null;
  299. if (cursor != null) {
  300. long id = cursor.getLong(cursor.getColumnIndexOrThrow(ProviderMeta.ProviderTableMeta._ID));
  301. String localPath = cursor.getString(cursor.getColumnIndexOrThrow(
  302. ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_LOCAL_PATH));
  303. String remotePath = cursor.getString(cursor.getColumnIndexOrThrow(
  304. ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_REMOTE_PATH));
  305. boolean wifiOnly = cursor.getInt(cursor.getColumnIndexOrThrow(
  306. ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_WIFI_ONLY)) == 1;
  307. boolean chargingOnly = cursor.getInt(cursor.getColumnIndexOrThrow(
  308. ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY)) == 1;
  309. boolean existing = cursor.getInt(cursor.getColumnIndexOrThrow(
  310. ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_EXISTING)) == 1;
  311. boolean subfolderByDate = cursor.getInt(cursor.getColumnIndexOrThrow(
  312. ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE)) == 1;
  313. String accountName = cursor.getString(cursor.getColumnIndexOrThrow(
  314. ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT));
  315. int uploadAction = cursor.getInt(cursor.getColumnIndexOrThrow(
  316. ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION));
  317. int nameCollisionPolicy = cursor.getInt(cursor.getColumnIndexOrThrow(
  318. ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_NAME_COLLISION_POLICY));
  319. boolean enabled = cursor.getInt(cursor.getColumnIndexOrThrow(
  320. ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED)) == 1;
  321. long enabledTimestampMs = cursor.getLong(cursor.getColumnIndexOrThrow(
  322. ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED_TIMESTAMP_MS));
  323. MediaFolderType type = MediaFolderType.getById(cursor.getInt(cursor.getColumnIndexOrThrow(
  324. ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_TYPE)));
  325. boolean hidden = cursor.getInt(cursor.getColumnIndexOrThrow(
  326. ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_HIDDEN)) == 1;
  327. SubFolderRule subFolderRule = SubFolderRule.values()[cursor.getInt(
  328. cursor.getColumnIndexOrThrow(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_RULE))];
  329. syncedFolder = new SyncedFolder(id,
  330. localPath,
  331. remotePath,
  332. wifiOnly,
  333. chargingOnly,
  334. existing,
  335. subfolderByDate,
  336. accountName,
  337. uploadAction,
  338. nameCollisionPolicy,
  339. enabled,
  340. enabledTimestampMs,
  341. type,
  342. hidden,
  343. subFolderRule);
  344. }
  345. return syncedFolder;
  346. }
  347. /**
  348. * create ContentValues object based on given SyncedFolder.
  349. *
  350. * @param syncedFolder the synced folder
  351. * @return the corresponding ContentValues object
  352. */
  353. @NonNull
  354. private ContentValues createContentValuesFromSyncedFolder(SyncedFolder syncedFolder) {
  355. ContentValues cv = new ContentValues();
  356. cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_LOCAL_PATH, syncedFolder.getLocalPath());
  357. cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_REMOTE_PATH, syncedFolder.getRemotePath());
  358. cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_WIFI_ONLY, syncedFolder.isWifiOnly());
  359. cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY, syncedFolder.isChargingOnly());
  360. cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_EXISTING, syncedFolder.isExisting());
  361. cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED, syncedFolder.isEnabled());
  362. cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED_TIMESTAMP_MS, syncedFolder.getEnabledTimestampMs());
  363. cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE, syncedFolder.isSubfolderByDate());
  364. cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT, syncedFolder.getAccount());
  365. cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION, syncedFolder.getUploadAction());
  366. cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_NAME_COLLISION_POLICY,
  367. syncedFolder.getNameCollisionPolicyInt());
  368. cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_TYPE, syncedFolder.getType().getId());
  369. cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_HIDDEN, syncedFolder.isHidden());
  370. cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_RULE, syncedFolder.getSubfolderRule().ordinal());
  371. return cv;
  372. }
  373. /**
  374. * method to check if sync folder for the remote path exist in table or not
  375. *
  376. * @param remotePath to be check
  377. * @param user for which we are looking
  378. * @return <code>true</code> if exist, <code>false</code> otherwise
  379. */
  380. public boolean findByRemotePathAndAccount(String remotePath, User user) {
  381. boolean result = false;
  382. //if path ends with / then remove the last / to work the query right way
  383. //because the sub folders of synced folders will not have the slash at the end
  384. if (remotePath.endsWith("/")) {
  385. remotePath = remotePath.substring(0, remotePath.length() - 1);
  386. }
  387. Cursor cursor = mContentResolver.query(
  388. ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
  389. null,
  390. ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_REMOTE_PATH + " LIKE ? AND " +
  391. ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + " =? ",
  392. new String[]{"%" + remotePath + "%", user.getAccountName()},
  393. null);
  394. if (cursor != null && cursor.getCount() >= 1) {
  395. result = true;
  396. } else {
  397. if (cursor == null) {
  398. Log_OC.e(TAG, "Sync folder db cursor for remote path = " + remotePath + " in NULL.");
  399. } else {
  400. Log_OC.e(TAG, cursor.getCount() + " items for remote path = " + remotePath
  401. + " available in sync folder db. Expected 1 or greater than 1. Failed to update sync folder db.");
  402. }
  403. }
  404. if (cursor != null) {
  405. cursor.close();
  406. }
  407. return result;
  408. }
  409. }