SyncedFolderProvider.java 15 KB

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