/** * Nextcloud Android client application * * Copyright (C) 2016 Andy Scherzinger * Copyright (C) 2016 Nextcloud. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE * License as published by the Free Software Foundation; either * version 3 of the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU AFFERO GENERAL PUBLIC LICENSE for more details. * * You should have received a copy of the GNU Affero General Public * License along with this program. If not, see . */ package com.owncloud.android.datamodel; import android.accounts.Account; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.support.annotation.NonNull; import com.owncloud.android.MainApp; import com.owncloud.android.db.PreferenceManager; import com.owncloud.android.db.ProviderMeta; import com.owncloud.android.lib.common.utils.Log_OC; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Observable; /** * Database provider for handling the persistence aspects of {@link SyncedFolder}s. */ public class SyncedFolderProvider extends Observable { static private final String TAG = SyncedFolderProvider.class.getSimpleName(); private ContentResolver mContentResolver; /** * constructor. * * @param contentResolver the ContentResolver to work with. */ public SyncedFolderProvider(ContentResolver contentResolver) { if (contentResolver == null) { throw new IllegalArgumentException("Cannot create an instance with a NULL contentResolver"); } mContentResolver = contentResolver; } /** * Stores an media folder sync object in database. * * @param syncedFolder synced folder to store * @return synced folder id, -1 if the insert process fails. */ public long storeFolderSync(SyncedFolder syncedFolder) { Log_OC.v(TAG, "Inserting " + syncedFolder.getLocalPath() + " with enabled=" + syncedFolder.isEnabled()); ContentValues cv = createContentValuesFromSyncedFolder(syncedFolder); Uri result = mContentResolver.insert(ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, cv); if (result != null) { notifyFolderSyncObservers(syncedFolder); return Long.parseLong(result.getPathSegments().get(1)); } else { Log_OC.e(TAG, "Failed to insert item " + syncedFolder.getLocalPath() + " into folder sync db."); return -1; } } /** * get all synced folder entries. * * @return all synced folder entries, empty if none have been found */ public List getSyncedFolders() { Cursor cursor = mContentResolver.query( ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, null, "1=1", null, null ); if (cursor != null) { List list = new ArrayList<>(cursor.getCount()); if (cursor.moveToFirst()) { do { SyncedFolder syncedFolder = createSyncedFolderFromCursor(cursor); if (syncedFolder == null) { Log_OC.e(TAG, "SyncedFolder could not be created from cursor"); } else { list.add(cursor.getPosition(), syncedFolder); } } while (cursor.moveToNext()); } cursor.close(); return list; } else { Log_OC.e(TAG, "DB error creating read all cursor for synced folders."); } return new ArrayList<>(0); } /** * Update upload status of file uniquely referenced by id. * * @param id folder sync id. * @param enabled new status. * @return the number of rows updated. */ public int updateFolderSyncEnabled(long id, Boolean enabled) { Log_OC.v(TAG, "Storing sync folder id" + id + " with enabled=" + enabled); int result = 0; Cursor cursor = mContentResolver.query( ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, null, ProviderMeta.ProviderTableMeta._ID + "=?", new String[]{String.valueOf(id)}, null ); if (cursor != null && cursor.getCount() == 1) { while (cursor.moveToNext()) { // read sync folder object and update SyncedFolder syncedFolder = createSyncedFolderFromCursor(cursor); syncedFolder.setEnabled(enabled); // update sync folder object in db result = updateSyncFolder(syncedFolder); cursor.close(); } } else { if (cursor == null) { Log_OC.e(TAG, "Sync folder db cursor for ID=" + id + " in NULL."); } else { Log_OC.e(TAG, cursor.getCount() + " items for id=" + id + " available in sync folder database. " + "Expected 1. Failed to update sync folder db."); } } return result; } /** * find a synced folder by local path. * * @param localPath the local path of the local folder * @return the synced folder if found, else null */ public SyncedFolder findByLocalPath(String localPath) { SyncedFolder result = null; Cursor cursor = mContentResolver.query( ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, null, ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_LOCAL_PATH + "== \"" + localPath + "\"", null, null ); if (cursor != null && cursor.getCount() == 1) { result = createSyncedFolderFromCursor(cursor); cursor.close(); } else { if (cursor == null) { Log_OC.e(TAG, "Sync folder db cursor for local path=" + localPath + " in NULL."); } else { Log_OC.e(TAG, cursor.getCount() + " items for local path=" + localPath + " available in sync folder db. Expected 1. Failed to update sync folder db."); } } return result; } /** * Delete all synced folders for an account * * @param account whose synced folders should be deleted */ public int deleteSyncFoldersForAccount(Account account) { int result = mContentResolver.delete( ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + " = ?", new String[]{String.valueOf(account.name)} ); return result; } /** * Delete a synced folder from the db * * @param id for the synced folder. */ private int deleteSyncFolderWithId(long id) { int result = mContentResolver.delete( ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, ProviderMeta.ProviderTableMeta._ID + " = ?", new String[]{String.valueOf(id)} ); return result; } /** * Try to figure out if a path exists for synced folder, and if not, go one folder back * Otherwise, delete the entry * * @param context the context. */ public void updateAutoUploadPaths(Context context) { List syncedFolders = getSyncedFolders(); for (int i = 0; i < syncedFolders.size(); i++) { SyncedFolder syncedFolder = syncedFolders.get(i); if (!new File(syncedFolder.getLocalPath()).exists()) { String localPath = syncedFolder.getLocalPath(); if (localPath.endsWith("/")) { localPath = localPath.substring(0, localPath.lastIndexOf("/")); } localPath = localPath.substring(0, localPath.lastIndexOf("/")); if (new File(localPath).exists()) { syncedFolders.get(i).setLocalPath(localPath); updateSyncFolder(syncedFolder); } else { deleteSyncFolderWithId(syncedFolder.getId()); } } } if (context != null) { PreferenceManager.setAutoUploadPathsUpdate(context, true); } } /** * delete any records of synchronized folders that are not within the given list of ids. * * @param context the context. * @param ids the list of ids to be excluded from deletion. * @return number of deleted records. */ public int deleteSyncedFoldersNotInList(Context context, ArrayList ids) { int result = mContentResolver.delete( ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, ProviderMeta.ProviderTableMeta._ID + " NOT IN (?)", new String[]{String.valueOf(ids)} ); if (result > 0 && context != null) { PreferenceManager.setLegacyClean(context, true); } return result; } /** * update given synced folder. * * @param syncedFolder the synced folder to be updated. * @return the number of rows updated. */ public int updateSyncFolder(SyncedFolder syncedFolder) { Log_OC.v(TAG, "Updating " + syncedFolder.getLocalPath() + " with enabled=" + syncedFolder.isEnabled()); ContentValues cv = createContentValuesFromSyncedFolder(syncedFolder); int result = mContentResolver.update( ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, cv, ProviderMeta.ProviderTableMeta._ID + "=?", new String[]{String.valueOf(syncedFolder.getId())} ); if (result > 0) { notifyFolderSyncObservers(syncedFolder); } return result; } /** * maps a cursor into a SyncedFolder object. * * @param cursor the db cursor * @return the mapped SyncedFolder, null if cursor is null */ private SyncedFolder createSyncedFolderFromCursor(Cursor cursor) { SyncedFolder syncedFolder = null; if (cursor != null) { long id = cursor.getLong(cursor.getColumnIndex(ProviderMeta.ProviderTableMeta._ID)); String localPath = cursor.getString(cursor.getColumnIndex( ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_LOCAL_PATH)); String remotePath = cursor.getString(cursor.getColumnIndex( ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_REMOTE_PATH)); Boolean wifiOnly = cursor.getInt(cursor.getColumnIndex( ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_WIFI_ONLY)) == 1; Boolean chargingOnly = cursor.getInt(cursor.getColumnIndex( ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY)) == 1; Boolean subfolderByDate = cursor.getInt(cursor.getColumnIndex( ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE)) == 1; String accountName = cursor.getString(cursor.getColumnIndex( ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT)); Integer uploadAction = cursor.getInt(cursor.getColumnIndex( ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION)); Boolean enabled = cursor.getInt(cursor.getColumnIndex( ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED)) == 1; syncedFolder = new SyncedFolder(id, localPath, remotePath, wifiOnly, chargingOnly, subfolderByDate, accountName, uploadAction, enabled); } return syncedFolder; } /** * create ContentValues object based on given SyncedFolder. * * @param syncedFolder the synced folder * @return the corresponding ContentValues object */ @NonNull private ContentValues createContentValuesFromSyncedFolder(SyncedFolder syncedFolder) { ContentValues cv = new ContentValues(); cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_LOCAL_PATH, syncedFolder.getLocalPath()); cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_REMOTE_PATH, syncedFolder.getRemotePath()); cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_WIFI_ONLY, syncedFolder.getWifiOnly()); cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_CHARGING_ONLY, syncedFolder.getChargingOnly()); cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED, syncedFolder.isEnabled()); cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE, syncedFolder.getSubfolderByDate()); cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT, syncedFolder.getAccount()); cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION, syncedFolder.getUploadAction()); return cv; } /** * Inform all observers about data change. * * @param syncedFolder changed, synchronized folder */ private void notifyFolderSyncObservers(SyncedFolder syncedFolder) { if (syncedFolder != null) { MainApp.getSyncedFolderObserverService().restartObserver(syncedFolder); Log_OC.d(TAG, "notifying folder sync data observers for changed/added: " + syncedFolder.getLocalPath()); } } }