SyncedFolderProvider.java 18 KB

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