Rebase master

Signed-off-by: alperozturk <alper_ozturk@proton.me>
This commit is contained in:
alperozturk 2024-01-10 12:56:01 +01:00 committed by Alper Öztürk
parent 0fd76576b2
commit f67b3a9f83
22 changed files with 211 additions and 865 deletions

View file

@ -23,7 +23,7 @@ package com.owncloud.android.files
import androidx.test.core.app.launchActivity
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.nextcloud.client.account.User
import com.nextcloud.client.files.downloader.FileDownloadWorker
import com.nextcloud.client.files.downloader.FilesDownloadWorker
import com.nextcloud.test.TestActivity
import com.nextcloud.utils.EditorUtils
import com.owncloud.android.AbstractIT
@ -62,7 +62,7 @@ class FileMenuFilterIT : AbstractIT() {
private lateinit var mockFileUploaderBinder: FileUploader.FileUploaderBinder
@MockK
private lateinit var mockFileDownloadProgressListener: FileDownloadWorker.FileDownloadProgressListener
private lateinit var mockFileDownloaderBinder: FilesDownloadWorker.FileDownloaderBinder
@MockK
private lateinit var mockOperationsServiceBinder: OperationsService.OperationsServiceBinder
@ -77,8 +77,8 @@ class FileMenuFilterIT : AbstractIT() {
MockKAnnotations.init(this)
every { mockFileUploaderBinder.isUploading(any(), any()) } returns false
every { mockComponentsGetter.fileUploaderBinder } returns mockFileUploaderBinder
every { mockFileDownloadProgressListener.isDownloading(any(), any()) } returns false
every { mockComponentsGetter.fileDownloadProgressListener } returns mockFileDownloadProgressListener
every { mockFileDownloaderBinder.isDownloading(any(), any()) } returns false
every { mockComponentsGetter.fileDownloaderBinder } returns mockFileDownloaderBinder
every { mockOperationsServiceBinder.isSynchronizing(any(), any()) } returns false
every { mockComponentsGetter.operationsServiceBinder } returns mockOperationsServiceBinder
every { mockStorageManager.getFileById(any()) } returns OCFile("/")

View file

@ -25,7 +25,7 @@ import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.nextcloud.client.files.downloader.FileDownloadWorker
import com.nextcloud.client.files.downloader.FilesDownloadWorker
import com.nextcloud.client.network.Connectivity
import com.nextcloud.client.network.ConnectivityService
import com.nextcloud.utils.EditorUtils
@ -130,7 +130,7 @@ class TestActivity :
return null
}
override fun getFileDownloadProgressListener(): FileDownloadWorker.FileDownloadProgressListener? {
override fun getFileDownloaderBinder(): FilesDownloadWorker.FileDownloaderBinder? {
return null
}

View file

@ -45,10 +45,10 @@ class FilesDownloadHelper {
user: User,
ocFile: OCFile,
behaviour: String,
downloadType: DownloadType,
downloadType: DownloadType?,
activityName: String,
packageName: String,
conflictUploadId: Long
conflictUploadId: Long?
) {
backgroundJobManager.startFilesDownloadJob(
user,

View file

@ -99,6 +99,14 @@ class FilesDownloadWorker(
const val EXTRA_REMOTE_PATH = "REMOTE_PATH"
const val EXTRA_LINKED_TO_PATH = "LINKED_TO"
const val ACCOUNT_NAME = "ACCOUNT_NAME"
fun getDownloadAddedMessage(): String {
return FilesDownloadWorker::class.java.name + "DOWNLOAD_ADDED"
}
fun getDownloadFinishMessage(): String {
return FilesDownloadWorker::class.java.name + "DOWNLOAD_FINISH"
}
}
private var notification: Notification? = null
@ -436,14 +444,6 @@ class FilesDownloadWorker(
pendingDownloads.remove(accountName)
}
private fun getDownloadAddedMessage(): String {
return FilesDownloadWorker::class.java.name + "DOWNLOAD_ADDED"
}
private fun getDownloadFinishMessage(): String {
return FilesDownloadWorker::class.java.name + "DOWNLOAD_FINISH"
}
private fun sendBroadcastNewDownload(
download: DownloadFileOperation,
linkedToRemotePath: String
@ -533,6 +533,23 @@ class FilesDownloadWorker(
return user != null && file != null && pendingDownloads.contains(user.accountName, file.remotePath)
}
fun addDatatransferProgressListener(listener: OnDatatransferProgressListener?, file: OCFile?) {
if (file == null || listener == null) {
return
}
mBoundListeners[file.fileId] = listener
}
fun removeDatatransferProgressListener(listener: OnDatatransferProgressListener?, file: OCFile?) {
if (file == null || listener == null) {
return
}
val fileId = file.fileId
if (mBoundListeners[fileId] === listener) {
mBoundListeners.remove(fileId)
}
}
override fun onTransferProgress(
progressRate: Long, totalTransferredSoFar: Long,
totalToTransfer: Long, fileName: String

View file

@ -149,10 +149,10 @@ interface BackgroundJobManager {
user: User,
ocFile: OCFile,
behaviour: String,
downloadType: DownloadType,
downloadType: DownloadType?,
activityName: String,
packageName: String,
conflictUploadId: Long
conflictUploadId: Long?
)
fun startPdfGenerateAndUploadWork(user: User, uploadFolder: String, imagePaths: List<String>, pdfPath: String)

View file

@ -42,7 +42,6 @@ import com.nextcloud.client.files.downloader.FilesDownloadWorker
import com.nextcloud.client.preferences.AppPreferences
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.operations.DownloadType
import java.io.File
import java.util.Date
import java.util.UUID
import java.util.concurrent.TimeUnit
@ -513,10 +512,10 @@ internal class BackgroundJobManagerImpl(
user: User,
ocFile: OCFile,
behaviour: String,
downloadType: DownloadType,
downloadType: DownloadType?,
activityName: String,
packageName: String,
conflictUploadId: Long
conflictUploadId: Long?
) {
val data = workDataOf(
FilesDownloadWorker.USER to user,

View file

@ -33,13 +33,12 @@ import androidx.core.app.NotificationCompat
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.nextcloud.client.account.User
import com.nextcloud.client.files.downloader.FilesDownloadHelper
import com.owncloud.android.R
import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.files.services.FileDownloader
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.operations.DownloadType
import com.owncloud.android.ui.dialog.SendShareDialog
import com.owncloud.android.ui.notifications.NotificationUtils
import com.owncloud.android.utils.FileExportUtils
import com.owncloud.android.utils.FileStorageUtils
@ -112,14 +111,15 @@ class FilesExportWork(
}
private fun downloadFile(ocFile: OCFile) {
val i = Intent(appContext, FileDownloader::class.java)
i.putExtra(FileDownloader.EXTRA_USER, user)
i.putExtra(FileDownloader.EXTRA_FILE, ocFile)
i.putExtra(SendShareDialog.PACKAGE_NAME, "")
i.putExtra(SendShareDialog.ACTIVITY_NAME, "")
i.putExtra(FileDownloader.DOWNLOAD_TYPE, DownloadType.EXPORT)
FileDownloader(i)
FilesDownloadHelper().downloadFile(
user,
ocFile,
behaviour = "",
packageName = "",
activityName = "",
conflictUploadId = 0L,
downloadType = DownloadType.EXPORT
)
}
private fun showErrorNotification(successfulExports: Int) {

View file

@ -31,7 +31,7 @@ import android.view.Menu;
import com.nextcloud.android.files.FileLockingHelper;
import com.nextcloud.client.account.User;
import com.nextcloud.client.editimage.EditImageActivity;
import com.nextcloud.client.files.downloader.FileDownloadHelper;
import com.nextcloud.client.files.downloader.FilesDownloadWorker;
import com.nextcloud.utils.EditorUtils;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.FileDataStorageManager;
@ -380,8 +380,9 @@ public class FileMenuFilter {
if (componentsGetter != null && !files.isEmpty() && user != null) {
OperationsServiceBinder opsBinder = componentsGetter.getOperationsServiceBinder();
FileUploaderBinder uploaderBinder = componentsGetter.getFileUploaderBinder();
FilesDownloadWorker.FileDownloaderBinder downloaderBinder = componentsGetter.getFileDownloaderBinder();
synchronizing = anyFileSynchronizing(opsBinder) || // comparing local and remote
anyFileDownloading() ||
anyFileDownloading(downloaderBinder) ||
anyFileUploading(uploaderBinder);
}
return synchronizing;
@ -397,14 +398,14 @@ public class FileMenuFilter {
return synchronizing;
}
private boolean anyFileDownloading() {
for (OCFile file : files) {
if (FileDownloadHelper.Companion.instance().isDownloading(user, file)) {
return true;
private boolean anyFileDownloading(FilesDownloadWorker.FileDownloaderBinder downloaderBinder) {
boolean downloading = false;
if (downloaderBinder != null) {
for (Iterator<OCFile> iterator = files.iterator(); !downloading && iterator.hasNext(); ) {
downloading = downloaderBinder.isDownloading(user, iterator.next());
}
}
return false;
return downloading;
}
private boolean anyFileUploading(FileUploaderBinder uploaderBinder) {

View file

@ -1,686 +0,0 @@
/*
* ownCloud Android client application
*
* Copyright (C) 2012 Bartek Przybylski
* Copyright (C) 2012-2016 ownCloud Inc.
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
package com.owncloud.android.files.services;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.OnAccountsUpdateListener;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.util.Pair;
import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.files.downloader.DownloadTask;
import com.nextcloud.client.files.downloader.FilesDownloadWorker;
import com.nextcloud.java.util.Optional;
import com.nextcloud.utils.extensions.IntentExtensionsKt;
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
import com.owncloud.android.authentication.AuthenticatorActivity;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.datamodel.UploadsStorageManager;
import com.owncloud.android.lib.common.OwnCloudAccount;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.files.FileUtils;
import com.owncloud.android.operations.DownloadFileOperation;
import com.owncloud.android.operations.DownloadType;
import com.owncloud.android.providers.DocumentsStorageProvider;
import com.owncloud.android.ui.activity.ConflictsResolveActivity;
import com.owncloud.android.ui.activity.FileActivity;
import com.owncloud.android.ui.activity.FileDisplayActivity;
import com.owncloud.android.ui.dialog.SendShareDialog;
import com.owncloud.android.ui.fragment.OCFileListFragment;
import com.owncloud.android.ui.notifications.NotificationUtils;
import com.owncloud.android.ui.preview.PreviewImageActivity;
import com.owncloud.android.ui.preview.PreviewImageFragment;
import com.owncloud.android.utils.ErrorMessageAdapter;
import com.owncloud.android.utils.MimeTypeUtil;
import com.owncloud.android.utils.theme.ViewThemeUtils;
import java.io.File;
import java.security.SecureRandom;
import java.util.AbstractList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;
import javax.inject.Inject;
import androidx.core.app.NotificationCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import androidx.work.WorkRequest;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import static android.content.Context.NOTIFICATION_SERVICE;
public class FileDownloader implements OnDatatransferProgressListener, OnAccountsUpdateListener {
private final Context context = MainApp.getAppContext();
private final Intent intent;
private static final String DOWNLOAD_ADDED_MESSAGE = "DOWNLOAD_ADDED";
private static final String DOWNLOAD_FINISH_MESSAGE = "DOWNLOAD_FINISH";
public static final String EXTRA_USER = "USER";
public static final String EXTRA_FILE = "FILE";
public static final String EXTRA_DOWNLOAD_RESULT = "RESULT";
public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH";
public static final String EXTRA_LINKED_TO_PATH = "LINKED_TO";
public static final String ACCOUNT_NAME = "ACCOUNT_NAME";
public static final String DOWNLOAD_TYPE = "DOWNLOAD_TYPE";
private static final int FOREGROUND_SERVICE_ID = 412;
private static final String TAG = FileDownloader.class.getSimpleName();
private Looper mServiceLooper;
private ServiceHandler mServiceHandler;
private IBinder mBinder;
private OwnCloudClient mDownloadClient;
private Optional<User> currentUser = Optional.empty();
private FileDataStorageManager mStorageManager;
private IndexedForest<DownloadFileOperation> mPendingDownloads = new IndexedForest<>();
private DownloadFileOperation mCurrentDownload;
private NotificationManager mNotificationManager;
private NotificationCompat.Builder mNotificationBuilder;
private int mLastPercent;
private Notification mNotification;
private long conflictUploadId;
public boolean mStartedDownload = false;
@Inject UserAccountManager accountManager;
@Inject UploadsStorageManager uploadsStorageManager;
@Inject LocalBroadcastManager localBroadcastManager;
@Inject ViewThemeUtils viewThemeUtils;
public static String getDownloadAddedMessage() {
return FileDownloader.class.getName() + DOWNLOAD_ADDED_MESSAGE;
}
public static String getDownloadFinishMessage() {
return FileDownloader.class.getName() + DOWNLOAD_FINISH_MESSAGE;
}
public FileDownloader(Intent intent) {
this.intent = intent;
Log_OC.d(TAG, "Creating service");
mNotificationManager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
HandlerThread thread = new HandlerThread("FileDownloaderThread", Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper, this);
mBinder = new FileDownloaderBinder();
NotificationCompat.Builder builder = NotificationUtils.newNotificationBuilder(context, viewThemeUtils).setContentTitle(
context.getResources().getString(R.string.app_name))
.setContentText(context.getResources().getString(R.string.foreground_service_download))
.setSmallIcon(R.drawable.notification_icon)
.setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.drawable.notification_icon));
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
builder.setChannelId(NotificationUtils.NOTIFICATION_CHANNEL_DOWNLOAD);
}
mNotification = builder.build();
// add AccountsUpdatedListener
AccountManager am = AccountManager.get(context);
am.addOnAccountsUpdatedListener(this, null, false);
}
@Override
protected void finalize() throws Throwable {
Log_OC.v(TAG, "Destroying service");
mBinder = null;
mServiceHandler = null;
mServiceLooper.quit();
mServiceLooper = null;
mNotificationManager = null;
// remove AccountsUpdatedListener
AccountManager am = AccountManager.get(context);
am.removeOnAccountsUpdatedListener(this);
super.finalize();
}
public void download() {
final User user = IntentExtensionsKt.getParcelableArgument(intent, EXTRA_USER, User.class);
final OCFile file = IntentExtensionsKt.getParcelableArgument(intent, EXTRA_FILE, OCFile.class);
final String behaviour = intent.getStringExtra(OCFileListFragment.DOWNLOAD_BEHAVIOUR);
DownloadType downloadType = DownloadType.DOWNLOAD;
if (intent.hasExtra(DOWNLOAD_TYPE)) {
downloadType = IntentExtensionsKt.getSerializableArgument(intent, DOWNLOAD_TYPE, DownloadType.class);
}
String activityName = intent.getStringExtra(SendShareDialog.ACTIVITY_NAME);
String packageName = intent.getStringExtra(SendShareDialog.PACKAGE_NAME);
conflictUploadId = intent.getLongExtra(ConflictsResolveActivity.EXTRA_CONFLICT_UPLOAD_ID, -1);
AbstractList<String> requestedDownloads = new Vector<String>();
try {
DownloadFileOperation newDownload = new DownloadFileOperation(user,
file,
behaviour,
activityName,
packageName,
context,
downloadType);
newDownload.addDatatransferProgressListener(this);
newDownload.addDatatransferProgressListener((FileDownloaderBinder) mBinder);
Pair<String, String> putResult = mPendingDownloads.putIfAbsent(user.getAccountName(),
file.getRemotePath(),
newDownload);
if (putResult != null) {
String downloadKey = putResult.first;
requestedDownloads.add(downloadKey);
sendBroadcastNewDownload(newDownload, putResult.second);
} // else, file already in the queue of downloads; don't repeat the request
} catch (IllegalArgumentException e) {
Log_OC.e(TAG, "Not enough information provided in intent: " + e.getMessage());
}
if (requestedDownloads.size() > 0) {
Message msg = mServiceHandler.obtainMessage();
// msg.arg1 = startId;
msg.obj = requestedDownloads;
mServiceHandler.sendMessage(msg);
}
}
@Override
public void onAccountsUpdated(Account[] accounts) {
//review the current download and cancel it if its account doesn't exist
if (mCurrentDownload != null && !accountManager.exists(mCurrentDownload.getUser().toPlatformAccount())) {
mCurrentDownload.cancel();
}
// The rest of downloads are cancelled when they try to start
}
/**
* Binder to let client components to perform operations on the queue of downloads.
* <p/>
* It provides by itself the available operations.
*/
public class FileDownloaderBinder2 extends Binder implements OnDatatransferProgressListener {
/**
* Map of listeners that will be reported about progress of downloads from a
* {@link FileDownloaderBinder}
* instance.
*/
private Map<Long, OnDatatransferProgressListener> mBoundListeners =
new HashMap<Long, OnDatatransferProgressListener>();
/**
* Cancels a pending or current download of a remote file.
*
* @param account ownCloud account where the remote file is stored.
* @param file A file in the queue of pending downloads
*/
public void cancel(Account account, OCFile file) {
Pair<DownloadFileOperation, String> removeResult =
mPendingDownloads.remove(account.name, file.getRemotePath());
DownloadFileOperation download = removeResult.first;
if (download != null) {
download.cancel();
} else {
if (mCurrentDownload != null && currentUser.isPresent() &&
mCurrentDownload.getRemotePath().startsWith(file.getRemotePath()) &&
account.name.equals(currentUser.get().getAccountName())) {
mCurrentDownload.cancel();
}
}
}
/**
* Cancels all the downloads for an account
*/
public void cancel(String accountName) {
if (mCurrentDownload != null && mCurrentDownload.getUser().nameEquals(accountName)) {
mCurrentDownload.cancel();
}
// Cancel pending downloads
cancelPendingDownloads(accountName);
}
/**
* Returns True when the file described by 'file' in the ownCloud account 'account'
* is downloading or waiting to download.
*
* If 'file' is a directory, returns 'true' if any of its descendant files is downloading or
* waiting to download.
*
* @param user user where the remote file is stored.
* @param file A file that could be in the queue of downloads.
*/
public boolean isDownloading(User user, OCFile file) {
return user != null && file != null && mPendingDownloads.contains(user.getAccountName(), file.getRemotePath());
}
/**
* Adds a listener interested in the progress of the download for a concrete file.
*
* @param listener Object to notify about progress of transfer.
* @param file {@link OCFile} of interest for listener.
*/
public void addDatatransferProgressListener(OnDatatransferProgressListener listener, OCFile file) {
if (file == null || listener == null) {
return;
}
mBoundListeners.put(file.getFileId(), listener);
}
/**
* Removes a listener interested in the progress of the download for a concrete file.
*
* @param listener Object to notify about progress of transfer.
* @param file {@link OCFile} of interest for listener.
*/
public void removeDatatransferProgressListener(OnDatatransferProgressListener listener, OCFile file) {
if (file == null || listener == null) {
return;
}
Long fileId = file.getFileId();
if (mBoundListeners.get(fileId) == listener) {
mBoundListeners.remove(fileId);
}
}
@Override
public void onTransferProgress(long progressRate, long totalTransferredSoFar,
long totalToTransfer, String fileName) {
OnDatatransferProgressListener boundListener =
mBoundListeners.get(mCurrentDownload.getFile().getFileId());
if (boundListener != null) {
boundListener.onTransferProgress(progressRate, totalTransferredSoFar,
totalToTransfer, fileName);
}
}
}
/**
* Download worker. Performs the pending downloads in the order they were requested.
* Created with the Looper of a new thread, started in {@link FileUploader#onCreate()}.
*/
private static class ServiceHandler extends Handler {
// don't make it a final class, and don't remove the static ; lint will warn about a
// possible memory leak
FileDownloader mService;
public ServiceHandler(Looper looper, FileDownloader service) {
super(looper);
if (service == null) {
throw new IllegalArgumentException("Received invalid NULL in parameter 'service'");
}
mService = service;
}
@Override
public void handleMessage(Message msg) {
@SuppressWarnings("unchecked")
AbstractList<String> requestedDownloads = (AbstractList<String>) msg.obj;
if (msg.obj != null) {
Iterator<String> it = requestedDownloads.iterator();
while (it.hasNext()) {
String next = it.next();
mService.downloadFile(next);
}
}
mService.mStartedDownload=false;
(new Handler()).postDelayed(() -> {
if(!mService.mStartedDownload){
mService.mNotificationManager.cancel(R.string.downloader_download_in_progress_ticker);
}
Log_OC.d(TAG, "Stopping after command with id " + msg.arg1);
mService.mNotificationManager.cancel(FOREGROUND_SERVICE_ID);
}, 2000);
}
}
void downloadFile(String downloadKey) {
mStartedDownload = true;
mCurrentDownload = mPendingDownloads.get(downloadKey);
if (mCurrentDownload != null) {
// Detect if the account exists
if (accountManager.exists(mCurrentDownload.getUser().toPlatformAccount())) {
notifyDownloadStart(mCurrentDownload);
RemoteOperationResult downloadResult = null;
try {
/// prepare client object to send the request to the ownCloud server
Account currentDownloadAccount = mCurrentDownload.getUser().toPlatformAccount();
Optional<User> currentDownloadUser = accountManager.getUser(currentDownloadAccount.name);
if (!currentUser.equals(currentDownloadUser)) {
currentUser = currentDownloadUser;
mStorageManager = new FileDataStorageManager(currentUser.get(), context.getContentResolver());
} // else, reuse storage manager from previous operation
// always get client from client manager, to get fresh credentials in case
// of update
OwnCloudAccount ocAccount = currentDownloadUser.get().toOwnCloudAccount();
mDownloadClient = OwnCloudClientManagerFactory.getDefaultSingleton().
getClientFor(ocAccount, context);
/// perform the download
downloadResult = mCurrentDownload.execute(mDownloadClient);
if (downloadResult.isSuccess() && mCurrentDownload.getDownloadType() == DownloadType.DOWNLOAD) {
saveDownloadedFile();
}
} catch (Exception e) {
Log_OC.e(TAG, "Error downloading", e);
downloadResult = new RemoteOperationResult(e);
} finally {
Pair<DownloadFileOperation, String> removeResult = mPendingDownloads.removePayload(
mCurrentDownload.getUser().getAccountName(), mCurrentDownload.getRemotePath());
if (downloadResult == null) {
downloadResult = new RemoteOperationResult(new RuntimeException("Error downloading…"));
}
/// notify result
notifyDownloadResult(mCurrentDownload, downloadResult);
sendBroadcastDownloadFinished(mCurrentDownload, downloadResult, removeResult.second);
}
} else {
cancelPendingDownloads(mCurrentDownload.getUser().getAccountName());
}
}
}
/**
* Updates the OC File after a successful download.
*
* TODO move to DownloadFileOperation
* unify with code from {@link DocumentsStorageProvider} and {@link DownloadTask}.
*/
private void saveDownloadedFile() {
OCFile file = mStorageManager.getFileById(mCurrentDownload.getFile().getFileId());
if (file == null) {
// try to get file via path, needed for overwriting existing files on conflict dialog
file = mStorageManager.getFileByDecryptedRemotePath(mCurrentDownload.getFile().getRemotePath());
}
if (file == null) {
Log_OC.e(this, "Could not save " + mCurrentDownload.getFile().getRemotePath());
return;
}
long syncDate = System.currentTimeMillis();
file.setLastSyncDateForProperties(syncDate);
file.setLastSyncDateForData(syncDate);
file.setUpdateThumbnailNeeded(true);
file.setModificationTimestamp(mCurrentDownload.getModificationTimestamp());
file.setModificationTimestampAtLastSyncForData(mCurrentDownload.getModificationTimestamp());
file.setEtag(mCurrentDownload.getEtag());
file.setMimeType(mCurrentDownload.getMimeType());
file.setStoragePath(mCurrentDownload.getSavePath());
file.setFileLength(new File(mCurrentDownload.getSavePath()).length());
file.setRemoteId(mCurrentDownload.getFile().getRemoteId());
mStorageManager.saveFile(file);
if (MimeTypeUtil.isMedia(mCurrentDownload.getMimeType())) {
FileDataStorageManager.triggerMediaScan(file.getStoragePath(), file);
}
mStorageManager.saveConflict(file, null);
}
/**
* Creates a status notification to show the download progress
*
* @param download Download operation starting.
*/
private void notifyDownloadStart(DownloadFileOperation download) {
/// create status notification with a progress bar
mLastPercent = 0;
mNotificationBuilder = NotificationUtils.newNotificationBuilder(context, viewThemeUtils);
mNotificationBuilder
.setSmallIcon(R.drawable.notification_icon)
.setTicker(context.getString(R.string.downloader_download_in_progress_ticker))
.setContentTitle(context.getString(R.string.downloader_download_in_progress_ticker))
.setOngoing(true)
.setProgress(100, 0, download.getSize() < 0)
.setContentText(
String.format(context.getString(R.string.downloader_download_in_progress_content), 0,
new File(download.getSavePath()).getName()));
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
mNotificationBuilder.setChannelId(NotificationUtils.NOTIFICATION_CHANNEL_DOWNLOAD);
}
/// includes a pending intent in the notification showing the details view of the file
Intent showDetailsIntent = null;
if (PreviewImageFragment.canBePreviewed(download.getFile())) {
showDetailsIntent = new Intent(context, PreviewImageActivity.class);
} else {
showDetailsIntent = new Intent(context, FileDisplayActivity.class);
}
showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, download.getFile());
showDetailsIntent.putExtra(FileActivity.EXTRA_USER, download.getUser());
showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
mNotificationBuilder.setContentIntent(PendingIntent.getActivity(context, (int) System.currentTimeMillis(),
showDetailsIntent, PendingIntent.FLAG_IMMUTABLE));
if (mNotificationManager == null) {
mNotificationManager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
}
if (mNotificationManager != null) {
mNotificationManager.notify(R.string.downloader_download_in_progress_ticker, mNotificationBuilder.build());
}
}
/**
* Callback method to update the progress bar in the status notification.
*/
@Override
public void onTransferProgress(long progressRate, long totalTransferredSoFar,
long totalToTransfer, String filePath) {
int percent = (int) (100.0 * ((double) totalTransferredSoFar) / ((double) totalToTransfer));
if (percent != mLastPercent) {
mNotificationBuilder.setProgress(100, percent, totalToTransfer < 0);
String fileName = filePath.substring(filePath.lastIndexOf(FileUtils.PATH_SEPARATOR) + 1);
String text = String.format(context.getString(R.string.downloader_download_in_progress_content), percent, fileName);
mNotificationBuilder.setContentText(text);
if (mNotificationManager == null) {
mNotificationManager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
}
if (mNotificationManager != null) {
mNotificationManager.notify(R.string.downloader_download_in_progress_ticker,
mNotificationBuilder.build());
}
}
mLastPercent = percent;
}
/**
* Updates the status notification with the result of a download operation.
*
* @param downloadResult Result of the download operation.
* @param download Finished download operation
*/
@SuppressFBWarnings("DMI")
private void notifyDownloadResult(DownloadFileOperation download,
RemoteOperationResult downloadResult) {
if (mNotificationManager == null) {
mNotificationManager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
}
if (!downloadResult.isCancelled()) {
if (downloadResult.isSuccess()) {
if (conflictUploadId > 0) {
uploadsStorageManager.removeUpload(conflictUploadId);
}
// Dont show notification except an error has occured.
return;
}
int tickerId = downloadResult.isSuccess() ?
R.string.downloader_download_succeeded_ticker : R.string.downloader_download_failed_ticker;
boolean needsToUpdateCredentials = ResultCode.UNAUTHORIZED == downloadResult.getCode();
tickerId = needsToUpdateCredentials ?
R.string.downloader_download_failed_credentials_error : tickerId;
mNotificationBuilder
.setTicker(context.getString(tickerId))
.setContentTitle(context.getString(tickerId))
.setAutoCancel(true)
.setOngoing(false)
.setProgress(0, 0, false);
if (needsToUpdateCredentials) {
configureUpdateCredentialsNotification(download.getUser());
} else {
// TODO put something smart in showDetailsIntent
Intent showDetailsIntent = new Intent();
mNotificationBuilder.setContentIntent(PendingIntent.getActivity(context, (int) System.currentTimeMillis(),
showDetailsIntent, PendingIntent.FLAG_IMMUTABLE));
}
mNotificationBuilder.setContentText(ErrorMessageAdapter.getErrorCauseMessage(downloadResult,
download, context.getResources()));
if (mNotificationManager != null) {
mNotificationManager.notify((new SecureRandom()).nextInt(), mNotificationBuilder.build());
// Remove success notification
if (downloadResult.isSuccess()) {
// Sleep 2 seconds, so show the notification before remove it
NotificationUtils.cancelWithDelay(mNotificationManager,
R.string.downloader_download_succeeded_ticker, 2000);
}
}
}
}
private void configureUpdateCredentialsNotification(User user) {
// let the user update credentials with one click
Intent updateAccountCredentials = new Intent(context, AuthenticatorActivity.class);
updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, user.toPlatformAccount());
updateAccountCredentials.putExtra(
AuthenticatorActivity.EXTRA_ACTION,
AuthenticatorActivity.ACTION_UPDATE_EXPIRED_TOKEN
);
updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
updateAccountCredentials.addFlags(Intent.FLAG_FROM_BACKGROUND);
mNotificationBuilder.setContentIntent(
PendingIntent.getActivity(context,
(int) System.currentTimeMillis(),
updateAccountCredentials,
PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE)
);
}
/**
* Sends a broadcast when a download finishes in order to the interested activities can
* update their view
*
* @param download Finished download operation
* @param downloadResult Result of the download operation
* @param unlinkedFromRemotePath Path in the downloads tree where the download was unlinked from
*/
private void sendBroadcastDownloadFinished(
DownloadFileOperation download,
RemoteOperationResult downloadResult,
String unlinkedFromRemotePath) {
Intent end = new Intent(getDownloadFinishMessage());
end.putExtra(EXTRA_DOWNLOAD_RESULT, downloadResult.isSuccess());
end.putExtra(ACCOUNT_NAME, download.getUser().getAccountName());
end.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath());
end.putExtra(OCFileListFragment.DOWNLOAD_BEHAVIOUR, download.getBehaviour());
end.putExtra(SendShareDialog.ACTIVITY_NAME, download.getActivityName());
end.putExtra(SendShareDialog.PACKAGE_NAME, download.getPackageName());
if (unlinkedFromRemotePath != null) {
end.putExtra(EXTRA_LINKED_TO_PATH, unlinkedFromRemotePath);
}
end.setPackage(context.getPackageName());
localBroadcastManager.sendBroadcast(end);
}
/**
* Sends a broadcast when a new download is added to the queue.
*
* @param download Added download operation
* @param linkedToRemotePath Path in the downloads tree where the download was linked to
*/
private void sendBroadcastNewDownload(DownloadFileOperation download,
String linkedToRemotePath) {
Intent added = new Intent(getDownloadAddedMessage());
added.putExtra(ACCOUNT_NAME, download.getUser().getAccountName());
added.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath());
added.putExtra(EXTRA_LINKED_TO_PATH, linkedToRemotePath);
added.setPackage(context.getPackageName());
localBroadcastManager.sendBroadcast(added);
}
private void cancelPendingDownloads(String accountName) {
mPendingDownloads.remove(accountName);
}
}

View file

@ -22,13 +22,12 @@
package com.owncloud.android.operations;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
import com.nextcloud.client.account.User;
import com.nextcloud.client.files.downloader.FilesDownloadHelper;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.files.services.FileDownloader;
import com.owncloud.android.files.services.FileUploader;
import com.owncloud.android.files.services.NameCollisionPolicy;
import com.owncloud.android.lib.common.OwnCloudClient;
@ -317,11 +316,17 @@ public class SynchronizeFileOperation extends SyncOperation {
* @param file OCFile object representing the file to download
*/
private void requestForDownload(OCFile file) {
Intent i = new Intent(mContext, FileDownloader.class);
i.putExtra(FileDownloader.EXTRA_USER, mUser);
i.putExtra(FileDownloader.EXTRA_FILE, file);
FilesDownloadHelper downloadHelper = new FilesDownloadHelper();
downloadHelper.downloadFile(
mUser,
file,
"",
null,
"",
"",
null);
new FileDownloader(i);
mTransferWasRequested = true;
}

View file

@ -25,7 +25,7 @@ import android.content.Intent;
import android.text.TextUtils;
import com.nextcloud.client.account.User;
import com.nextcloud.client.files.downloader.FileDownloadHelper;
import com.nextcloud.client.files.downloader.FilesDownloadHelper;
import com.owncloud.android.datamodel.DecryptedFolderMetadata;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
@ -438,16 +438,32 @@ public class SynchronizeFolderOperation extends SyncOperation {
}
}
private void syncContents() throws OperationCancelledException {
startDirectDownloads();
startContentSynchronizations(mFilesToSyncContents);
}
private void startDirectDownloads() {
FileDownloadHelper.Companion.instance().downloadFolder(mLocalFolder,
private void startDirectDownloads() throws OperationCancelledException {
for (OCFile file : mFilesForDirectDownload) {
synchronized(mCancellationRequested) {
if (mCancellationRequested.get()) {
throw new OperationCancelledException();
}
FilesDownloadHelper downloadHelper = new FilesDownloadHelper();
downloadHelper.downloadFile(
user,
mFilesForDirectDownload);
file,
"",
null,
"",
"",
null);
}
}
}
/**

View file

@ -307,7 +307,7 @@ public class DocumentsStorageProvider extends DocumentsProvider {
/**
* Updates the OC File after a successful download.
*
* TODO unify with code from {@link com.nextcloud.client.files.downloader.FileDownloadWorker} and {@link DownloadTask}.
* TODO unify with code from {@link com.nextcloud.client.files.downloader.FilesDownloadWorker} and {@link DownloadTask}.
*/
private void saveDownloadedFile(FileDataStorageManager storageManager, DownloadFileOperation dfo, OCFile file) {
long syncDate = System.currentTimeMillis();

View file

@ -28,7 +28,7 @@ import android.os.Message;
import android.util.Pair;
import com.nextcloud.client.account.User;
import com.nextcloud.client.files.downloader.FileDownloadWorker;
import com.nextcloud.client.files.downloader.FilesDownloadWorker;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.files.services.IndexedForest;
import com.owncloud.android.lib.common.OwnCloudAccount;
@ -169,9 +169,9 @@ class SyncFolderHandler extends Handler {
* this is a fast and ugly patch.
*/
private void sendBroadcastNewSyncFolder(Account account, String remotePath) {
Intent added = new Intent(FileDownloadWorker.Companion.getDownloadAddedMessage());
added.putExtra(FileDownloadWorker.EXTRA_ACCOUNT_NAME, account.name);
added.putExtra(FileDownloadWorker.EXTRA_REMOTE_PATH, remotePath);
Intent added = new Intent(FilesDownloadWorker.Companion.getDownloadAddedMessage());
added.putExtra(FilesDownloadWorker.ACCOUNT_NAME, account.name);
added.putExtra(FilesDownloadWorker.EXTRA_REMOTE_PATH, remotePath);
added.setPackage(mService.getPackageName());
LocalBroadcastManager.getInstance(mService.getApplicationContext()).sendBroadcast(added);
}
@ -182,10 +182,10 @@ class SyncFolderHandler extends Handler {
*/
private void sendBroadcastFinishedSyncFolder(Account account, String remotePath,
boolean success) {
Intent finished = new Intent(FileDownloadWorker.Companion.getDownloadFinishMessage());
finished.putExtra(FileDownloadWorker.EXTRA_ACCOUNT_NAME, account.name);
finished.putExtra(FileDownloadWorker.EXTRA_REMOTE_PATH, remotePath);
finished.putExtra(FileDownloadWorker.EXTRA_DOWNLOAD_RESULT, success);
Intent finished = new Intent(FilesDownloadWorker.Companion.getDownloadFinishMessage());
finished.putExtra(FilesDownloadWorker.ACCOUNT_NAME, account.name);
finished.putExtra(FilesDownloadWorker.EXTRA_REMOTE_PATH, remotePath);
finished.putExtra(FilesDownloadWorker.EXTRA_DOWNLOAD_RESULT, success);
finished.setPackage(mService.getPackageName());
LocalBroadcastManager.getInstance(mService.getApplicationContext()).sendBroadcast(finished);
}

View file

@ -20,7 +20,7 @@
package com.owncloud.android.ui.activity;
import com.nextcloud.client.files.downloader.FileDownloadWorker;
import com.nextcloud.client.files.downloader.FilesDownloadWorker;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
@ -30,9 +30,9 @@ public interface ComponentsGetter {
/**
* To be invoked when the parent activity is fully created to get a reference
* to the FileDownloadWorker.
* to the FileDownloader service API.
*/
public FileDownloadWorker.FileDownloadProgressListener getFileDownloadProgressListener();
public FilesDownloadWorker.FileDownloaderBinder getFileDownloaderBinder();
/**

View file

@ -21,13 +21,13 @@ import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import com.nextcloud.client.account.User
import com.nextcloud.client.files.downloader.FilesDownloadHelper
import com.nextcloud.model.HTTPStatusCodes
import com.nextcloud.utils.extensions.getParcelableArgument
import com.owncloud.android.R
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.datamodel.UploadsStorageManager
import com.owncloud.android.db.OCUpload
import com.owncloud.android.files.services.FileDownloader
import com.owncloud.android.files.services.FileUploader
import com.owncloud.android.files.services.NameCollisionPolicy
import com.owncloud.android.lib.common.utils.Log_OC
@ -114,12 +114,17 @@ class ConflictsResolveActivity : FileActivity(), OnConflictDecisionMadeListener
Decision.KEEP_SERVER -> if (!shouldDeleteLocal()) {
// Overwrite local file
val intent = Intent(baseContext, FileDownloader::class.java)
intent.putExtra(FileDownloader.EXTRA_USER, getUser().orElseThrow { RuntimeException() })
intent.putExtra(FileDownloader.EXTRA_FILE, file)
intent.putExtra(EXTRA_CONFLICT_UPLOAD_ID, conflictUploadId)
FileDownloader(intent)
file?.let {
FilesDownloadHelper().downloadFile(
user = getUser().orElseThrow { RuntimeException() },
ocFile = file,
conflictUploadId = conflictUploadId,
behaviour = "",
packageName = "",
activityName = "",
downloadType = null
)
}
} else {
uploadsStorageManager!!.removeUpload(upload)
}

View file

@ -43,6 +43,8 @@ import android.text.TextUtils;
import com.google.android.material.snackbar.Snackbar;
import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.files.downloader.FilesDownloadHelper;
import com.nextcloud.client.files.downloader.FilesDownloadWorker;
import com.nextcloud.client.jobs.BackgroundJobManager;
import com.nextcloud.client.network.ConnectivityService;
import com.nextcloud.utils.EditorUtils;
@ -54,8 +56,6 @@ import com.owncloud.android.authentication.AuthenticatorActivity;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.files.services.FileDownloader;
import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
import com.owncloud.android.files.services.FileUploader;
import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
import com.owncloud.android.lib.common.OwnCloudAccount;
@ -166,7 +166,7 @@ public abstract class FileActivity extends DrawerActivity
private boolean mResumed;
protected FileDownloaderBinder mDownloaderBinder;
protected FilesDownloadWorker.FileDownloaderBinder mDownloaderBinder;
protected FileUploaderBinder mUploaderBinder;
private ServiceConnection mDownloadServiceConnection;
private ServiceConnection mUploadServiceConnection;
@ -206,6 +206,7 @@ public abstract class FileActivity extends DrawerActivity
super.onCreate(savedInstanceState);
mHandler = new Handler();
mFileOperationsHelper = new FileOperationsHelper(this, getUserAccountManager(), connectivityService, editorUtils);
User user = null;
if (savedInstanceState != null) {
mFile = BundleExtensionsKt.getParcelableArgument(savedInstanceState, FileActivity.EXTRA_FILE, OCFile.class);
@ -218,10 +219,11 @@ public abstract class FileActivity extends DrawerActivity
viewThemeUtils.files.themeActionBar(this, actionBar, savedInstanceState.getString(KEY_ACTION_BAR_TITLE));
}
} else {
User user = IntentExtensionsKt.getParcelableArgument(getIntent(), FileActivity.EXTRA_USER, User.class);
user = IntentExtensionsKt.getParcelableArgument(getIntent(), FileActivity.EXTRA_USER, User.class);
mFile = IntentExtensionsKt.getParcelableArgument(getIntent(), FileActivity.EXTRA_FILE, OCFile.class);
mFromNotification = getIntent().getBooleanExtra(FileActivity.EXTRA_FROM_NOTIFICATION,
false);
if (user != null) {
setUser(user);
}
@ -233,8 +235,8 @@ public abstract class FileActivity extends DrawerActivity
Context.BIND_AUTO_CREATE);
mDownloadServiceConnection = newTransferenceServiceConnection();
if (mDownloadServiceConnection != null) {
new FileDownloader(new Intent(this, FileDownloader.class));
if (mDownloadServiceConnection != null && user != null) {
new FilesDownloadHelper().downloadFile(user, mFile, "", null, "", "", null);
}
mUploadServiceConnection = newTransferenceServiceConnection();
if (mUploadServiceConnection != null) {
@ -615,7 +617,7 @@ public abstract class FileActivity extends DrawerActivity
}
@Override
public FileDownloaderBinder getFileDownloaderBinder() {
public FilesDownloadWorker.FileDownloaderBinder getFileDownloaderBinder() {
return mDownloaderBinder;
}

View file

@ -65,16 +65,14 @@ import com.nextcloud.client.core.AsyncRunner;
import com.nextcloud.client.di.Injectable;
import com.nextcloud.client.editimage.EditImageActivity;
import com.nextcloud.client.files.DeepLinkHandler;
import com.nextcloud.client.files.downloader.FileDownloadHelper;
import com.nextcloud.client.files.downloader.FileDownloadWorker;
import com.nextcloud.client.files.downloader.FilesDownloadHelper;
import com.nextcloud.client.files.downloader.FilesDownloadWorker;
import com.nextcloud.client.media.PlayerServiceConnection;
import com.nextcloud.client.network.ClientFactory;
import com.nextcloud.client.network.ConnectivityService;
import com.nextcloud.client.preferences.AppPreferences;
import com.nextcloud.client.utils.IntentUtil;
import com.nextcloud.java.util.Optional;
import com.nextcloud.model.WorkerState;
import com.nextcloud.model.WorkerStateLiveData;
import com.nextcloud.utils.extensions.BundleExtensionsKt;
import com.nextcloud.utils.extensions.IntentExtensionsKt;
import com.nextcloud.utils.view.FastScrollUtils;
@ -96,7 +94,6 @@ import com.owncloud.android.lib.resources.files.RestoreFileVersionRemoteOperatio
import com.owncloud.android.lib.resources.files.SearchRemoteOperation;
import com.owncloud.android.operations.CopyFileOperation;
import com.owncloud.android.operations.CreateFolderOperation;
import com.owncloud.android.operations.DownloadType;
import com.owncloud.android.operations.MoveFileOperation;
import com.owncloud.android.operations.RefreshFolderOperation;
import com.owncloud.android.operations.RemoveFileOperation;
@ -287,7 +284,6 @@ public class FileDisplayActivity extends FileActivity
checkStoragePath();
initSyncBroadcastReceiver();
observeWorkerState();
}
@SuppressWarnings("unchecked")
@ -688,12 +684,12 @@ public class FileDisplayActivity extends FileActivity
// the user browsed to other file ; forget the automatic preview
mWaitingToPreview = null;
} else if (downloadEvent.equals(FileDownloadWorker.Companion.getDownloadAddedMessage())) {
} else if (downloadEvent.equals(FilesDownloadWorker.Companion.getDownloadAddedMessage())) {
// grant that the details fragment updates the progress bar
detailsFragment.listenForTransferProgress();
detailsFragment.updateFileDetails(true, false);
} else if (downloadEvent.equals(FileDownloadWorker.Companion.getDownloadFinishMessage())) {
} else if (downloadEvent.equals(FilesDownloadWorker.Companion.getDownloadFinishMessage())) {
// update the details panel
boolean detailsFragmentChanged = false;
if (waitedPreview) {
@ -1119,8 +1115,8 @@ public class FileDisplayActivity extends FileActivity
localBroadcastManager.registerReceiver(mUploadFinishReceiver, uploadIntentFilter);
// Listen for download messages
IntentFilter downloadIntentFilter = new IntentFilter(FileDownloadWorker.Companion.getDownloadAddedMessage());
downloadIntentFilter.addAction(FileDownloadWorker.Companion.getDownloadFinishMessage());
IntentFilter downloadIntentFilter = new IntentFilter(FilesDownloadWorker.Companion.getDownloadAddedMessage());
downloadIntentFilter.addAction(FilesDownloadWorker.Companion.getDownloadFinishMessage());
mDownloadFinishReceiver = new DownloadFinishReceiver();
localBroadcastManager.registerReceiver(mDownloadFinishReceiver, downloadIntentFilter);
@ -1421,7 +1417,7 @@ public class FileDisplayActivity extends FileActivity
/**
* Class waiting for broadcast events from the {@link FileDownloadWorker} service.
* Class waiting for broadcast events from the {@link FilesDownloadWorker} service.
* <p>
* Updates the UI when a download is started or finished, provided that it is relevant for the current folder.
*/
@ -1430,16 +1426,16 @@ public class FileDisplayActivity extends FileActivity
@Override
public void onReceive(Context context, Intent intent) {
boolean sameAccount = isSameAccount(intent);
String downloadedRemotePath = intent.getStringExtra(FileDownloadWorker.EXTRA_REMOTE_PATH);
String downloadedRemotePath = intent.getStringExtra(FilesDownloadWorker.EXTRA_REMOTE_PATH);
String downloadBehaviour = intent.getStringExtra(OCFileListFragment.DOWNLOAD_BEHAVIOUR);
boolean isDescendant = isDescendant(downloadedRemotePath);
if (sameAccount && isDescendant) {
String linkedToRemotePath = intent.getStringExtra(FileDownloadWorker.EXTRA_LINKED_TO_PATH);
String linkedToRemotePath = intent.getStringExtra(FilesDownloadWorker.EXTRA_LINKED_TO_PATH);
if (linkedToRemotePath == null || isAscendant(linkedToRemotePath)) {
updateListOfFilesFragment(false);
}
refreshDetailsFragmentIfVisible(intent.getAction(), downloadedRemotePath, intent.getBooleanExtra(FileDownloadWorker.EXTRA_DOWNLOAD_RESULT, false));
refreshDetailsFragmentIfVisible(intent.getAction(), downloadedRemotePath, intent.getBooleanExtra(FilesDownloadWorker.EXTRA_DOWNLOAD_RESULT, false));
}
if (mWaitingToSend != null) {
@ -1472,7 +1468,7 @@ public class FileDisplayActivity extends FileActivity
}
private boolean isSameAccount(Intent intent) {
String accountName = intent.getStringExtra(FileDownloadWorker.EXTRA_ACCOUNT_NAME);
String accountName = intent.getStringExtra(FilesDownloadWorker.ACCOUNT_NAME);
return accountName != null && getAccount() != null && accountName.equals(getAccount().name);
}
}
@ -1562,24 +1558,6 @@ public class FileDisplayActivity extends FileActivity
return isRoot(getCurrentDir());
}
private void observeWorkerState() {
WorkerStateLiveData.Companion.instance().observe(this, state -> {
if (state instanceof WorkerState.Download) {
Log_OC.d(TAG, "Download worker started");
handleDownloadWorkerState();
}
});
}
private void handleDownloadWorkerState() {
if (mWaitingToPreview != null && getStorageManager() != null) {
mWaitingToPreview = getStorageManager().getFileById(mWaitingToPreview.getFileId());
if (mWaitingToPreview != null && !mWaitingToPreview.isDown()) {
requestForDownload();
}
}
}
@Override
protected ServiceConnection newTransferenceServiceConnection() {
return new ListServiceConnection();
@ -1592,13 +1570,22 @@ public class FileDisplayActivity extends FileActivity
@Override
public void onServiceConnected(ComponentName component, IBinder service) {
if (component.equals(new ComponentName(FileDisplayActivity.this, FileUploader.class))) {
if (component.equals(new ComponentName(FileDisplayActivity.this, FilesDownloadWorker.class))) {
Log_OC.d(TAG, "Download service connected");
mDownloaderBinder = (FilesDownloadWorker.FileDownloaderBinder) service;
if (mWaitingToPreview != null && getStorageManager() != null) {
// update the file
mWaitingToPreview = getStorageManager().getFileById(mWaitingToPreview.getFileId());
if (mWaitingToPreview != null && !mWaitingToPreview.isDown()) {
requestForDownload();
}
}
} else if (component.equals(new ComponentName(FileDisplayActivity.this, FileUploader.class))) {
Log_OC.d(TAG, "Upload service connected");
mUploaderBinder = (FileUploaderBinder) service;
} else {
return;
}
// a new chance to get the mDownloadBinder through
// getFileDownloadBinder() - THIS IS A MESS
OCFileListFragment listOfFiles = getListOfFilesFragment();
@ -1606,9 +1593,9 @@ public class FileDisplayActivity extends FileActivity
IntentExtensionsKt.getParcelableArgument(getIntent(), EXTRA_FILE, OCFile.class) == null))) {
listOfFiles.listDirectory(MainApp.isOnlyOnDevice(), false);
}
Fragment leftFragment = getLeftFragment();
if (leftFragment instanceof FileDetailFragment detailFragment) {
if (leftFragment instanceof FileDetailFragment) {
FileDetailFragment detailFragment = (FileDetailFragment) leftFragment;
detailFragment.listenForTransferProgress();
detailFragment.updateFileDetails(false, false);
}
@ -1616,9 +1603,9 @@ public class FileDisplayActivity extends FileActivity
@Override
public void onServiceDisconnected(ComponentName component) {
if (component.equals(new ComponentName(FileDisplayActivity.this, FileDownloadWorker.class))) {
if (component.equals(new ComponentName(FileDisplayActivity.this, FilesDownloadWorker.class))) {
Log_OC.d(TAG, "Download service disconnected");
fileDownloadProgressListener = null;
mDownloaderBinder = null;
} else if (component.equals(new ComponentName(FileDisplayActivity.this, FileUploader.class))) {
Log_OC.d(TAG, "Upload service disconnected");
mUploaderBinder = null;
@ -1896,7 +1883,10 @@ public class FileDisplayActivity extends FileActivity
private void requestForDownload() {
User user = getUser().orElseThrow(RuntimeException::new);
FileDownloadHelper.Companion.instance().downloadFileIfNotStartedBefore(user, mWaitingToPreview);
//if (!mWaitingToPreview.isDownloading()) {
if (!mDownloaderBinder.isDownloading(user, mWaitingToPreview)) {
new FilesDownloadHelper().downloadFile(user, mWaitingToPreview, "", null, "", "", null);
}
}
@Override
@ -1966,8 +1956,8 @@ public class FileDisplayActivity extends FileActivity
private void requestForDownload(OCFile file, String downloadBehaviour, String packageName, String activityName) {
final User currentUser = getUser().orElseThrow(RuntimeException::new);
if (!FileDownloadHelper.Companion.instance().isDownloading(currentUser, file)) {
FileDownloadHelper.Companion.instance().downloadFile(currentUser, file, downloadBehaviour, DownloadType.DOWNLOAD, activityName, packageName, null);
if (!mDownloaderBinder.isDownloading(currentUser, mWaitingToPreview)) {
new FilesDownloadHelper().downloadFile(currentUser, file, downloadBehaviour, null, activityName, packageName, null);
}
}

View file

@ -41,12 +41,10 @@ import android.view.View;
import com.google.common.collect.Sets;
import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.files.downloader.FileDownloadHelper;
import com.nextcloud.client.files.downloader.FilesDownloadWorker;
import com.nextcloud.client.jobs.BackgroundJobManager;
import com.nextcloud.client.onboarding.FirstRunActivity;
import com.nextcloud.java.util.Optional;
import com.nextcloud.model.WorkerState;
import com.nextcloud.model.WorkerStateLiveData;
import com.nextcloud.utils.extensions.BundleExtensionsKt;
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
@ -58,7 +56,6 @@ import com.owncloud.android.files.services.FileUploader;
import com.owncloud.android.lib.common.OwnCloudAccount;
import com.owncloud.android.lib.common.UserInfo;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.operations.DownloadFileOperation;
import com.owncloud.android.services.OperationsService;
import com.owncloud.android.ui.adapter.UserListAdapter;
import com.owncloud.android.ui.adapter.UserListItem;
@ -108,6 +105,7 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
private final Handler handler = new Handler();
private String accountName;
private UserListAdapter userListAdapter;
private ServiceConnection downloadServiceConnection;
private ServiceConnection uploadServiceConnection;
private Set<String> originalUsers;
private String originalCurrentUser;
@ -115,9 +113,6 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
private ArbitraryDataProvider arbitraryDataProvider;
private boolean multipleAccountsSupported;
private String workerAccountName;
private DownloadFileOperation workerCurrentDownload;
@Inject BackgroundJobManager backgroundJobManager;
@Inject UserAccountManager accountManager;
@ -165,7 +160,6 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
recyclerView.setAdapter(userListAdapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
initializeComponentGetters();
observeWorkerState();
}
@ -247,6 +241,11 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
* Initialize ComponentsGetters.
*/
private void initializeComponentGetters() {
downloadServiceConnection = newTransferenceServiceConnection();
if (downloadServiceConnection != null) {
// FIXME check this usage
// bindService(new Intent(this, FileDownloader.class), downloadServiceConnection, Context.BIND_AUTO_CREATE);
}
uploadServiceConnection = newTransferenceServiceConnection();
if (uploadServiceConnection != null) {
bindService(new Intent(this, FileUploader.class), uploadServiceConnection,
@ -341,8 +340,9 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
if (mUploaderBinder != null) {
mUploaderBinder.cancel(accountName);
}
FileDownloadHelper.Companion.instance().cancelAllDownloadsForAccount(workerAccountName, workerCurrentDownload);
if (mDownloaderBinder != null) {
mDownloaderBinder.cancel(accountName);
}
}
User currentUser = getUserAccountManager().getUser();
@ -374,6 +374,10 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
@Override
protected void onDestroy() {
if (downloadServiceConnection != null) {
unbindService(downloadServiceConnection);
downloadServiceConnection = null;
}
if (uploadServiceConnection != null) {
unbindService(uploadServiceConnection);
uploadServiceConnection = null;
@ -431,8 +435,9 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
if (mUploaderBinder != null) {
mUploaderBinder.cancel(user);
}
FileDownloadHelper.Companion.instance().cancelAllDownloadsForAccount(workerAccountName, workerCurrentDownload);
if (mDownloaderBinder != null) {
mDownloaderBinder.cancel(user.getAccountName());
}
backgroundJobManager.startAccountRemovalJob(user.getAccountName(), false);
@ -517,16 +522,6 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
}
}
private void observeWorkerState() {
WorkerStateLiveData.Companion.instance().observe(this, state -> {
if (state instanceof WorkerState.Download) {
Log_OC.d(TAG, "Download worker started");
workerAccountName = ((WorkerState.Download) state).getUser().getAccountName();
workerCurrentDownload = ((WorkerState.Download) state).getCurrentDownload();
}
});
}
@Override
public void onAccountClicked(User user) {
openAccount(user);
@ -539,7 +534,11 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
@Override
public void onServiceConnected(ComponentName component, IBinder service) {
if (component.equals(new ComponentName(ManageAccountsActivity.this, FileUploader.class))) {
if (component.equals(new ComponentName(ManageAccountsActivity.this, FilesDownloadWorker.class))) {
mDownloaderBinder = (FilesDownloadWorker.FileDownloaderBinder) service;
} else if (component.equals(new ComponentName(ManageAccountsActivity.this, FileUploader.class))) {
Log_OC.d(TAG, "Upload service connected");
mUploaderBinder = (FileUploader.FileUploaderBinder) service;
}
@ -547,7 +546,10 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
@Override
public void onServiceDisconnected(ComponentName component) {
if (component.equals(new ComponentName(ManageAccountsActivity.this, FileUploader.class))) {
if (component.equals(new ComponentName(ManageAccountsActivity.this, FilesDownloadWorker.class))) {
Log_OC.d(TAG, "Download service suddenly disconnected");
mDownloaderBinder = null;
} else if (component.equals(new ComponentName(ManageAccountsActivity.this, FileUploader.class))) {
Log_OC.d(TAG, "Upload service suddenly disconnected");
mUploaderBinder = null;
}

View file

@ -44,7 +44,7 @@ import com.google.android.material.tabs.TabLayout;
import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.di.Injectable;
import com.nextcloud.client.files.downloader.FileDownloadHelper;
import com.nextcloud.client.files.downloader.FilesDownloadWorker;
import com.nextcloud.client.jobs.BackgroundJobManager;
import com.nextcloud.client.network.ClientFactory;
import com.nextcloud.client.network.ConnectivityService;
@ -502,7 +502,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
* TODO Remove parameter when the transferring state of files is kept in database.
*
* @param transferring Flag signaling if the file should be considered as downloading or uploading, although
* {@link FileDownloadHelper#isDownloading(User, OCFile)} and
* {@link com.nextcloud.client.files.downloader.FilesDownloadWorker.FileDownloaderBinder#isDownloading(User, OCFile)} and
* {@link FileUploaderBinder#isUploading(User, OCFile)} return false.
* @param refresh If 'true', try to refresh the whole file from the database
*/
@ -534,9 +534,10 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
setFavoriteIconStatus(file.isFavorite());
// configure UI for depending upon local state of the file
FilesDownloadWorker.FileDownloaderBinder downloaderBinder = containerActivity.getFileDownloaderBinder();
FileUploaderBinder uploaderBinder = containerActivity.getFileUploaderBinder();
if (transferring
|| (FileDownloadHelper.Companion.instance().isDownloading(user, file))
|| (downloaderBinder != null && downloaderBinder.isDownloading(user, file))
|| (uploaderBinder != null && uploaderBinder.isUploading(user, file))) {
setButtonsForTransferring();
@ -658,8 +659,10 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
// show the progress bar for the transfer
binding.progressBlock.setVisibility(View.VISIBLE);
binding.progressText.setVisibility(View.VISIBLE);
FilesDownloadWorker.FileDownloaderBinder downloaderBinder = containerActivity.getFileDownloaderBinder();
FileUploaderBinder uploaderBinder = containerActivity.getFileUploaderBinder();
if (FileDownloadHelper.Companion.instance().isDownloading(user, getFile())) {
//if (getFile().isDownloading()) {
if (downloaderBinder != null && downloaderBinder.isDownloading(user, getFile())) {
binding.progressText.setText(R.string.downloader_download_in_progress_ticker);
} else {
if (uploaderBinder != null && uploaderBinder.isUploading(user, getFile())) {
@ -691,9 +694,9 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
public void listenForTransferProgress() {
if (progressListener != null) {
if (containerActivity.getFileDownloadProgressListener() != null) {
containerActivity.getFileDownloadProgressListener().
addDataTransferProgressListener(progressListener, getFile());
if (containerActivity.getFileDownloaderBinder() != null) {
containerActivity.getFileDownloaderBinder().
addDatatransferProgressListener(progressListener, getFile());
}
if (containerActivity.getFileUploaderBinder() != null) {
containerActivity.getFileUploaderBinder().
@ -706,9 +709,9 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
private void leaveTransferProgress() {
if (progressListener != null) {
if (containerActivity.getFileDownloadProgressListener() != null) {
containerActivity.getFileDownloadProgressListener().
removeDataTransferProgressListener(progressListener, getFile());
if (containerActivity.getFileDownloaderBinder() != null) {
containerActivity.getFileDownloaderBinder().
removeDatatransferProgressListener(progressListener, getFile());
}
if (containerActivity.getFileUploaderBinder() != null) {
containerActivity.getFileUploaderBinder().

View file

@ -47,7 +47,7 @@ import android.webkit.MimeTypeMap;
import com.nextcloud.client.account.CurrentAccountProvider;
import com.nextcloud.client.account.User;
import com.nextcloud.client.files.downloader.FileDownloadHelper;
import com.nextcloud.client.files.downloader.FilesDownloadWorker;
import com.nextcloud.client.jobs.BackgroundJobManager;
import com.nextcloud.client.network.ConnectivityService;
import com.nextcloud.java.util.Optional;
@ -996,10 +996,11 @@ public class FileOperationsHelper {
}
}
if (FileDownloadHelper.Companion.instance().isDownloading(currentUser, file)) {
FileDownloadHelper.Companion.instance().cancelPendingOrCurrentDownloads(currentUser, file);
// for both files and folders
FilesDownloadWorker.FileDownloaderBinder downloaderBinder = fileActivity.getFileDownloaderBinder();
if (downloaderBinder != null && downloaderBinder.isDownloading(currentUser, file)) {
downloaderBinder.cancel(currentUser.toPlatformAccount(), file);
}
FileUploaderBinder uploaderBinder = fileActivity.getFileUploaderBinder();
if (uploaderBinder != null && uploaderBinder.isUploading(currentUser, file)) {
uploaderBinder.cancel(currentUser.toPlatformAccount(), file);

View file

@ -37,6 +37,8 @@ import android.view.View;
import com.nextcloud.client.account.User;
import com.nextcloud.client.di.Injectable;
import com.nextcloud.client.editimage.EditImageActivity;
import com.nextcloud.client.files.downloader.FilesDownloadHelper;
import com.nextcloud.client.files.downloader.FilesDownloadWorker;
import com.nextcloud.client.preferences.AppPreferences;
import com.nextcloud.java.util.Optional;
import com.nextcloud.utils.extensions.IntentExtensionsKt;
@ -45,8 +47,6 @@ import com.owncloud.android.R;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.datamodel.VirtualFolderType;
import com.owncloud.android.files.services.FileDownloader;
import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
import com.owncloud.android.files.services.FileUploader;
import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
import com.owncloud.android.lib.common.operations.OnRemoteOperationListener;
@ -311,8 +311,8 @@ public class PreviewImageActivity extends FileActivity implements
public void onServiceConnected(ComponentName component, IBinder service) {
if (component.equals(new ComponentName(PreviewImageActivity.this,
FileDownloader.class))) {
mDownloaderBinder = (FileDownloaderBinder) service;
FilesDownloadWorker.class))) {
mDownloaderBinder = (FilesDownloadWorker.FileDownloaderBinder) service;
if (mRequestWaitingForBinder) {
mRequestWaitingForBinder = false;
Log_OC.d(TAG, "Simulating reselection of current page after connection " +
@ -331,7 +331,7 @@ public class PreviewImageActivity extends FileActivity implements
@Override
public void onServiceDisconnected(ComponentName component) {
if (component.equals(new ComponentName(PreviewImageActivity.this,
FileDownloader.class))) {
FilesDownloadWorker.class))) {
Log_OC.d(TAG, "Download service suddenly disconnected");
mDownloaderBinder = null;
} else if (component.equals(new ComponentName(PreviewImageActivity.this,
@ -359,7 +359,7 @@ public class PreviewImageActivity extends FileActivity implements
super.onResume();
mDownloadFinishReceiver = new DownloadFinishReceiver();
IntentFilter downloadIntentFilter = new IntentFilter(FileDownloader.getDownloadFinishMessage());
IntentFilter downloadIntentFilter = new IntentFilter(FilesDownloadWorker.Companion.getDownloadFinishMessage());
localBroadcastManager.registerReceiver(mDownloadFinishReceiver, downloadIntentFilter);
mUploadFinishReceiver = new UploadFinishReceiver();
@ -413,13 +413,7 @@ public class PreviewImageActivity extends FileActivity implements
} else if (!mDownloaderBinder.isDownloading(getUserAccountManager().getUser(), file)) {
final User user = getUser().orElseThrow(RuntimeException::new);
Intent i = new Intent(this, FileDownloader.class);
i.putExtra(FileDownloader.EXTRA_USER, user);
i.putExtra(FileDownloader.EXTRA_FILE, file);
if (downloadBehaviour != null) {
i.putExtra(OCFileListFragment.DOWNLOAD_BEHAVIOUR, downloadBehaviour);
}
new FileDownloader(i);
new FilesDownloadHelper().downloadFile(user, file, downloadBehaviour, null, "", "", null);
}
}
@ -484,7 +478,7 @@ public class PreviewImageActivity extends FileActivity implements
}
/**
* Class waiting for broadcast events from the {@link FileDownloader} service.
* Class waiting for broadcast events from the {@link FilesDownloadWorker} service.
*
* Updates the UI when a download is started or finished, provided that it is relevant for the
* folder displayed in the gallery.
@ -504,12 +498,12 @@ public class PreviewImageActivity extends FileActivity implements
}
private void previewNewImage(Intent intent) {
String accountName = intent.getStringExtra(FileDownloader.ACCOUNT_NAME);
String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH);
String accountName = intent.getStringExtra(FilesDownloadWorker.ACCOUNT_NAME);
String downloadedRemotePath = intent.getStringExtra(FilesDownloadWorker.EXTRA_REMOTE_PATH);
String downloadBehaviour = intent.getStringExtra(OCFileListFragment.DOWNLOAD_BEHAVIOUR);
if (getAccount().name.equals(accountName) && downloadedRemotePath != null) {
OCFile file = getStorageManager().getFileByPath(downloadedRemotePath);
boolean downloadWasFine = intent.getBooleanExtra(FileDownloader.EXTRA_DOWNLOAD_RESULT, false);
boolean downloadWasFine = intent.getBooleanExtra(FilesDownloadWorker.EXTRA_DOWNLOAD_RESULT, false);
if (EditImageActivity.OPEN_IMAGE_EDITOR.equals(downloadBehaviour)) {
startImageEditor(file);

View file

@ -53,6 +53,7 @@ import android.view.ViewGroup;
import com.nextcloud.client.account.User;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.di.Injectable;
import com.nextcloud.client.files.downloader.FilesDownloadHelper;
import com.nextcloud.client.jobs.BackgroundJobManager;
import com.nextcloud.client.media.ExoplayerListener;
import com.nextcloud.client.media.NextcloudExoPlayer;
@ -66,7 +67,6 @@ import com.owncloud.android.databinding.FragmentPreviewMediaBinding;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.datamodel.ThumbnailsCacheManager;
import com.owncloud.android.files.StreamMediaFileOperation;
import com.owncloud.android.files.services.FileDownloader;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.utils.Log_OC;
@ -479,10 +479,7 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
backgroundJobManager);
} else if (itemId == R.id.action_download_file) {
if (!containerActivity.getFileDownloaderBinder().isDownloading(user, getFile())) {
Intent i = new Intent(requireActivity(), FileDownloader.class);
i.putExtra(FileDownloader.EXTRA_USER, user);
i.putExtra(FileDownloader.EXTRA_FILE, getFile());
new FileDownloader(i);
new FilesDownloadHelper().downloadFile(user, getFile(), "", null, "", "", null);
}
}
}