diff --git a/owncloudApp/src/main/AndroidManifest.xml b/owncloudApp/src/main/AndroidManifest.xml index 0abeee83540..8f15da93d0b 100644 --- a/owncloudApp/src/main/AndroidManifest.xml +++ b/owncloudApp/src/main/AndroidManifest.xml @@ -138,9 +138,6 @@ android:name="android.content.SyncAdapter" android:resource="@xml/syncadapter_files" /> - > { - return listOf() - // FIXME: 13/10/2020 : New_arch: Av.Offline -// val result = ArrayList>() -// var cursorOnKeptInSync: Cursor? = null -// try { -// cursorOnKeptInSync = performQuery( -// uri = CONTENT_URI, -// projection = null, -// selection = "$FILE_KEEP_IN_SYNC = ? OR $FILE_KEEP_IN_SYNC = ?", -// selectionArgs = arrayOf(AVAILABLE_OFFLINE.value.toString(), AVAILABLE_OFFLINE_PARENT.value.toString()), -// sortOrder = null, -// performWithContentProviderClient = false -// ) -// -// if (cursorOnKeptInSync != null && cursorOnKeptInSync.moveToFirst()) { -// var file: OCFile? -// var accountName: String -// do { -// file = createFileInstance(cursorOnKeptInSync) -// accountName = -// cursorOnKeptInSync.getStringFromColumnOrEmpty(FILE_ACCOUNT_OWNER) -// if (!file!!.isFolder && AccountUtils.exists(accountName, mContext)) { -// result.add(Pair(file, accountName)) -// } -// } while (cursorOnKeptInSync.moveToNext()) -// } else { -// Timber.d("No available offline files found") -// } -// -// } catch (e: Exception) { -// Timber.e(e, "Exception retrieving all the available offline files") -// -// } finally { -// cursorOnKeptInSync?.close() -// } -// -// return result - } - - /** - * Get a collection with all the files set by the user as available offline, from current account - * putting away files whose parent is also available offline - * - * @return List with all the files set by current user as available offline. - */ - val availableOfflineFilesFromCurrentAccount: Vector - get() { - return Vector() - // FIXME: 13/10/2020 : New_arch: Av.Offline -// val result = Vector() -// -// var cursorOnKeptInSync: Cursor? = null -// try { -// cursorOnKeptInSync = performQuery( -// uri = CONTENT_URI, -// projection = null, -// selection = "($FILE_KEEP_IN_SYNC = ? AND NOT $FILE_KEEP_IN_SYNC = ? ) AND $FILE_ACCOUNT_OWNER = ? ", -// selectionArgs = arrayOf( -// AVAILABLE_OFFLINE.value.toString(), -// AVAILABLE_OFFLINE_PARENT.value.toString(), -// account.name -// ), -// sortOrder = null, -// performWithContentProviderClient = false -// ) -// -// if (cursorOnKeptInSync != null && cursorOnKeptInSync.moveToFirst()) { -// var file: OCFile? -// do { -// file = createFileInstance(cursorOnKeptInSync) -// result.add(file) -// } while (cursorOnKeptInSync.moveToNext()) -// } else { -// Timber.d("No available offline files found") -// } -// -// } catch (e: Exception) { -// Timber.e(e, "Exception retrieving all the available offline files") -// -// } finally { -// cursorOnKeptInSync?.close() -// } -// -// return result.apply { sort() } - } - fun sharedByLinkFilesFromCurrentAccount(): List? = runBlocking(CoroutinesDispatcherProvider().io) { val getFilesSharedByLinkUseCase: GetFilesSharedByLinkUseCase by inject() @@ -498,23 +397,6 @@ class FileDataStorageManager : KoinComponent { // } } - /** - * Adds the appropriate initial value for FILE_KEEP_IN_SYNC to - * passed [ContentValues] instance. - * - * @param file [OCFile] which av-offline property will be set. - * @param cv [ContentValues] instance where the property is added. - */ - private fun setInitialAvailableOfflineStatus(file: OCFile, cv: ContentValues) { - // set appropriate av-off folder depending on ancestor - val inFolderAvailableOffline = isAnyAncestorAvailableOfflineFolder(file) - if (inFolderAvailableOffline) { - cv.put(FILE_KEEP_IN_SYNC, AVAILABLE_OFFLINE_PARENT.value) - } else { - cv.put(FILE_KEEP_IN_SYNC, NOT_AVAILABLE_OFFLINE.value) - } - } - fun migrateLegacyToScopedPath( legacyStorageDirectoryPath: String, rootStorageDirectoryPath: String, @@ -560,69 +442,6 @@ class FileDataStorageManager : KoinComponent { Timber.d("Updated path for ${filesWithPathUpdated.size} downloaded files") } - /** - * Updates available-offline status of OCFile received as a parameter, with its current value. - * - * - * Saves the new value property for the given file in persistent storage. - * - * - * If the file is a folder, updates the value of all its known descendants accordingly. - * - * @param file File which available-offline status will be updated. - * @return 'true' if value was updated, 'false' otherwise. - */ - fun saveLocalAvailableOfflineStatus(file: OCFile): Boolean { - return false - // FIXME: 13/10/2020 : New_arch: Av.Offline -// if (!fileExists(file.fileId)) { -// return false -// } -// -// val newStatus = file.availableOfflineStatus -// require(AVAILABLE_OFFLINE_PARENT != newStatus) { -// "Forbidden value, AVAILABLE_OFFLINE_PARENT is calculated, cannot be set" -// } -// -// val cv = ContentValues() -// cv.put(FILE_KEEP_IN_SYNC, file.availableOfflineStatus.value) -// -// var updatedCount: Int -// try { -// updatedCount = performUpdate( -// uri = CONTENT_URI, -// contentValues = cv, -// where = "$_ID=?", -// selectionArgs = arrayOf(file.fileId.toString()) -// ) -// -// // Update descendants -// if (file.isFolder && updatedCount > 0) { -// val descendantsCv = ContentValues() -// if (newStatus == AVAILABLE_OFFLINE) { -// // all descendant files MUST be av-off due to inheritance, not due to previous value -// descendantsCv.put(FILE_KEEP_IN_SYNC, AVAILABLE_OFFLINE_PARENT.value) -// } else { -// // all descendant files MUST be not-available offline -// descendantsCv.put(FILE_KEEP_IN_SYNC, NOT_AVAILABLE_OFFLINE.value) -// } -// val selectDescendants = selectionForAllDescendantsOf(file) -// updatedCount += performUpdate( -// uri = CONTENT_URI, -// contentValues = descendantsCv, -// where = selectDescendants.first, -// selectionArgs = selectDescendants.second -// ) -// } -// -// } catch (e: RemoteException) { -// Timber.e(e, "Fail updating available offline status") -// return false -// } -// -// return updatedCount > 0 - } - // TODO: New_arch: Remove this and call usecase inside FilesViewModel fun getFolderContent(parentId: Long): List = runBlocking(CoroutinesDispatcherProvider().io) { val getFolderContentUseCase: GetFolderContentUseCase by inject() @@ -633,37 +452,6 @@ class FileDataStorageManager : KoinComponent { result ?: listOf() } - /** - * Checks if it is favorite or it is inside a favorite folder - * - * @param file [OCFile] which ancestors will be searched. - * @return true/false - */ - // FIXME: 13/10/2020 : New_arch: Av.Offline - private fun isAnyAncestorAvailableOfflineFolder(file: OCFile) = false //getAvailableOfflineAncestorOf(file) != null - - /** - * Returns ancestor folder with available offline status AVAILABLE_OFFLINE. - * - * @param file [OCFile] which ancestors will be searched. - * @return Ancestor folder with available offline status AVAILABLE_OFFLINE, or null if - * does not exist. - */ - // FIXME: 13/10/2020 : New_arch: Av.Offline - private fun getAvailableOfflineAncestorOf(file: OCFile): OCFile? { - return null -// var avOffAncestor: OCFile? = null -// val parent = getFileById(file.parentId) -// if (parent != null && parent.isFolder) { // file is null for the parent of the root folder -// if (parent.availableOfflineStatus == AVAILABLE_OFFLINE) { -// avOffAncestor = parent -// } else if (parent.fileName != ROOT_PATH) { -// avOffAncestor = getAvailableOfflineAncestorOf(parent) -// } -// } -// return avOffAncestor - } - // FIXME: 13/10/2020 : New_arch: Migration private fun createFileInstance(c: Cursor?): OCFile? = null//c?.let { // OCFile(it.getStringFromColumnOrThrow(FILE_PATH)).apply { @@ -967,13 +755,6 @@ class FileDataStorageManager : KoinComponent { ) } - // FIXME: 13/10/2020 : New_arch: Av.Offline - private fun selectionForAllDescendantsOf(file: OCFile): Pair> { - val selection = "$FILE_ACCOUNT_OWNER=? AND $FILE_PATH LIKE ? " - val selectionArgs = arrayOf(account.name, "${file.remotePath}_%") // one or more characters after remote path - return Pair(selection, selectionArgs) - } - private fun performQuery( uri: Uri, projection: Array?, diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RepositoryModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RepositoryModule.kt index 17cd746feb1..33f941ea5f2 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RepositoryModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/RepositoryModule.kt @@ -55,4 +55,5 @@ val repositoryModule = module { factory { OAuthRepositoryImpl(get()) } factory { FolderBackupRepositoryImpl(get()) } factory { OCTransferRepository(get()) } + } diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt index e95bc62bda3..b2b1a442424 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/UseCaseModule.kt @@ -29,6 +29,10 @@ import com.owncloud.android.domain.authentication.usecases.GetBaseUrlUseCase import com.owncloud.android.domain.authentication.usecases.LoginBasicAsyncUseCase import com.owncloud.android.domain.authentication.usecases.LoginOAuthAsyncUseCase import com.owncloud.android.domain.authentication.usecases.SupportsOAuth2UseCase +import com.owncloud.android.domain.availableoffline.usecases.GetFilesAvailableOfflineFromAccountUseCase +import com.owncloud.android.domain.availableoffline.usecases.GetFilesAvailableOfflineFromEveryAccountUseCase +import com.owncloud.android.domain.availableoffline.usecases.SetFilesAsAvailableOfflineUseCase +import com.owncloud.android.domain.availableoffline.usecases.UnsetFilesAsAvailableOfflineUseCase import com.owncloud.android.domain.camerauploads.usecases.GetCameraUploadsConfigurationUseCase import com.owncloud.android.domain.camerauploads.usecases.GetPictureUploadsConfigurationStreamUseCase import com.owncloud.android.domain.camerauploads.usecases.GetVideoUploadsConfigurationStreamUseCase @@ -43,7 +47,6 @@ import com.owncloud.android.domain.files.usecases.CopyFileUseCase import com.owncloud.android.domain.files.usecases.CreateFolderAsyncUseCase import com.owncloud.android.domain.files.usecases.GetFileByIdUseCase import com.owncloud.android.domain.files.usecases.GetFileByRemotePathUseCase -import com.owncloud.android.domain.files.usecases.GetFilesAvailableOfflineUseCase import com.owncloud.android.domain.files.usecases.GetFilesSharedByLinkUseCase import com.owncloud.android.domain.files.usecases.GetFolderContentAsLiveDataUseCase import com.owncloud.android.domain.files.usecases.GetFolderContentUseCase @@ -112,12 +115,17 @@ val useCaseModule = module { factory { RenameFileUseCase(get()) } factory { SaveFileOrFolderUseCase(get()) } factory { GetFilesSharedByLinkUseCase(get()) } - factory { GetFilesAvailableOfflineUseCase(get()) } factory { GetSearchFolderContentUseCase(get()) } factory { SynchronizeFileUseCase(get(), get(), get(), get()) } factory { SynchronizeFolderUseCase(get(), get()) } factory { SortFilesUseCase() } + // Av Offline + factory { GetFilesAvailableOfflineFromAccountUseCase(get()) } + factory { GetFilesAvailableOfflineFromEveryAccountUseCase(get()) } + factory { SetFilesAsAvailableOfflineUseCase(get()) } + factory { UnsetFilesAsAvailableOfflineUseCase(get()) } + // Sharing factory { CreatePrivateShareAsyncUseCase(get()) } factory { CreatePublicShareAsyncUseCase(get()) } diff --git a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt index 328dc7aca50..ee67335e06a 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/dependecyinjection/ViewModelModule.kt @@ -86,7 +86,7 @@ val viewModelModule = module { viewModel { PreviewImageViewModel(get(), get(), get()) } viewModel { FileDetailsViewModel(get(), get(), get(), get(), get()) } - viewModel { FileOperationsViewModel(get(), get(), get(), get(), get(), get(), get(), get()) } + viewModel { FileOperationsViewModel(get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) } viewModel { MainFileListViewModel(get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) } viewModel { TransfersViewModel(get(), get(), get()) } } diff --git a/owncloudApp/src/main/java/com/owncloud/android/files/FileMenuFilter.java b/owncloudApp/src/main/java/com/owncloud/android/files/FileMenuFilter.java index c5500d907b2..9e60015455e 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/files/FileMenuFilter.java +++ b/owncloudApp/src/main/java/com/owncloud/android/files/FileMenuFilter.java @@ -33,6 +33,7 @@ import androidx.work.WorkManager; import com.owncloud.android.R; import com.owncloud.android.domain.capabilities.model.OCCapability; +import com.owncloud.android.domain.availableoffline.model.AvailableOfflineStatus; import com.owncloud.android.domain.files.model.OCFile; import com.owncloud.android.extensions.WorkManagerExtKt; import com.owncloud.android.services.OperationsService.OperationsServiceBinder; @@ -360,32 +361,30 @@ private boolean anyFileDown() { return false; } - // FIXME: 13/10/2020 : New_arch: Av.Offline private boolean anyFavorite() { -// for (OCFile file : mFiles) { -// if (file.getAvailableOfflineStatus() == OCFile.AvailableOfflineStatus.AVAILABLE_OFFLINE) { -// return true; -// } -// } + for (OCFile file : mFiles) { + if (file.getAvailableOfflineStatus() == AvailableOfflineStatus.AVAILABLE_OFFLINE) { + return true; + } + } return false; } - // FIXME: 13/10/2020 : New_arch: Av.Offline private boolean anyUnfavorite() { -// for (OCFile file : mFiles) { -// if (file.getAvailableOfflineStatus() == OCFile.AvailableOfflineStatus.NOT_AVAILABLE_OFFLINE) { -// return true; -// } -// } + for (OCFile file : mFiles) { + if (file.getAvailableOfflineStatus() == AvailableOfflineStatus.NOT_AVAILABLE_OFFLINE) { + return true; + } + } return false; - } - // FIXME: 13/10/2020 : New_arch: Shared by Link + } + private boolean anyFileSharedWithMe() { -// for (OCFile file : mFiles) { -// if (file.isSharedWithMe()) { -// return true; -// } -// } + for (OCFile file : mFiles) { + if (file.isSharedWithMe()) { + return true; + } + } return false; } } diff --git a/owncloudApp/src/main/java/com/owncloud/android/files/services/AvailableOfflineHandler.java b/owncloudApp/src/main/java/com/owncloud/android/files/services/AvailableOfflineHandler.java deleted file mode 100644 index 331877eb874..00000000000 --- a/owncloudApp/src/main/java/com/owncloud/android/files/services/AvailableOfflineHandler.java +++ /dev/null @@ -1,74 +0,0 @@ -/** - * ownCloud Android client application - * - * @author David González Verdugo - * Copyright (C) 2020 ownCloud GmbH. - *

- * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2, - * as published by the Free Software Foundation. - *

- * 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 General Public License for more details. - *

- * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.owncloud.android.files.services; - -import android.app.job.JobInfo; -import android.app.job.JobScheduler; -import android.content.ComponentName; -import android.content.Context; -import android.os.PersistableBundle; - -import com.owncloud.android.utils.Extras; -import timber.log.Timber; - -/** - * Schedule the periodic job responsible for synchronizing available offline files, a.k.a. kept-in-sync files, that - * have been updated locally, with the remote server - */ -public class AvailableOfflineHandler { - - private static final long MILLISECONDS_INTERVAL_AVAILABLE_OFFLINE = 900000; - - // It needs to be always the same so that the previous job is removed and replaced with a new one with the recent - // configuration - private static final int JOB_ID_AVAILABLE_OFFLINE = 2; - - private JobScheduler mJobScheduler; - - public AvailableOfflineHandler(Context context) { - mJobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); - } - - /** - * Schedule a periodic job to check whether recently updated available offline files need to be synchronized - */ - public void scheduleAvailableOfflineJob(Context context) { - ComponentName serviceComponent = new ComponentName(context, AvailableOfflineSyncJobService.class); - JobInfo.Builder builder; - - builder = new JobInfo.Builder(JOB_ID_AVAILABLE_OFFLINE, serviceComponent); - - builder.setPersisted(true); - - // Execute job every 15 minutes - builder.setPeriodic(MILLISECONDS_INTERVAL_AVAILABLE_OFFLINE); - - // Extra data - PersistableBundle extras = new PersistableBundle(); - - extras.putInt(Extras.EXTRA_AVAILABLE_OFFLINE_SYNC_JOB_ID, JOB_ID_AVAILABLE_OFFLINE); - - builder.setExtras(extras); - - Timber.d("Scheduling an AvailableOfflineSyncJobService"); - - mJobScheduler.schedule(builder.build()); - } -} diff --git a/owncloudApp/src/main/java/com/owncloud/android/files/services/AvailableOfflineSyncJobService.java b/owncloudApp/src/main/java/com/owncloud/android/files/services/AvailableOfflineSyncJobService.java deleted file mode 100644 index ca349aa5ffb..00000000000 --- a/owncloudApp/src/main/java/com/owncloud/android/files/services/AvailableOfflineSyncJobService.java +++ /dev/null @@ -1,186 +0,0 @@ -/** - * ownCloud Android client application - * - * @author David González Verdugo - * Copyright (C) 2020 ownCloud GmbH. - *

- * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2, - * as published by the Free Software Foundation. - *

- * 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 General Public License for more details. - *

- * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.owncloud.android.files.services; - -import android.app.job.JobParameters; -import android.app.job.JobScheduler; -import android.app.job.JobService; -import android.content.Context; -import android.os.AsyncTask; - -import androidx.core.util.Pair; -import com.owncloud.android.domain.files.model.OCFile; -import timber.log.Timber; - -import java.util.List; - -/** - * Job to watch for local changes in available offline files (formerly known as kept-in-sync files) and try to - * synchronize them with the OC server. - * This job should be executed every 15 minutes since a file is set as available offline for the first time and stopped - * when there's no available offline files - */ -public class AvailableOfflineSyncJobService extends JobService { - - @Override - public boolean onStartJob(JobParameters jobParameters) { - Timber.d("Starting job to sync available offline files"); - - new AvailableOfflineJobTask(this).execute(jobParameters); - - return true; // True because we have a thread still running in background - } - - private static class AvailableOfflineJobTask extends AsyncTask { - - private final JobService mAvailableOfflineJobService; - - public AvailableOfflineJobTask(JobService mAvailableOfflineJobService) { - this.mAvailableOfflineJobService = mAvailableOfflineJobService; - } - - @Override - protected JobParameters doInBackground(JobParameters... jobParams) { -// FIXME: 13/10/2020 : New_arch: Av.Offline -// Account account; -// -// if (mAvailableOfflineJobService != null) { -// account = AccountUtils.getCurrentOwnCloudAccount(mAvailableOfflineJobService.getApplicationContext()); -// } else { -// Timber.w("AvailableOfflineJobService is null"); -// return jobParams[0]; -// } -// -// if (account == null) { -// Timber.w("Account is null, do not sync av. offline files."); -// return jobParams[0]; -// } -// -// if (mAvailableOfflineJobService.getContentResolver() == null) { -// Timber.w("Content resolver is null, do not sync av. offline files."); -// return jobParams[0]; -// } -// -// FileDataStorageManager fileDataStorageManager = new FileDataStorageManager( -// mAvailableOfflineJobService, account, mAvailableOfflineJobService.getContentResolver() -// ); -// -// List> availableOfflineFilesFromEveryAccount = fileDataStorageManager. -// getAvailableOfflineFilesFromEveryAccount(); -// -// // Cancel periodic job if there's no available offline files to watch for local changes -// if (availableOfflineFilesFromEveryAccount.isEmpty()) { -// Timber.w("No available files for any account."); -// cancelPeriodicJob(jobParams[0].getJobId()); -// return jobParams[0]; -// } else { -// syncAvailableOfflineFiles(availableOfflineFilesFromEveryAccount); -// } - - return jobParams[0]; - } - - private void syncAvailableOfflineFiles(List> availableOfflineFilesForAccount) { - // FIXME: 13/10/2020 : New_arch: Av.Offline -// for (Pair fileForAccount : availableOfflineFilesForAccount) { -// -// String localPath = fileForAccount.first.getStoragePath(); -// -// if (localPath == null) { -// localPath = FileStorageUtils.getDefaultSavePathFor( -// fileForAccount.second, // Account name -// fileForAccount.first // OCFile -// ); -// } -// -// File localFile = new File(localPath); -// -// if (localFile.lastModified() <= fileForAccount.first.getLastSyncDateForData() && -// MainApp.Companion.getEnabledLogging()) { -// Timber.i("File " + fileForAccount.first.getRemotePath() + " already synchronized " + -// "in account " + fileForAccount.second + ", ignoring"); -// continue; -// } -// -// startSyncOperation(fileForAccount.first, fileForAccount.second); -// } - } - - /** - * Triggers an operation to synchronize the contents of a recently modified available offline file with - * its remote counterpart in the associated ownCloud account. - * - * @param availableOfflineFile file to synchronize - * @param accountName account to synchronize the available offline file with - */ - private void startSyncOperation(OCFile availableOfflineFile, String accountName) { - // FIXME: 13/10/2020 : New_arch: Av.Offline - Timber.i("Requested synchronization for file %1s in account %2s", - availableOfflineFile.getRemotePath(), accountName); -// -// Account account = AccountUtils.getOwnCloudAccountByName(mAvailableOfflineJobService, accountName); -// if (account == null) { -// Timber.w("Account '" + accountName + "' not found in account manager. Aborting Sync operation..."); -// return; -// } -// -// FileDataStorageManager storageManager = -// new FileDataStorageManager(mAvailableOfflineJobService, account, mAvailableOfflineJobService.getContentResolver()); -// -// SynchronizeFileOperation synchronizeFileOperation = -// new SynchronizeFileOperation(availableOfflineFile, null, account, false, -// mAvailableOfflineJobService, true); -// -// RemoteOperationResult result = synchronizeFileOperation.execute(storageManager, mAvailableOfflineJobService); -// -// if (result.getCode() == RemoteOperationResult.ResultCode.SYNC_CONFLICT) { -// notifyConflict(availableOfflineFile, account, mAvailableOfflineJobService); -// } - } - - /** - * Cancel the periodic job - * - * @param jobId id of the job to cancel - */ - private void cancelPeriodicJob(int jobId) { - JobScheduler jobScheduler = (JobScheduler) mAvailableOfflineJobService.getSystemService( - Context.JOB_SCHEDULER_SERVICE); - - jobScheduler.cancel(jobId); - - Timber.d("No available offline files to check, cancelling the periodic job"); - } - - @Override - protected void onPostExecute(JobParameters jobParameters) { - mAvailableOfflineJobService.jobFinished(jobParameters, false); - } - } - - @Override - /* - * Called by the system if the job is cancelled before being finished - */ - public boolean onStopJob(JobParameters jobParameters) { - Timber.d("Job was cancelled before finishing."); - return true; - } -} diff --git a/owncloudApp/src/main/java/com/owncloud/android/operations/SynchronizeFolderOperation.java b/owncloudApp/src/main/java/com/owncloud/android/operations/SynchronizeFolderOperation.java index fb7d569c078..c06cbf123a3 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/operations/SynchronizeFolderOperation.java +++ b/owncloudApp/src/main/java/com/owncloud/android/operations/SynchronizeFolderOperation.java @@ -33,6 +33,7 @@ import com.owncloud.android.datamodel.OCUpload; import com.owncloud.android.datamodel.UploadsStorageManager; import com.owncloud.android.domain.UseCaseResult; +import com.owncloud.android.domain.availableoffline.model.AvailableOfflineStatus; import com.owncloud.android.domain.files.model.OCFile; import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.operations.OperationCancelledException; @@ -59,6 +60,7 @@ import java.util.Vector; import java.util.concurrent.atomic.AtomicBoolean; +import static com.owncloud.android.usecases.synchronization.SynchronizeFolderUseCase.SyncFolderMode.REFRESH_FOLDER; import static com.owncloud.android.usecases.synchronization.SynchronizeFolderUseCase.SyncFolderMode.SYNC_FOLDER_RECURSIVELY; import static org.koin.java.KoinJavaComponent.get; import static org.koin.java.KoinJavaComponent.inject; @@ -207,7 +209,7 @@ protected RemoteOperationResult> run(OwnCloudClient client final RemoteOperationResult> fetchFolderResult; SynchronizeFolderUseCase synchronizeFolderUseCase = get(SynchronizeFolderUseCase.class); - synchronizeFolderUseCase.execute(new SynchronizeFolderUseCase.Params(mRemotePath, mAccount.name, SYNC_FOLDER_RECURSIVELY)); + synchronizeFolderUseCase.execute(new SynchronizeFolderUseCase.Params(mRemotePath, mAccount.name, REFRESH_FOLDER)); mFailsInFileSyncsFound = 0; mConflictsFound = 0; @@ -364,12 +366,11 @@ private void mergeRemoteFolder(ArrayList remoteFolderAndFiles) // remote eTag will not be set unless file CONTENTS are synchronized updatedLocalFile.setEtag(""); // new files need to check av-off status of parent folder! - // FIXME: 19/10/2020 : New_arch: Av.Offline - // if (updatedFolder.isAvailableOffline()) { - // updatedLocalFile.setAvailableOfflineStatus( - // OCFile.AvailableOfflineStatus.AVAILABLE_OFFLINE_PARENT - // ); - // } + if (updatedFolder.isAvailableOffline()) { + updatedLocalFile.setAvailableOfflineStatus( + AvailableOfflineStatus.AVAILABLE_OFFLINE_PARENT + ); + } // new files need to update thumbnails updatedLocalFile.setNeedsToUpdateThumbnail(true); } @@ -417,8 +418,7 @@ private void preparePushOfLocalChanges() { */ private boolean addToSyncContents(OCFile localFile, OCFile remoteFile) { - // FIXME: 13/10/2020 : New_arch: Av.Offline - boolean shouldSyncContents = (mSyncContentOfRegularFiles || localFile.isAvailableLocally()); //|| localFile.isAvailableOffline()); + boolean shouldSyncContents = (mSyncContentOfRegularFiles || localFile.isAvailableLocally() || localFile.isAvailableOffline()); boolean serverUnchanged; if (localFile.isFolder()) { diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/adapters/filelist/FileListAdapter.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/adapters/filelist/FileListAdapter.kt index 2fecc2822b4..352a838cc23 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/adapters/filelist/FileListAdapter.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/adapters/filelist/FileListAdapter.kt @@ -321,6 +321,9 @@ class FileListAdapter( // conflict localStateView.setImageResource(R.drawable.error_pin) localStateView.visibility = View.VISIBLE + } else if (file.isAvailableOffline) { + localStateView.visibility = View.VISIBLE + localStateView.setImageResource(R.drawable.offline_available_pin) } else if (file.isAvailableLocally) { localStateView.visibility = View.VISIBLE localStateView.setImageResource(R.drawable.downloaded_pin) diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListFragment.kt index a6b57100fdc..0a9abd06cc3 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/filelist/MainFileListFragment.kt @@ -219,7 +219,7 @@ class MainFileListFragment : Fragment(), } .filter { when (fileListOption) { - FileListOption.AV_OFFLINE -> it.keepInSync == 1 + FileListOption.AV_OFFLINE -> it.isAvailableOffline FileListOption.SHARED_BY_LINK -> it.sharedByLink || it.sharedWithSharee == true else -> true } @@ -512,6 +512,12 @@ class MainFileListFragment : Fragment(), } return true } + R.id.action_set_available_offline -> { + fileOperationsViewModel.performOperation(FileOperation.SetFilesAsAvailableOffline(listOf(singleFile))) + } + R.id.action_unset_available_offline -> { + fileOperationsViewModel.performOperation(FileOperation.UnsetFilesAsAvailableOffline(listOf(singleFile))) + } } } @@ -544,19 +550,11 @@ class MainFileListFragment : Fragment(), return true } R.id.action_set_available_offline -> { - // TODO Waiting to be implemented - //containerActivity?.fileOperationsHelper?.toggleAvailableOffline(checkedFiles, true) - //getListView().invalidateViews() + fileOperationsViewModel.performOperation(FileOperation.SetFilesAsAvailableOffline(checkedFiles)) return true } R.id.action_unset_available_offline -> { - // TODO Waiting to be implemented - //containerActivity?.fileOperationsHelper?.toggleAvailableOffline(checkedFiles, false) - //getListView().invalidateViews() - //invalidateActionMode() - /*if (fileListOption?.isAvailableOffline() == true) { - onRefresh() - }*/ + fileOperationsViewModel.performOperation(FileOperation.UnsetFilesAsAvailableOffline(checkedFiles)) return true } R.id.action_move -> { diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/operations/FileOperation.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/operations/FileOperation.kt index 59706fba965..b1e157a571b 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/operations/FileOperation.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/operations/FileOperation.kt @@ -28,4 +28,6 @@ sealed class FileOperation { data class RemoveOperation(val listOfFilesToRemove: List, val removeOnlyLocalCopy: Boolean) : FileOperation() data class RenameOperation(val ocFileToRename: OCFile, val newName: String) : FileOperation() data class SynchronizeFileOperation(val fileToSync: OCFile, val accountName: String) : FileOperation() + data class SetFilesAsAvailableOffline(val filesToUpdate: List) : FileOperation() + data class UnsetFilesAsAvailableOffline(val filesToUpdate: List) : FileOperation() } diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/operations/FileOperationsViewModel.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/operations/FileOperationsViewModel.kt index b3aad7a1cda..c9efcb62ac3 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/operations/FileOperationsViewModel.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/operations/FileOperationsViewModel.kt @@ -25,6 +25,8 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.owncloud.android.domain.BaseUseCaseWithResult import com.owncloud.android.domain.UseCaseResult +import com.owncloud.android.domain.availableoffline.usecases.SetFilesAsAvailableOfflineUseCase +import com.owncloud.android.domain.availableoffline.usecases.UnsetFilesAsAvailableOfflineUseCase import com.owncloud.android.domain.exceptions.NoNetworkConnectionException import com.owncloud.android.domain.files.model.OCFile import com.owncloud.android.domain.files.usecases.CopyFileUseCase @@ -48,6 +50,8 @@ class FileOperationsViewModel( private val removeFileUseCase: RemoveFileUseCase, private val renameFileUseCase: RenameFileUseCase, private val synchronizeFileUseCase: SynchronizeFileUseCase, + private val setFilesAsAvailableOfflineUseCase: SetFilesAsAvailableOfflineUseCase, + private val unsetFilesAsAvailableOfflineUseCase: UnsetFilesAsAvailableOfflineUseCase, private val contextProvider: ContextProvider, private val coroutinesDispatcherProvider: CoroutinesDispatcherProvider ) : ViewModel() { @@ -78,6 +82,8 @@ class FileOperationsViewModel( is FileOperation.CopyOperation -> copyOperation(fileOperation) is FileOperation.SynchronizeFileOperation -> syncFileOperation(fileOperation) is FileOperation.CreateFolder -> createFolderOperation(fileOperation) + is FileOperation.SetFilesAsAvailableOffline -> setFileAsAvailableOffline(fileOperation) + is FileOperation.UnsetFilesAsAvailableOffline -> unsetFileAsAvailableOffline(fileOperation) } } @@ -136,6 +142,18 @@ class FileOperationsViewModel( ) } + private fun setFileAsAvailableOffline(fileOperation: FileOperation.SetFilesAsAvailableOffline) { + viewModelScope.launch(coroutinesDispatcherProvider.io) { + setFilesAsAvailableOfflineUseCase.execute(SetFilesAsAvailableOfflineUseCase.Params(fileOperation.filesToUpdate)) + } + } + + private fun unsetFileAsAvailableOffline(fileOperation: FileOperation.UnsetFilesAsAvailableOffline) { + viewModelScope.launch(coroutinesDispatcherProvider.io) { + unsetFilesAsAvailableOfflineUseCase.execute(UnsetFilesAsAvailableOfflineUseCase.Params(fileOperation.filesToUpdate)) + } + } + private fun runOperation( liveData: MediatorLiveData>>, useCase: BaseUseCaseWithResult, diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/removefile/RemoveFilesDialogFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/removefile/RemoveFilesDialogFragment.kt index f67943563e3..7438d08306d 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/removefile/RemoveFilesDialogFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/files/removefile/RemoveFilesDialogFragment.kt @@ -29,7 +29,6 @@ import com.owncloud.android.presentation.ui.files.operations.FileOperationsViewM import com.owncloud.android.ui.dialog.ConfirmationDialogFragment import com.owncloud.android.ui.dialog.ConfirmationDialogFragment.ConfirmationDialogFragmentListener import org.koin.androidx.viewmodel.ext.android.sharedViewModel -import java.util.ArrayList /** * Dialog requiring confirmation before removing a collection of given OCFiles. @@ -82,7 +81,7 @@ class RemoveFilesDialogFragment : ConfirmationDialogFragment(), ConfirmationDial val messageStringId: Int var containsFolder = false var containsDown = false - val containsAvailableOffline = false + var containsAvailableOffline = false for (file in files) { if (file.isFolder) { containsFolder = true @@ -90,10 +89,9 @@ class RemoveFilesDialogFragment : ConfirmationDialogFragment(), ConfirmationDial if (file.isAvailableLocally) { containsDown = true } - // FIXME: 13/10/2020 : New_arch: Av.Offline - // if (file.getAvailableOfflineStatus() != OCFile.AvailableOfflineStatus.NOT_AVAILABLE_OFFLINE) { - // containsAvailableOffline = true; - // } + if (file.isAvailableOffline) { + containsAvailableOffline = true + } } messageStringId = if (files.size == 1) { // choose message for a single file diff --git a/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/settings/SettingsActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/settings/SettingsActivity.kt index a2826d265b2..77311c6e50c 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/settings/SettingsActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/presentation/ui/settings/SettingsActivity.kt @@ -36,6 +36,7 @@ import com.owncloud.android.presentation.ui.settings.fragments.SettingsMoreFragm import com.owncloud.android.presentation.ui.settings.fragments.SettingsPictureUploadsFragment import com.owncloud.android.presentation.ui.settings.fragments.SettingsSecurityFragment import com.owncloud.android.presentation.ui.settings.fragments.SettingsVideoUploadsFragment +import com.owncloud.android.providers.WorkManagerProvider import com.owncloud.android.ui.activity.FileDisplayActivity class SettingsActivity : AppCompatActivity() { @@ -61,6 +62,12 @@ class SettingsActivity : AppCompatActivity() { redirectToSubsection(intent) } + override fun onDestroy() { + val workerProvider = WorkManagerProvider(context = this) + workerProvider.enqueueAvailableOfflinePeriodicWorker() + super.onDestroy() + } + private fun updateToolbarTitle() { val titleId = when (supportFragmentManager.fragments.lastOrNull()) { is SettingsSecurityFragment -> R.string.prefs_subsection_security diff --git a/owncloudApp/src/main/java/com/owncloud/android/providers/FileContentProvider.kt b/owncloudApp/src/main/java/com/owncloud/android/providers/FileContentProvider.kt index 0c85feeefa4..9ab33857826 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/providers/FileContentProvider.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/providers/FileContentProvider.kt @@ -1064,10 +1064,8 @@ class FileContentProvider(val executors: Executors = Executors()) : ContentProvi } } - // TODO: Remove old database once it is not needed anymore. - // FIXME: 29/10/2020 : New_arch: Av.Offline // Drop old files table from old database - //db.execSQL("DROP TABLE IF EXISTS " + ProviderTableMeta.FILE_TABLE_NAME + ";") + db.execSQL("DROP TABLE IF EXISTS " + ProviderTableMeta.FILE_TABLE_NAME + ";") } } if (!upgraded) { diff --git a/owncloudApp/src/main/java/com/owncloud/android/providers/WorkManagerProvider.kt b/owncloudApp/src/main/java/com/owncloud/android/providers/WorkManagerProvider.kt index b3fc3d90591..70ba84bce55 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/providers/WorkManagerProvider.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/providers/WorkManagerProvider.kt @@ -25,6 +25,8 @@ import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.NetworkType import androidx.work.PeriodicWorkRequestBuilder import androidx.work.WorkManager +import com.owncloud.android.workers.AvailableOfflinePeriodicWorker +import com.owncloud.android.workers.AvailableOfflinePeriodicWorker.Companion.AVAILABLE_OFFLINE_PERIODIC_WORKER import com.owncloud.android.workers.CameraUploadsWorker import com.owncloud.android.workers.OldLogsCollectorWorker @@ -56,4 +58,22 @@ class WorkManagerProvider( WorkManager.getInstance(context) .enqueueUniquePeriodicWork(OldLogsCollectorWorker.OLD_LOGS_COLLECTOR_WORKER, ExistingPeriodicWorkPolicy.REPLACE, oldLogsCollectorWorker) } + + fun enqueueAvailableOfflinePeriodicWorker() { + val constraintsRequired = Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .setRequiresBatteryNotLow(true) + .build() + + val availableOfflinePeriodicWorker = PeriodicWorkRequestBuilder( + repeatInterval = AvailableOfflinePeriodicWorker.repeatInterval, + repeatIntervalTimeUnit = AvailableOfflinePeriodicWorker.repeatIntervalTimeUnit + ) + .addTag(AVAILABLE_OFFLINE_PERIODIC_WORKER) + .setConstraints(constraintsRequired) + .build() + + WorkManager.getInstance(context) + .enqueueUniquePeriodicWork(AVAILABLE_OFFLINE_PERIODIC_WORKER, ExistingPeriodicWorkPolicy.KEEP, availableOfflinePeriodicWorker) + } } diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index 510362e20a9..0a87c5802ac 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -1570,10 +1570,8 @@ class FileDisplayActivity : FileActivity(), fileOperationsViewModel.performOperation(FileOperation.SynchronizeFileOperation(file, account.name)) } PreviewVideoFragment.canBePreviewed(file) && !WorkManager.getInstance(this).isDownloadPending(account, file) -> { - // FIXME: 13/10/2020 : New_arch: Av.Offline - // Available offline exception, don't initialize streaming - // if (!file.isAvailableLocally() && file.isAvailableOffline()) { - if (file.isAvailableLocally) { + // Available offline but not downloaded yet, don't initialize streaming + if (!file.isAvailableLocally && file.isAvailableOffline) { // sync file content, then open with external apps startSyncThenOpen(file) } else { diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.kt index eab67416188..bec38d409af 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.kt @@ -46,6 +46,8 @@ import com.owncloud.android.domain.files.model.OCFile import com.owncloud.android.extensions.observeWorkerTillItFinishes import com.owncloud.android.extensions.showMessageInSnackbar import com.owncloud.android.files.FileMenuFilter +import com.owncloud.android.presentation.ui.files.operations.FileOperation +import com.owncloud.android.presentation.ui.files.operations.FileOperationsViewModel import com.owncloud.android.presentation.ui.files.removefile.RemoveFilesDialogFragment import com.owncloud.android.presentation.viewmodels.files.FileDetailsViewModel import com.owncloud.android.ui.activity.ComponentsGetter @@ -71,6 +73,7 @@ class FileDetailFragment : FileFragment(), View.OnClickListener { private var progressController: TransferProgressController? = null private val fileDetailsViewModel: FileDetailsViewModel by inject() + private val fileOperationsViewModel: FileOperationsViewModel by inject() override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { setHasOptionsMenu(true) @@ -270,11 +273,11 @@ class FileDetailFragment : FileFragment(), View.OnClickListener { true } R.id.action_set_available_offline -> { - mContainerActivity.fileOperationsHelper.toggleAvailableOffline(file, true) + fileOperationsViewModel.performOperation(FileOperation.SetFilesAsAvailableOffline(listOf(file))) true } R.id.action_unset_available_offline -> { - mContainerActivity.fileOperationsHelper.toggleAvailableOffline(file, false) + fileOperationsViewModel.performOperation(FileOperation.UnsetFilesAsAvailableOffline(listOf(file))) true } else -> super.onOptionsItemSelected(item) diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java b/owncloudApp/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java index fbc5c577363..be033da2d35 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java @@ -284,49 +284,6 @@ public void syncFile(OCFile file) { } } - public void toggleAvailableOffline(Collection files, boolean isAvailableOffline) { - for (OCFile file : files) { - toggleAvailableOffline(file, isAvailableOffline); - } - } - - public void toggleAvailableOffline(OCFile file, boolean isAvailableOffline) { - // FIXME: 13/10/2020 : New_arch: Av.Offline - - // if (OCFile.AvailableOfflineStatus.AVAILABLE_OFFLINE_PARENT == file.getAvailableOfflineStatus()) { - // /// files descending of an av-offline folder can't be toggled - // mFileActivity.showSnackMessage( - // mFileActivity.getString(R.string.available_offline_inherited_msg) - // ); - // - // } else { - // /// update local property, for file and all its descendents (if folder) - // OCFile.AvailableOfflineStatus targetAvailableOfflineStatus = isAvailableOffline ? - // OCFile.AvailableOfflineStatus.AVAILABLE_OFFLINE : - // OCFile.AvailableOfflineStatus.NOT_AVAILABLE_OFFLINE; - // file.setAvailableOfflineStatus(targetAvailableOfflineStatus); - // boolean success = mFileActivity.getStorageManager().saveLocalAvailableOfflineStatus(file); - // - // if (success) { - // // Schedule job to check to watch for local changes in available offline files and sync them - // AvailableOfflineHandler availableOfflineHandler = new AvailableOfflineHandler(mFileActivity); - // availableOfflineHandler.scheduleAvailableOfflineJob(mFileActivity); - // - // /// immediate content synchronization - // if (OCFile.AvailableOfflineStatus.AVAILABLE_OFFLINE == file.getAvailableOfflineStatus()) { - // syncFile(file); - // } else { - // cancelTransference(file); - // } - // } else { - // /// unexpected error - // mFileActivity.showSnackMessage( - // mFileActivity.getString(R.string.common_error_unknown) - // ); - // } - // } - } - /** * Cancel the transference in downloads (files/folders) and file uploads * diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/preview/PreviewAudioFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/preview/PreviewAudioFragment.kt index 8cba9e36a97..3c0f478d514 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/preview/PreviewAudioFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/preview/PreviewAudioFragment.kt @@ -48,11 +48,14 @@ import com.owncloud.android.files.FileMenuFilter import com.owncloud.android.media.MediaControlView import com.owncloud.android.media.MediaService import com.owncloud.android.media.MediaServiceBinder +import com.owncloud.android.presentation.ui.files.operations.FileOperation +import com.owncloud.android.presentation.ui.files.operations.FileOperationsViewModel +import com.owncloud.android.presentation.ui.files.removefile.RemoveFilesDialogFragment import com.owncloud.android.ui.controller.TransferProgressController import com.owncloud.android.ui.dialog.ConfirmationDialogFragment -import com.owncloud.android.presentation.ui.files.removefile.RemoveFilesDialogFragment import com.owncloud.android.ui.fragment.FileFragment import com.owncloud.android.utils.PreferenceUtils +import org.koin.android.ext.android.inject import timber.log.Timber /** @@ -83,6 +86,8 @@ class PreviewAudioFragment : FileFragment() { private var progressBar: ProgressBar? = null var progressController: TransferProgressController? = null + private val fileOperationsViewModel: FileOperationsViewModel by inject() + /** * {@inheritDoc} */ @@ -302,11 +307,11 @@ class PreviewAudioFragment : FileFragment() { true } R.id.action_set_available_offline -> { - mContainerActivity.fileOperationsHelper.toggleAvailableOffline(file, true) + fileOperationsViewModel.performOperation(FileOperation.SetFilesAsAvailableOffline(listOf(file))) true } R.id.action_unset_available_offline -> { - mContainerActivity.fileOperationsHelper.toggleAvailableOffline(file, false) + fileOperationsViewModel.performOperation(FileOperation.UnsetFilesAsAvailableOffline(listOf(file))) true } else -> super.onOptionsItemSelected(item) diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.kt index ae8a17b5c7b..96604d0ba36 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.kt @@ -51,11 +51,15 @@ import com.owncloud.android.databinding.TopProgressBarBinding import com.owncloud.android.domain.files.model.MIME_SVG import com.owncloud.android.domain.files.model.OCFile import com.owncloud.android.files.FileMenuFilter +import com.owncloud.android.presentation.ui.files.operations.FileOperation +import com.owncloud.android.presentation.ui.files.operations.FileOperationsViewModel import com.owncloud.android.ui.controller.TransferProgressController import com.owncloud.android.ui.dialog.ConfirmationDialogFragment import com.owncloud.android.presentation.ui.files.removefile.RemoveFilesDialogFragment import com.owncloud.android.ui.fragment.FileFragment import com.owncloud.android.utils.PreferenceUtils +import org.koin.android.ext.android.inject +import org.koin.java.KoinJavaComponent.inject import timber.log.Timber import java.io.File @@ -86,6 +90,8 @@ class PreviewImageFragment : FileFragment() { private var _bindingTopProgress: TopProgressBarBinding? = null private val bindingTopProgress get() = _bindingTopProgress!! + private val fileOperationsViewModel: FileOperationsViewModel by inject() + /** * {@inheritDoc} */ @@ -254,11 +260,11 @@ class PreviewImageFragment : FileFragment() { true } R.id.action_set_available_offline -> { - mContainerActivity.fileOperationsHelper.toggleAvailableOffline(file, true) + fileOperationsViewModel.performOperation(FileOperation.SetFilesAsAvailableOffline(listOf(file))) true } R.id.action_unset_available_offline -> { - mContainerActivity.fileOperationsHelper.toggleAvailableOffline(file, false) + fileOperationsViewModel.performOperation(FileOperation.UnsetFilesAsAvailableOffline(listOf(file))) true } else -> super.onOptionsItemSelected(item) diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/preview/PreviewTextFragment.java b/owncloudApp/src/main/java/com/owncloud/android/ui/preview/PreviewTextFragment.java index fa78e85bf31..3c60f89a556 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/preview/PreviewTextFragment.java +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/preview/PreviewTextFragment.java @@ -39,10 +39,12 @@ import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.domain.files.model.OCFile; import com.owncloud.android.files.FileMenuFilter; +import com.owncloud.android.presentation.ui.files.operations.FileOperation; +import com.owncloud.android.presentation.ui.files.operations.FileOperationsViewModel; +import com.owncloud.android.presentation.ui.files.removefile.RemoveFilesDialogFragment; import com.owncloud.android.ui.controller.TransferProgressController; import com.owncloud.android.ui.dialog.ConfirmationDialogFragment; import com.owncloud.android.ui.dialog.LoadingDialog; -import com.owncloud.android.presentation.ui.files.removefile.RemoveFilesDialogFragment; import com.owncloud.android.ui.fragment.FileFragment; import com.owncloud.android.utils.PreferenceUtils; import timber.log.Timber; @@ -52,10 +54,13 @@ import java.io.IOException; import java.io.StringWriter; import java.lang.ref.WeakReference; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Scanner; +import static org.koin.java.KoinJavaComponent.get; + public class PreviewTextFragment extends FileFragment { private static final String EXTRA_FILE = "FILE"; private static final String EXTRA_ACCOUNT = "ACCOUNT"; @@ -66,6 +71,8 @@ public class PreviewTextFragment extends FileFragment { private TextView mTextPreview; private TextLoadAsyncTask mTextLoadTask; + FileOperationsViewModel fileOperationsViewModel = get(FileOperationsViewModel.class); + /** * Public factory method to create new PreviewTextFragment instances. * @@ -371,15 +378,19 @@ public boolean onOptionsItemSelected(MenuItem item) { return true; } case R.id.action_sync_file: { - mContainerActivity.getFileOperationsHelper().syncFile(getFile()); + fileOperationsViewModel.performOperation(new FileOperation.SynchronizeFileOperation(getFile(), mAccount.name)); return true; } case R.id.action_set_available_offline: { - mContainerActivity.getFileOperationsHelper().toggleAvailableOffline(getFile(), true); + ArrayList fileToSetAsAvailableOffline = new ArrayList<>(); + fileToSetAsAvailableOffline.add(getFile()); + fileOperationsViewModel.performOperation(new FileOperation.SetFilesAsAvailableOffline(fileToSetAsAvailableOffline)); return true; } case R.id.action_unset_available_offline: { - mContainerActivity.getFileOperationsHelper().toggleAvailableOffline(getFile(), false); + ArrayList fileToUnsetAsAvailableOffline = new ArrayList<>(); + fileToUnsetAsAvailableOffline.add(getFile()); + fileOperationsViewModel.performOperation(new FileOperation.UnsetFilesAsAvailableOffline(fileToUnsetAsAvailableOffline)); return true; } default: diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/preview/PreviewVideoFragment.java b/owncloudApp/src/main/java/com/owncloud/android/ui/preview/PreviewVideoFragment.java index def707352ad..46ae6943a33 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/preview/PreviewVideoFragment.java +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/preview/PreviewVideoFragment.java @@ -51,14 +51,20 @@ import com.owncloud.android.domain.files.model.MimeTypeConstantsKt; import com.owncloud.android.domain.files.model.OCFile; import com.owncloud.android.files.FileMenuFilter; +import com.owncloud.android.presentation.ui.files.operations.FileOperation; +import com.owncloud.android.presentation.ui.files.operations.FileOperationsViewModel; +import com.owncloud.android.presentation.ui.files.removefile.RemoveFilesDialogFragment; import com.owncloud.android.ui.activity.FileActivity; import com.owncloud.android.ui.activity.FileDisplayActivity; import com.owncloud.android.ui.controller.TransferProgressController; import com.owncloud.android.ui.dialog.ConfirmationDialogFragment; -import com.owncloud.android.presentation.ui.files.removefile.RemoveFilesDialogFragment; import com.owncloud.android.ui.fragment.FileFragment; import timber.log.Timber; +import java.util.ArrayList; + +import static org.koin.java.KoinJavaComponent.get; + /** * This fragment shows a preview of a downloaded video file, or starts streaming if file is not * downloaded yet. @@ -97,6 +103,8 @@ public class PreviewVideoFragment extends FileFragment implements View.OnClickLi private boolean mAutoplay; private long mPlaybackPosition; + FileOperationsViewModel fileOperationsViewModel = get(FileOperationsViewModel.class); + /** * Public factory method to create new PreviewVideoFragment instances. * @@ -347,11 +355,16 @@ public boolean onOptionsItemSelected(MenuItem item) { return true; } case R.id.action_set_available_offline: { - mContainerActivity.getFileOperationsHelper().toggleAvailableOffline(getFile(), true); + ArrayList fileToSetAsAvailableOffline = new ArrayList<>(); + fileToSetAsAvailableOffline.add(getFile()); + fileOperationsViewModel.performOperation(new FileOperation.SetFilesAsAvailableOffline(fileToSetAsAvailableOffline)); return true; } case R.id.action_unset_available_offline: { - mContainerActivity.getFileOperationsHelper().toggleAvailableOffline(getFile(), false); + + ArrayList fileToUnsetAsAvailableOffline = new ArrayList<>(); + fileToUnsetAsAvailableOffline.add(getFile()); + fileOperationsViewModel.performOperation(new FileOperation.UnsetFilesAsAvailableOffline(fileToUnsetAsAvailableOffline)); return true; } case R.id.action_download_file: { diff --git a/owncloudApp/src/main/java/com/owncloud/android/usecases/synchronization/SynchronizeFolderUseCase.kt b/owncloudApp/src/main/java/com/owncloud/android/usecases/synchronization/SynchronizeFolderUseCase.kt index 8e95da21520..fb810d25564 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/usecases/synchronization/SynchronizeFolderUseCase.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/usecases/synchronization/SynchronizeFolderUseCase.kt @@ -40,7 +40,7 @@ class SynchronizeFolderUseCase( folderContent.forEach { ocFile -> if (ocFile.isFolder) { - if (params.syncMode.isOneOf(REFRESH_FOLDER_RECURSIVELY, SYNC_FOLDER_RECURSIVELY)) { + if (shouldSyncFolder(params.syncMode, ocFile)) { SynchronizeFolderUseCase(synchronizeFileUseCase, fileRepository).execute( Params( remotePath = ocFile.remotePath, @@ -59,8 +59,11 @@ class SynchronizeFolderUseCase( } } + private fun shouldSyncFolder(syncMode: SyncFolderMode, ocFolder: OCFile) = + syncMode.isOneOf(REFRESH_FOLDER_RECURSIVELY, SYNC_FOLDER_RECURSIVELY) || syncMode == SYNC_CONTENTS && ocFolder.isAvailableOffline + private fun shouldSyncFile(syncMode: SyncFolderMode, ocFile: OCFile) = - syncMode == SYNC_FOLDER_RECURSIVELY || (syncMode == SYNC_CONTENTS && ocFile.isAvailableLocally) + syncMode == SYNC_FOLDER_RECURSIVELY || (syncMode == SYNC_CONTENTS && (ocFile.isAvailableLocally || ocFile.isAvailableOffline)) data class Params( val remotePath: String, diff --git a/owncloudApp/src/main/java/com/owncloud/android/workers/AvailableOfflinePeriodicWorker.kt b/owncloudApp/src/main/java/com/owncloud/android/workers/AvailableOfflinePeriodicWorker.kt new file mode 100644 index 00000000000..c000173cf2e --- /dev/null +++ b/owncloudApp/src/main/java/com/owncloud/android/workers/AvailableOfflinePeriodicWorker.kt @@ -0,0 +1,79 @@ +/** + * ownCloud Android client application + * + * @author Abel García de Prada + * Copyright (C) 2022 ownCloud GmbH. + *

+ * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + *

+ * 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 General Public License for more details. + *

+ * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.owncloud.android.workers + +import android.content.Context +import androidx.work.CoroutineWorker +import androidx.work.WorkerParameters +import com.owncloud.android.domain.availableoffline.usecases.GetFilesAvailableOfflineFromEveryAccountUseCase +import com.owncloud.android.domain.files.model.OCFile +import com.owncloud.android.usecases.synchronization.SynchronizeFileUseCase +import com.owncloud.android.usecases.synchronization.SynchronizeFolderUseCase +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import timber.log.Timber +import java.util.concurrent.TimeUnit + +class AvailableOfflinePeriodicWorker( + val appContext: Context, + workerParameters: WorkerParameters +) : CoroutineWorker( + appContext, + workerParameters +), KoinComponent { + + private val getFilesAvailableOfflineFromEveryAccountUseCase: GetFilesAvailableOfflineFromEveryAccountUseCase by inject() + private val synchronizeFileUseCase: SynchronizeFileUseCase by inject() + private val synchronizeFolderUseCase: SynchronizeFolderUseCase by inject() + + override suspend fun doWork(): Result { + + return try { + val availableOfflineFiles = getFilesAvailableOfflineFromEveryAccountUseCase.execute(Unit) + Timber.i("Available offline files that needs to be synced: ${availableOfflineFiles.size}") + + syncAvailableOfflineFiles(availableOfflineFiles) + + Result.success() + } catch (exception: Exception) { + Result.failure() + } + } + + private fun syncAvailableOfflineFiles(availableOfflineFiles: List) { + availableOfflineFiles.forEach { + if (it.isFolder) { + synchronizeFolderUseCase.execute( + SynchronizeFolderUseCase.Params( + remotePath = it.remotePath, + accountName = it.owner, + syncMode = SynchronizeFolderUseCase.SyncFolderMode.SYNC_FOLDER_RECURSIVELY + ) + ) + } else { + synchronizeFileUseCase.execute(SynchronizeFileUseCase.Params(it)) + } + } + } + companion object { + const val AVAILABLE_OFFLINE_PERIODIC_WORKER = "AVAILABLE_OFFLINE_PERIODIC_WORKER" + const val repeatInterval: Long = 15L + val repeatIntervalTimeUnit: TimeUnit = TimeUnit.MINUTES + } +} diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/LocalFileDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/LocalFileDataSource.kt index 91bab5b7115..71c9156cd1f 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/LocalFileDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/LocalFileDataSource.kt @@ -21,10 +21,11 @@ package com.owncloud.android.data.files.datasources import androidx.lifecycle.LiveData +import com.owncloud.android.domain.availableoffline.model.AvailableOfflineStatus import com.owncloud.android.domain.files.model.OCFile interface LocalFileDataSource { - fun copyFile(sourceFile: OCFile, targetFile: OCFile, finalRemotePath: String, remoteId: String) + fun copyFile(sourceFile: OCFile, targetFolder: OCFile, finalRemotePath: String, remoteId: String) fun getFileById(fileId: Long): OCFile? fun getFileByRemotePath(remotePath: String, owner: String): OCFile? fun getFileByRemoteId(remoteId: String): OCFile? @@ -35,10 +36,12 @@ interface LocalFileDataSource { fun getFolderContentAsLiveData(folderId: Long): LiveData> fun getFolderImages(folderId: Long): List fun getFilesSharedByLink(owner: String): List - fun getFilesAvailableOffline(owner: String): List - fun moveFile(sourceFile: OCFile, targetFile: OCFile, finalRemotePath: String, finalStoragePath: String) + fun getFilesAvailableOfflineFromAccount(owner: String): List + fun getFilesAvailableOfflineFromEveryAccount(): List + fun moveFile(sourceFile: OCFile, targetFolder: OCFile, finalRemotePath: String, finalStoragePath: String) fun saveFilesInFolder(listOfFiles: List, folder: OCFile) fun saveFile(file: OCFile) fun removeFile(fileId: Long) fun renameFile(fileToRename: OCFile, finalRemotePath: String, finalStoragePath: String) + fun updateAvailableOfflineStatusForFile(ocFile: OCFile, newAvailableOfflineStatus: AvailableOfflineStatus) } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCLocalFileDataSource.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCLocalFileDataSource.kt index c1f0b09d1bf..000ca7ffa64 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCLocalFileDataSource.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/datasources/implementation/OCLocalFileDataSource.kt @@ -24,6 +24,7 @@ import androidx.lifecycle.Transformations import com.owncloud.android.data.files.datasources.LocalFileDataSource import com.owncloud.android.data.files.db.FileDao import com.owncloud.android.data.files.db.OCFileEntity +import com.owncloud.android.domain.availableoffline.model.AvailableOfflineStatus import com.owncloud.android.domain.files.model.MIME_DIR import com.owncloud.android.domain.files.model.MIME_PREFIX_IMAGE import com.owncloud.android.domain.files.model.OCFile @@ -33,10 +34,10 @@ import com.owncloud.android.domain.files.model.OCFile.Companion.ROOT_PATH class OCLocalFileDataSource( private val fileDao: FileDao, ) : LocalFileDataSource { - override fun copyFile(sourceFile: OCFile, targetFile: OCFile, finalRemotePath: String, remoteId: String) { + override fun copyFile(sourceFile: OCFile, targetFolder: OCFile, finalRemotePath: String, remoteId: String) { fileDao.copy( sourceFile = sourceFile.toEntity(), - targetFile = targetFile.toEntity(), + targetFolder = targetFolder.toEntity(), finalRemotePath = finalRemotePath, remoteId = remoteId ) @@ -103,14 +104,20 @@ class OCLocalFileDataSource( it.toModel() } - override fun getFilesAvailableOffline(owner: String): List = fileDao.getFilesAvailableOffline(accountOwner = owner).map { - it.toModel() - } + override fun getFilesAvailableOfflineFromAccount(owner: String): List = + fileDao.getFilesAvailableOfflineFromAccount(accountOwner = owner).map { + it.toModel() + } - override fun moveFile(sourceFile: OCFile, targetFile: OCFile, finalRemotePath: String, finalStoragePath: String) = + override fun getFilesAvailableOfflineFromEveryAccount(): List = + fileDao.getFilesAvailableOfflineFromEveryAccount().map { + it.toModel() + } + + override fun moveFile(sourceFile: OCFile, targetFolder: OCFile, finalRemotePath: String, finalStoragePath: String) = fileDao.moveFile( sourceFile = sourceFile.toEntity(), - targetFile = targetFile.toEntity(), + targetFolder = targetFolder.toEntity(), finalRemotePath = finalRemotePath, finalStoragePath = sourceFile.storagePath?.let { finalStoragePath } ) @@ -134,12 +141,16 @@ class OCLocalFileDataSource( override fun renameFile(fileToRename: OCFile, finalRemotePath: String, finalStoragePath: String) { fileDao.moveFile( sourceFile = fileToRename.toEntity(), - targetFile = fileDao.getFileById(fileToRename.parentId!!)!!, + targetFolder = fileDao.getFileById(fileToRename.parentId!!)!!, finalRemotePath = finalRemotePath, finalStoragePath = fileToRename.storagePath?.let { finalStoragePath } ) } + override fun updateAvailableOfflineStatusForFile(ocFile: OCFile, newAvailableOfflineStatus: AvailableOfflineStatus) { + fileDao.updateAvailableOfflineStatusForFile(ocFile, newAvailableOfflineStatus.ordinal) + } + companion object { @VisibleForTesting fun OCFileEntity.toModel(): OCFile = @@ -159,7 +170,7 @@ class OCLocalFileDataSource( sharedByLink = sharedByLink, sharedWithSharee = sharedWithSharee, storagePath = storagePath, - keepInSync = keepInSync, + availableOfflineStatus = AvailableOfflineStatus.fromValue(availableOfflineStatus), needsToUpdateThumbnail = needsToUpdateThumbnail, fileIsDownloading = fileIsDownloading, lastSyncDateForData = lastSyncDateForData, @@ -186,7 +197,7 @@ class OCLocalFileDataSource( sharedByLink = sharedByLink, sharedWithSharee = sharedWithSharee, storagePath = storagePath, - keepInSync = keepInSync, + availableOfflineStatus = availableOfflineStatus?.ordinal ?: AvailableOfflineStatus.NOT_AVAILABLE_OFFLINE.ordinal, needsToUpdateThumbnail = needsToUpdateThumbnail, fileIsDownloading = fileIsDownloading, lastSyncDateForData = lastSyncDateForData, diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/db/FileDao.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/db/FileDao.kt index 7345b6e5c30..71ba79811aa 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/db/FileDao.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/db/FileDao.kt @@ -25,6 +25,11 @@ import androidx.room.OnConflictStrategy import androidx.room.Query import androidx.room.Transaction import com.owncloud.android.data.ProviderMeta +import com.owncloud.android.domain.availableoffline.model.AvailableOfflineStatus.AVAILABLE_OFFLINE +import com.owncloud.android.domain.availableoffline.model.AvailableOfflineStatus.AVAILABLE_OFFLINE_PARENT +import com.owncloud.android.domain.availableoffline.model.AvailableOfflineStatus.NOT_AVAILABLE_OFFLINE +import com.owncloud.android.domain.ext.isOneOf +import com.owncloud.android.domain.files.model.OCFile import java.io.File.separatorChar @Dao @@ -85,11 +90,14 @@ abstract class FileDao { accountOwner: String ): List - @Query(SELECT_FILES_AVAILABLE_OFFLINE) - abstract fun getFilesAvailableOffline( + @Query(SELECT_FILES_AVAILABLE_OFFLINE_FROM_ACCOUNT) + abstract fun getFilesAvailableOfflineFromAccount( accountOwner: String ): List + @Query(SELECT_FILES_AVAILABLE_OFFLINE_FROM_EVERY_ACCOUNT) + abstract fun getFilesAvailableOfflineFromEveryAccount(): List + @Insert(onConflict = OnConflictStrategy.REPLACE) abstract fun insert(ocFileEntity: OCFileEntity): Long @@ -104,7 +112,10 @@ abstract class FileDao { val folderId = insert(folder) folderContent.forEach { fileToInsert -> - insert(fileToInsert.apply { parentId = folderId }) + insert(fileToInsert.apply { + parentId = folderId + availableOfflineStatus = getNewAvailableOfflineStatus(folder.availableOfflineStatus, fileToInsert.availableOfflineStatus) + }) } } @@ -126,6 +137,7 @@ abstract class FileDao { storagePath = localFile.storagePath, treeEtag = localFile.treeEtag, etagInConflict = localFile.etagInConflict, + availableOfflineStatus = localFile.availableOfflineStatus, ).apply { id = localFile.id }) @@ -135,22 +147,22 @@ abstract class FileDao { @Transaction open fun copy( sourceFile: OCFileEntity, - targetFile: OCFileEntity, + targetFolder: OCFileEntity, finalRemotePath: String, remoteId: String? ) { // 1. Update target size insert( - targetFile.copy( - length = targetFile.length + sourceFile.length - ).apply { id = targetFile.id } + targetFolder.copy( + length = targetFolder.length + sourceFile.length + ).apply { id = targetFolder.id } ) // 2. Insert a new file with common attributes and retrieved remote id insert( OCFileEntity( - parentId = targetFile.id, - owner = targetFile.owner, + parentId = targetFolder.id, + owner = targetFolder.owner, remotePath = finalRemotePath, remoteId = remoteId, length = sourceFile.length, @@ -161,7 +173,8 @@ abstract class FileDao { etag = "", creationTimestamp = null, permissions = null, - treeEtag = "" + treeEtag = "", + availableOfflineStatus = NOT_AVAILABLE_OFFLINE.ordinal, ) ) } @@ -169,15 +182,15 @@ abstract class FileDao { @Transaction open fun moveFile( sourceFile: OCFileEntity, - targetFile: OCFileEntity, + targetFolder: OCFileEntity, finalRemotePath: String, finalStoragePath: String? ) { // 1. Update target size insert( - targetFile.copy( - length = targetFile.length + sourceFile.length - ).apply { id = targetFile.id } + targetFolder.copy( + length = targetFolder.length + sourceFile.length + ).apply { id = targetFolder.id } ) // 2. Update source @@ -185,7 +198,7 @@ abstract class FileDao { // Update remote path and storage path when moving a folder moveFolder( sourceFolder = sourceFile, - targetFile = targetFile, + targetFolder = targetFolder, targetRemotePath = finalRemotePath, targetStoragePath = finalStoragePath ) @@ -193,7 +206,7 @@ abstract class FileDao { // Update remote path, storage path, parent file when moving a file moveSingleFile( sourceFile = sourceFile, - targetFile = targetFile, + targetFolder = targetFolder, finalRemotePath = finalRemotePath, finalStoragePath = finalStoragePath ) @@ -203,24 +216,55 @@ abstract class FileDao { @Query(DELETE_FILE_WITH_ID) abstract fun deleteFileWithId(id: Long) + @Transaction + open fun updateAvailableOfflineStatusForFile(ocFile: OCFile, newAvailableOfflineStatus: Int) { + if (ocFile.isFolder) { + updateFolderWithNewAvailableOfflineStatus(ocFile.id!!, newAvailableOfflineStatus) + } else { + updateFileWithAvailableOfflineStatus(ocFile.id!!, newAvailableOfflineStatus) + } + } + + private fun updateFolderWithNewAvailableOfflineStatus(ocFolderId: Long, newAvailableOfflineStatus: Int) { + updateFileWithAvailableOfflineStatus(ocFolderId, newAvailableOfflineStatus) + + val newStatusForChildren = if (newAvailableOfflineStatus == NOT_AVAILABLE_OFFLINE.ordinal) { + NOT_AVAILABLE_OFFLINE.ordinal + } else { + AVAILABLE_OFFLINE_PARENT.ordinal + } + val folderContent = getFolderContent(ocFolderId) + folderContent.forEach { folderChild -> + if (folderChild.isFolder) { + updateFolderWithNewAvailableOfflineStatus(folderChild.id, newStatusForChildren) + } else { + updateFileWithAvailableOfflineStatus(folderChild.id, newStatusForChildren) + } + } + } + + @Query(UPDATE_FILE_WITH_NEW_AVAILABLE_OFFLINE_STATUS) + abstract fun updateFileWithAvailableOfflineStatus(id: Long, availableOfflineStatus: Int) + private fun moveSingleFile( sourceFile: OCFileEntity, - targetFile: OCFileEntity, + targetFolder: OCFileEntity, finalRemotePath: String, finalStoragePath: String? ) { insert( sourceFile.copy( - parentId = targetFile.id, + parentId = targetFolder.id, remotePath = finalRemotePath, - storagePath = finalStoragePath + storagePath = finalStoragePath, + availableOfflineStatus = getNewAvailableOfflineStatus(targetFolder.availableOfflineStatus, sourceFile.availableOfflineStatus) ).apply { id = sourceFile.id } ) } private fun moveFolder( sourceFolder: OCFileEntity, - targetFile: OCFileEntity, + targetFolder: OCFileEntity, targetRemotePath: String, targetStoragePath: String? ) { @@ -232,7 +276,7 @@ abstract class FileDao { moveSingleFile( sourceFile = sourceFolder, - targetFile = targetFile, + targetFolder = targetFolder, finalRemotePath = folderRemotePath, finalStoragePath = folderStoragePath ) @@ -247,14 +291,14 @@ abstract class FileDao { if (file.isFolder) { moveFolder( sourceFolder = file, - targetFile = sourceFolder, + targetFolder = sourceFolder, targetRemotePath = remotePathForChild, targetStoragePath = storagePathForChild ) } else { moveSingleFile( sourceFile = file, - targetFile = sourceFolder, + targetFolder = sourceFolder, finalRemotePath = remotePathForChild, finalStoragePath = storagePathForChild ) @@ -262,6 +306,24 @@ abstract class FileDao { } } + /** + * If folder is available offline, the child gets the AVAILABLE_OFFLINE_PARENT status + * If child was available offline because of the previous parent, it won't be av offline anymore + * Otherwise, keep the child available offline status + */ + private fun getNewAvailableOfflineStatus( + parentFolderAvailableOfflineStatus: Int?, + currentFileAvailableOfflineStatus: Int?, + ): Int { + return if ((parentFolderAvailableOfflineStatus != null) && + parentFolderAvailableOfflineStatus.isOneOf(AVAILABLE_OFFLINE.ordinal, AVAILABLE_OFFLINE_PARENT.ordinal) + ) { + AVAILABLE_OFFLINE_PARENT.ordinal + } else if (currentFileAvailableOfflineStatus == AVAILABLE_OFFLINE.ordinal) { + AVAILABLE_OFFLINE.ordinal + } else NOT_AVAILABLE_OFFLINE.ordinal + } + companion object { private const val SELECT_FILE_WITH_ID = "SELECT * " + @@ -322,10 +384,20 @@ abstract class FileDao { "AND sharedByLink NOT LIKE '%0%' " + "OR sharedWithSharee NOT LIKE '%0%'" - private const val SELECT_FILES_AVAILABLE_OFFLINE = + private const val SELECT_FILES_AVAILABLE_OFFLINE_FROM_ACCOUNT = "SELECT * " + "FROM ${ProviderMeta.ProviderTableMeta.FILES_TABLE_NAME} " + "WHERE owner = :accountOwner " + "AND keepInSync = '1'" + + private const val SELECT_FILES_AVAILABLE_OFFLINE_FROM_EVERY_ACCOUNT = + "SELECT * " + + "FROM ${ProviderMeta.ProviderTableMeta.FILES_TABLE_NAME} " + + "WHERE keepInSync = '1'" + + private const val UPDATE_FILE_WITH_NEW_AVAILABLE_OFFLINE_STATUS = + "UPDATE ${ProviderMeta.ProviderTableMeta.FILES_TABLE_NAME} " + + "SET keepInSync = :availableOfflineStatus " + + "WHERE id = :id" } } diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/db/OCFileEntity.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/db/OCFileEntity.kt index c43904e8432..d105804e5d4 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/db/OCFileEntity.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/db/OCFileEntity.kt @@ -19,6 +19,7 @@ package com.owncloud.android.data.files.db import android.database.Cursor +import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import com.owncloud.android.data.ProviderMeta.ProviderTableMeta.FILES_TABLE_NAME @@ -69,8 +70,8 @@ data class OCFileEntity( var name: String? = null, val treeEtag: String? = null, - //TODO: May not needed - val keepInSync: Int? = null, + @ColumnInfo(name = "keepInSync") + var availableOfflineStatus: Int? = null, val lastSyncDateForData: Long? = null, val fileShareViaLink: Int? = null, var lastSyncDateForProperties: Long? = null, @@ -111,7 +112,7 @@ data class OCFileEntity( treeEtag = cursor.getString(cursor.getColumnIndexOrThrow(FILE_TREE_ETAG)), lastSyncDateForProperties = cursor.getLong(cursor.getColumnIndexOrThrow(FILE_LAST_SYNC_DATE)), lastSyncDateForData = cursor.getLong(cursor.getColumnIndexOrThrow(FILE_LAST_SYNC_DATE_FOR_DATA)), - keepInSync = cursor.getInt(cursor.getColumnIndexOrThrow(FILE_KEEP_IN_SYNC)), + availableOfflineStatus = cursor.getInt(cursor.getColumnIndexOrThrow(FILE_KEEP_IN_SYNC)), fileShareViaLink = cursor.getInt(cursor.getColumnIndexOrThrow(FILE_SHARED_VIA_LINK)), needsToUpdateThumbnail = cursor.getInt(cursor.getColumnIndexOrThrow(FILE_UPDATE_THUMBNAIL)) == 1, modifiedAtLastSyncForData = cursor.getLong(cursor.getColumnIndexOrThrow(FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA)), diff --git a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt index d0e11d3038a..a9949459e67 100644 --- a/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt +++ b/owncloudData/src/main/java/com/owncloud/android/data/files/repository/OCFileRepository.kt @@ -24,6 +24,9 @@ import androidx.lifecycle.LiveData import com.owncloud.android.data.files.datasources.LocalFileDataSource import com.owncloud.android.data.files.datasources.RemoteFileDataSource import com.owncloud.android.data.storage.LocalStorageProvider +import com.owncloud.android.domain.availableoffline.model.AvailableOfflineStatus +import com.owncloud.android.domain.availableoffline.model.AvailableOfflineStatus.AVAILABLE_OFFLINE_PARENT +import com.owncloud.android.domain.availableoffline.model.AvailableOfflineStatus.NOT_AVAILABLE_OFFLINE import com.owncloud.android.domain.exceptions.ConflictException import com.owncloud.android.domain.exceptions.FileAlreadyExistsException import com.owncloud.android.domain.exceptions.FileNotFoundException @@ -99,7 +102,7 @@ class OCFileRepository( // 3. Update database with latest changes localFileDataSource.copyFile( sourceFile = ocFile, - targetFile = targetFolder, + targetFolder = targetFolder, finalRemotePath = finalRemotePath, remoteId = remoteId ) @@ -131,8 +134,11 @@ class OCFileRepository( override fun getFilesSharedByLink(owner: String): List = localFileDataSource.getFilesSharedByLink(owner) - override fun getFilesAvailableOffline(owner: String): List = - localFileDataSource.getFilesAvailableOffline(owner) + override fun getFilesAvailableOfflineFromAccount(owner: String): List = + localFileDataSource.getFilesAvailableOfflineFromAccount(owner) + + override fun getFilesAvailableOfflineFromEveryAccount(): List = + localFileDataSource.getFilesAvailableOfflineFromEveryAccount() override fun moveFile(listOfFilesToMove: List, targetFile: OCFile) { listOfFilesToMove.forEach { ocFile -> @@ -170,7 +176,7 @@ class OCFileRepository( // 3. Update database with latest changes localFileDataSource.moveFile( sourceFile = ocFile, - targetFile = targetFile, + targetFolder = targetFile, finalRemotePath = finalRemotePath, finalStoragePath = finalStoragePath ) @@ -226,6 +232,9 @@ class OCFileRepository( needsToUpdateThumbnail = !remoteChild.isFolder // remote eTag will not be set unless file CONTENTS are synchronized etag = "" + availableOfflineStatus = + if (remoteFolder.isAvailableOffline) AVAILABLE_OFFLINE_PARENT else NOT_AVAILABLE_OFFLINE + }) } else { // File exists in the database, we need to check several stuff. @@ -236,6 +245,10 @@ class OCFileRepository( etag = localChildToSync.etag needsToUpdateThumbnail = !remoteChild.isFolder && remoteChild.modificationTimestamp != localChildToSync.modificationTimestamp + // Probably not needed, if the child was already in the database, the av offline status should be also there + if (remoteFolder.isAvailableOffline) { + availableOfflineStatus = AVAILABLE_OFFLINE_PARENT + } // FIXME: What about renames? Need to fix storage path }) } @@ -314,6 +327,10 @@ class OCFileRepository( localFileDataSource.saveFile(file) } + override fun updateFileWithNewAvailableOfflineStatus(ocFile: OCFile, newAvailableOfflineStatus: AvailableOfflineStatus) { + localFileDataSource.updateAvailableOfflineStatusForFile(ocFile, newAvailableOfflineStatus) + } + private fun removeLocalFolderRecursively(ocFile: OCFile, onlyFromLocalStorage: Boolean) { val folderContent = localFileDataSource.getFolderContent(ocFile.id!!) diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/availableoffline/model/AvailableOfflineStatus.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/availableoffline/model/AvailableOfflineStatus.kt new file mode 100644 index 00000000000..7dd8ae7502a --- /dev/null +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/availableoffline/model/AvailableOfflineStatus.kt @@ -0,0 +1,47 @@ +/** + * ownCloud Android client application + * + * @author Abel García de Prada + * Copyright (C) 2022 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.owncloud.android.domain.availableoffline.model + + +enum class AvailableOfflineStatus { + /** + * File is not available offline + */ + NOT_AVAILABLE_OFFLINE, + + /** + * File is available offline + */ + AVAILABLE_OFFLINE, + + /** + * File belongs to an available offline folder + */ + AVAILABLE_OFFLINE_PARENT; + + companion object { + fun fromValue(value: Int?): AvailableOfflineStatus { + return when (value) { + AVAILABLE_OFFLINE.ordinal -> AVAILABLE_OFFLINE + AVAILABLE_OFFLINE_PARENT.ordinal -> AVAILABLE_OFFLINE_PARENT + else -> NOT_AVAILABLE_OFFLINE + } + } + } +} diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/files/usecases/GetFilesAvailableOfflineUseCase.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/availableoffline/usecases/GetFilesAvailableOfflineFromAccountUseCase.kt similarity index 84% rename from owncloudDomain/src/main/java/com/owncloud/android/domain/files/usecases/GetFilesAvailableOfflineUseCase.kt rename to owncloudDomain/src/main/java/com/owncloud/android/domain/availableoffline/usecases/GetFilesAvailableOfflineFromAccountUseCase.kt index 6a8b02ac109..9ae27add63f 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/files/usecases/GetFilesAvailableOfflineUseCase.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/availableoffline/usecases/GetFilesAvailableOfflineFromAccountUseCase.kt @@ -15,17 +15,17 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package com.owncloud.android.domain.files.usecases +package com.owncloud.android.domain.availableoffline.usecases import com.owncloud.android.domain.BaseUseCaseWithResult import com.owncloud.android.domain.files.FileRepository import com.owncloud.android.domain.files.model.OCFile -class GetFilesAvailableOfflineUseCase( +class GetFilesAvailableOfflineFromAccountUseCase( private val fileRepository: FileRepository -) : BaseUseCaseWithResult, GetFilesAvailableOfflineUseCase.Params>() { +) : BaseUseCaseWithResult, GetFilesAvailableOfflineFromAccountUseCase.Params>() { - override fun run(params: Params): List = fileRepository.getFilesAvailableOffline(params.owner) + override fun run(params: Params): List = fileRepository.getFilesAvailableOfflineFromAccount(params.owner) data class Params( val owner: String diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/availableoffline/usecases/GetFilesAvailableOfflineFromEveryAccountUseCase.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/availableoffline/usecases/GetFilesAvailableOfflineFromEveryAccountUseCase.kt new file mode 100644 index 00000000000..daa06c819fc --- /dev/null +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/availableoffline/usecases/GetFilesAvailableOfflineFromEveryAccountUseCase.kt @@ -0,0 +1,30 @@ +/* + * ownCloud Android client application + * + * @author Abel García de Prada + * Copyright (C) 2022 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.owncloud.android.domain.availableoffline.usecases + +import com.owncloud.android.domain.BaseUseCase +import com.owncloud.android.domain.files.FileRepository +import com.owncloud.android.domain.files.model.OCFile + +class GetFilesAvailableOfflineFromEveryAccountUseCase( + private val fileRepository: FileRepository +) : BaseUseCase, Unit>() { + + override fun run(params: Unit): List = fileRepository.getFilesAvailableOfflineFromEveryAccount() +} diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/availableoffline/usecases/SetFilesAsAvailableOfflineUseCase.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/availableoffline/usecases/SetFilesAsAvailableOfflineUseCase.kt new file mode 100644 index 00000000000..05b8136b6ac --- /dev/null +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/availableoffline/usecases/SetFilesAsAvailableOfflineUseCase.kt @@ -0,0 +1,46 @@ +/** + * ownCloud Android client application + * + * @author Abel García de Prada + * Copyright (C) 2022 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.owncloud.android.domain.availableoffline.usecases + +import com.owncloud.android.domain.BaseUseCaseWithResult +import com.owncloud.android.domain.availableoffline.model.AvailableOfflineStatus +import com.owncloud.android.domain.files.FileRepository +import com.owncloud.android.domain.files.model.OCFile + +class SetFilesAsAvailableOfflineUseCase( + private val fileRepository: FileRepository, +) : BaseUseCaseWithResult() { + + override fun run(params: Params) { + params.filesToSetAsAvailableOffline.forEach { fileToSetAsAvailableOffline -> + // Its possible to multiselect several files including already available offline files. + // If it is already available offline, we will ignore it. + if (!fileToSetAsAvailableOffline.isAvailableOffline) { + fileRepository.updateFileWithNewAvailableOfflineStatus( + ocFile = fileToSetAsAvailableOffline, + newAvailableOfflineStatus = AvailableOfflineStatus.AVAILABLE_OFFLINE, + ) + } + } + } + + data class Params( + val filesToSetAsAvailableOffline: List + ) +} diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/availableoffline/usecases/UnsetFilesAsAvailableOfflineUseCase.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/availableoffline/usecases/UnsetFilesAsAvailableOfflineUseCase.kt new file mode 100644 index 00000000000..e2dfbf10919 --- /dev/null +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/availableoffline/usecases/UnsetFilesAsAvailableOfflineUseCase.kt @@ -0,0 +1,46 @@ +/** + * ownCloud Android client application + * + * @author Abel García de Prada + * Copyright (C) 2022 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.owncloud.android.domain.availableoffline.usecases + +import com.owncloud.android.domain.BaseUseCaseWithResult +import com.owncloud.android.domain.availableoffline.model.AvailableOfflineStatus +import com.owncloud.android.domain.files.FileRepository +import com.owncloud.android.domain.files.model.OCFile + +class UnsetFilesAsAvailableOfflineUseCase( + private val fileRepository: FileRepository, +) : BaseUseCaseWithResult() { + + override fun run(params: Params) { + params.filesToUnsetAsAvailableOffline.forEach { fileToUnsetAsAvailableOffline -> + // Its possible to multiselect several files including not available offline files. + // If it is not available offline, we will ignore it. + if (fileToUnsetAsAvailableOffline.availableOfflineStatus == AvailableOfflineStatus.AVAILABLE_OFFLINE) { + fileRepository.updateFileWithNewAvailableOfflineStatus( + ocFile = fileToUnsetAsAvailableOffline, + newAvailableOfflineStatus = AvailableOfflineStatus.NOT_AVAILABLE_OFFLINE, + ) + } + } + } + + data class Params( + val filesToUnsetAsAvailableOffline: List + ) +} diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/files/FileRepository.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/files/FileRepository.kt index aa204961e16..18c8a2e8ed3 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/files/FileRepository.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/files/FileRepository.kt @@ -21,6 +21,7 @@ package com.owncloud.android.domain.files import androidx.lifecycle.LiveData +import com.owncloud.android.domain.availableoffline.model.AvailableOfflineStatus import com.owncloud.android.domain.files.model.FileListOption import com.owncloud.android.domain.files.model.OCFile @@ -34,11 +35,14 @@ interface FileRepository { fun getFolderContentAsLiveData(folderId: Long): LiveData> fun getFolderImages(folderId: Long): List fun getFilesSharedByLink(owner: String): List - fun getFilesAvailableOffline(owner: String): List + fun getFilesAvailableOfflineFromAccount(owner: String): List + fun getFilesAvailableOfflineFromEveryAccount(): List fun moveFile(listOfFilesToMove: List, targetFile: OCFile) fun readFile(remotePath: String): OCFile fun refreshFolder(remotePath: String): List fun removeFile(listOfFilesToRemove: List, removeOnlyLocalCopy: Boolean) fun renameFile(ocFile: OCFile, newName: String) fun saveFile(file: OCFile) + + fun updateFileWithNewAvailableOfflineStatus(ocFile: OCFile, newAvailableOfflineStatus: AvailableOfflineStatus) } diff --git a/owncloudDomain/src/main/java/com/owncloud/android/domain/files/model/OCFile.kt b/owncloudDomain/src/main/java/com/owncloud/android/domain/files/model/OCFile.kt index 2e88ff94352..8111f713ed7 100644 --- a/owncloudDomain/src/main/java/com/owncloud/android/domain/files/model/OCFile.kt +++ b/owncloudDomain/src/main/java/com/owncloud/android/domain/files/model/OCFile.kt @@ -22,12 +22,14 @@ package com.owncloud.android.domain.files.model import android.os.Parcelable import android.webkit.MimeTypeMap +import com.owncloud.android.domain.availableoffline.model.AvailableOfflineStatus import com.owncloud.android.domain.ext.isOneOf +import com.owncloud.android.domain.availableoffline.model.AvailableOfflineStatus.AVAILABLE_OFFLINE +import com.owncloud.android.domain.availableoffline.model.AvailableOfflineStatus.AVAILABLE_OFFLINE_PARENT import kotlinx.parcelize.Parcelize import java.io.File import java.util.Locale -//TODO: Add new attributes on demand. Let's try to perform a clean up :) @Parcelize data class OCFile( var id: Long? = null, @@ -45,8 +47,7 @@ data class OCFile( var storagePath: String? = null, var treeEtag: String? = "", - //TODO: May not needed - val keepInSync: Int? = null, + var availableOfflineStatus: AvailableOfflineStatus? = null, var lastSyncDateForData: Long? = 0, var lastSyncDateForProperties: Long? = 0, var needsToUpdateThumbnail: Boolean = false, @@ -142,6 +143,9 @@ data class OCFile( val isSharedWithMe get() = permissions != null && permissions.contains(PERMISSION_SHARED_WITH_ME) + val isAvailableOffline + get() = availableOfflineStatus?.isOneOf(AVAILABLE_OFFLINE, AVAILABLE_OFFLINE_PARENT) ?: false + val localModificationTimestamp: Long get() = storagePath?.takeIf { @@ -158,8 +162,7 @@ data class OCFile( storagePath = sourceFile.storagePath treeEtag = sourceFile.treeEtag etagInConflict = sourceFile.etagInConflict - // FIXME: 19/10/2020 : New_arch: Av.Offline -// setAvailableOfflineStatus(sourceFile.getAvailableOfflineStatus()) + availableOfflineStatus = sourceFile.availableOfflineStatus } /** diff --git a/owncloudDomain/src/test/java/com/owncloud/android/domain/files/model/OCFileTest.kt b/owncloudDomain/src/test/java/com/owncloud/android/domain/files/model/OCFileTest.kt index db2c256e4d6..70bdb558337 100644 --- a/owncloudDomain/src/test/java/com/owncloud/android/domain/files/model/OCFileTest.kt +++ b/owncloudDomain/src/test/java/com/owncloud/android/domain/files/model/OCFileTest.kt @@ -45,7 +45,7 @@ class OCFileTest { OC_FILE.privateLink, OC_FILE.storagePath, OC_FILE.treeEtag, - OC_FILE.keepInSync, + OC_FILE.availableOfflineStatus, OC_FILE.lastSyncDateForData, OC_FILE.lastSyncDateForProperties, OC_FILE.needsToUpdateThumbnail, @@ -71,7 +71,7 @@ class OCFileTest { privateLink = OC_FILE.privateLink, storagePath = OC_FILE.storagePath, treeEtag = OC_FILE.treeEtag, - keepInSync = OC_FILE.keepInSync, + availableOfflineStatus = OC_FILE.availableOfflineStatus, lastSyncDateForData = OC_FILE.lastSyncDateForData, lastSyncDateForProperties = OC_FILE.lastSyncDateForProperties, needsToUpdateThumbnail = OC_FILE.needsToUpdateThumbnail, @@ -103,7 +103,7 @@ class OCFileTest { OC_FILE.privateLink, OC_FILE.storagePath, OC_FILE.treeEtag, - OC_FILE.keepInSync, + OC_FILE.availableOfflineStatus, OC_FILE.lastSyncDateForData, OC_FILE.lastSyncDateForProperties, OC_FILE.needsToUpdateThumbnail, @@ -129,7 +129,7 @@ class OCFileTest { privateLink = OC_FILE.privateLink, storagePath = OC_FILE.storagePath, treeEtag = OC_FILE.treeEtag, - keepInSync = OC_FILE.keepInSync, + availableOfflineStatus = OC_FILE.availableOfflineStatus, lastSyncDateForData = OC_FILE.lastSyncDateForData, lastSyncDateForProperties = OC_FILE.lastSyncDateForProperties, needsToUpdateThumbnail = OC_FILE.needsToUpdateThumbnail, diff --git a/owncloudDomain/src/test/java/com/owncloud/android/domain/files/usecases/GetFilesAvailableOfflineUseCaseTest.kt b/owncloudDomain/src/test/java/com/owncloud/android/domain/files/usecases/GetFilesAvailableOfflineFromAccountUseCaseTest.kt similarity index 72% rename from owncloudDomain/src/test/java/com/owncloud/android/domain/files/usecases/GetFilesAvailableOfflineUseCaseTest.kt rename to owncloudDomain/src/test/java/com/owncloud/android/domain/files/usecases/GetFilesAvailableOfflineFromAccountUseCaseTest.kt index e0df9c44af3..ea8eeb06abe 100644 --- a/owncloudDomain/src/test/java/com/owncloud/android/domain/files/usecases/GetFilesAvailableOfflineUseCaseTest.kt +++ b/owncloudDomain/src/test/java/com/owncloud/android/domain/files/usecases/GetFilesAvailableOfflineFromAccountUseCaseTest.kt @@ -17,55 +17,56 @@ */ package com.owncloud.android.domain.files.usecases +import com.owncloud.android.domain.availableoffline.usecases.GetFilesAvailableOfflineFromAccountUseCase import com.owncloud.android.domain.exceptions.UnauthorizedException import com.owncloud.android.domain.files.FileRepository -import com.owncloud.android.testutil.OC_EMPTY_FILES import com.owncloud.android.testutil.OC_AVAILABLE_OFFLINE_FILES +import com.owncloud.android.testutil.OC_EMPTY_FILES import io.mockk.every import io.mockk.spyk import io.mockk.verify import org.junit.Assert import org.junit.Test -class GetFilesAvailableOfflineUseCaseTest { +class GetFilesAvailableOfflineFromAccountUseCaseTest { private val repository: FileRepository = spyk() - private val useCase = GetFilesAvailableOfflineUseCase(repository) - private val useCaseParams = GetFilesAvailableOfflineUseCase.Params(owner = "owner") + private val useCase = GetFilesAvailableOfflineFromAccountUseCase(repository) + private val useCaseParams = GetFilesAvailableOfflineFromAccountUseCase.Params(owner = "owner") @Test fun `get files available offline - ok`() { - every { repository.getFilesAvailableOffline(useCaseParams.owner) } returns OC_AVAILABLE_OFFLINE_FILES + every { repository.getFilesAvailableOfflineFromAccount(useCaseParams.owner) } returns OC_AVAILABLE_OFFLINE_FILES val useCaseResult = useCase.execute(useCaseParams) Assert.assertTrue(useCaseResult.isSuccess) Assert.assertEquals(OC_AVAILABLE_OFFLINE_FILES, useCaseResult.getDataOrNull()) - verify(exactly = 1) { repository.getFilesAvailableOffline(useCaseParams.owner) } + verify(exactly = 1) { repository.getFilesAvailableOfflineFromAccount(useCaseParams.owner) } } @Test fun `get files available offline - ok - empty list`() { - every { repository.getFilesAvailableOffline(useCaseParams.owner) } returns OC_EMPTY_FILES + every { repository.getFilesAvailableOfflineFromAccount(useCaseParams.owner) } returns OC_EMPTY_FILES val useCaseResult = useCase.execute(useCaseParams) Assert.assertTrue(useCaseResult.isSuccess) Assert.assertEquals(OC_EMPTY_FILES, useCaseResult.getDataOrNull()) - verify(exactly = 1) { repository.getFilesAvailableOffline(useCaseParams.owner) } + verify(exactly = 1) { repository.getFilesAvailableOfflineFromAccount(useCaseParams.owner) } } @Test fun `get files savailable offline - ko`() { - every { repository.getFilesAvailableOffline(useCaseParams.owner) } throws UnauthorizedException() + every { repository.getFilesAvailableOfflineFromAccount(useCaseParams.owner) } throws UnauthorizedException() val useCaseResult = useCase.execute(useCaseParams) Assert.assertTrue(useCaseResult.isError) Assert.assertTrue(useCaseResult.getThrowableOrNull() is UnauthorizedException) - verify(exactly = 1) { repository.getFilesAvailableOffline(useCaseParams.owner) } + verify(exactly = 1) { repository.getFilesAvailableOfflineFromAccount(useCaseParams.owner) } } } diff --git a/owncloudTestUtil/src/main/java/com/owncloud/android/testutil/OCFile.kt b/owncloudTestUtil/src/main/java/com/owncloud/android/testutil/OCFile.kt index 9abfc452f83..505cbbc9527 100644 --- a/owncloudTestUtil/src/main/java/com/owncloud/android/testutil/OCFile.kt +++ b/owncloudTestUtil/src/main/java/com/owncloud/android/testutil/OCFile.kt @@ -18,6 +18,7 @@ */ package com.owncloud.android.testutil +import com.owncloud.android.domain.availableoffline.model.AvailableOfflineStatus import com.owncloud.android.domain.files.model.OCFile val OC_FOLDER = OCFile( @@ -47,7 +48,8 @@ val OC_FILE = OCFile( modificationTimestamp = 1593510589000, etag = "5efb0c13c688f", mimeType = "image/jpeg", - length = 3000000 + length = 3000000, + availableOfflineStatus = AvailableOfflineStatus.NOT_AVAILABLE_OFFLINE, ) val OC_AVAILABLE_OFFLINE_FILE = OCFile( @@ -62,7 +64,7 @@ val OC_AVAILABLE_OFFLINE_FILE = OCFile( modificationTimestamp = 1593510589000, etag = "5efb0c13c688f", mimeType = "image/jpeg", - keepInSync = 1, + availableOfflineStatus = AvailableOfflineStatus.AVAILABLE_OFFLINE_PARENT, length = 3000000 )