/*
 * Nextcloud - Android Client
 *
 * SPDX-FileCopyrightText: 2025 Alper Ozturk <alper.ozturk@nextcloud.com>
 * SPDX-FileCopyrightText: 2024 Jonas Mayer <jonas.a.mayer@gmx.net>
 * SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
 * SPDX-FileCopyrightText: 2018-2020 Tobias Kaminsky <tobias@kaminsky.me>
 * SPDX-FileCopyrightText: 2019-2021 Chris Narkiewicz <hello@ezaquarii.com>
 * SPDX-FileCopyrightText: 2016-2020 Andy Scherzinger <info@andy-scherzinger.de>
 * SPDX-FileCopyrightText: 2016 ownCloud Inc.
 * SPDX-FileCopyrightText: 2016 María Asensio Valverde <masensio@solidgear.es>
 * SPDX-FileCopyrightText: 2016 David A. Velasco <dvelasco@solidgear.es>
 * SPDX-FileCopyrightText: 2014 Luke Owncloud <owncloud@ohrt.org>
 * SPDX-License-Identifier: GPL-2.0-only AND (AGPL-3.0-or-later OR GPL-2.0-only)
 */
package com.owncloud.android.datamodel;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;

import com.nextcloud.client.account.CurrentAccountProvider;
import com.nextcloud.client.account.User;
import com.nextcloud.client.database.NextcloudDatabase;
import com.nextcloud.client.database.dao.UploadDao;
import com.nextcloud.client.database.entity.UploadEntity;
import com.nextcloud.client.database.entity.UploadEntityKt;
import com.nextcloud.client.jobs.upload.FileUploadHelper;
import com.nextcloud.client.jobs.upload.FileUploadWorker;
import com.nextcloud.utils.autoRename.AutoRename;
import com.owncloud.android.MainApp;
import com.owncloud.android.db.OCUpload;
import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
import com.owncloud.android.db.UploadResult;
import com.owncloud.android.files.services.NameCollisionPolicy;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.status.OCCapability;
import com.owncloud.android.operations.UploadFileOperation;
import com.owncloud.android.utils.theme.CapabilityUtils;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
import java.util.Observable;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

/**
 * Database helper for storing list of files to be uploaded, including status information for each file.
 */
public class UploadsStorageManager extends Observable {
    private static final String TAG = UploadsStorageManager.class.getSimpleName();

    private static final String IS_EQUAL =  "== ?";
    private static final String EQUAL =  "==";
    private static final String OR =  " OR ";
    private static final String AND = " AND ";
    private static final String ANGLE_BRACKETS = "<>";
    private static final int SINGLE_RESULT = 1;

    private static final long QUERY_PAGE_SIZE = 100;

    private final ContentResolver contentResolver;
    private final CurrentAccountProvider currentAccountProvider;
    private OCCapability capability;
    public final UploadDao uploadDao = NextcloudDatabase.getInstance(MainApp.getAppContext()).uploadDao();

    public UploadsStorageManager(
        CurrentAccountProvider currentAccountProvider,
        ContentResolver contentResolver
                                ) {
        if (contentResolver == null) {
            throw new IllegalArgumentException("Cannot create an instance with a NULL contentResolver");
        }
        this.contentResolver = contentResolver;
        this.currentAccountProvider = currentAccountProvider;
    }

    private void initOCCapability() {
        try {
            this.capability = CapabilityUtils.getCapability(MainApp.getAppContext());
        } catch (RuntimeException e) {
            Log_OC.e(TAG,"Failed to set OCCapability: Dependencies are not yet ready.");
        }
    }

    /**
     * Update an upload object in DB.
     *
     * @param ocUpload Upload object with state to update
     * @return num of updated uploads.
     */
    public synchronized int updateUpload(OCUpload ocUpload) {
        Log_OC.v(TAG, "Updating " + ocUpload.getLocalPath() + " with status=" + ocUpload.getUploadStatus());

        OCUpload existingUpload = getUploadById(ocUpload.getUploadId());
        if (existingUpload == null) {
            Log_OC.e(TAG, "Upload not found for ID: " + ocUpload.getUploadId());
            return 0;
        }

        if (!existingUpload.getAccountName().equals(ocUpload.getAccountName())) {
            Log_OC.e(TAG, "Account mismatch for upload ID " + ocUpload.getUploadId() +
                ": expected " + existingUpload.getAccountName() +
                ", got " + ocUpload.getAccountName());
            return 0;
        }

        ContentValues cv = new ContentValues();
        cv.put(ProviderTableMeta.UPLOADS_LOCAL_PATH, ocUpload.getLocalPath());
        cv.put(ProviderTableMeta.UPLOADS_REMOTE_PATH, ocUpload.getRemotePath());
        cv.put(ProviderTableMeta.UPLOADS_ACCOUNT_NAME, ocUpload.getAccountName());
        cv.put(ProviderTableMeta.UPLOADS_STATUS, ocUpload.getUploadStatus().value);
        cv.put(ProviderTableMeta.UPLOADS_LAST_RESULT, ocUpload.getLastResult().getValue());
        cv.put(ProviderTableMeta.UPLOADS_UPLOAD_END_TIMESTAMP, ocUpload.getUploadEndTimestamp());
        cv.put(ProviderTableMeta.UPLOADS_FILE_SIZE, ocUpload.getFileSize());
        cv.put(ProviderTableMeta.UPLOADS_FOLDER_UNLOCK_TOKEN, ocUpload.getFolderUnlockToken());

        int result = getDB().update(ProviderTableMeta.CONTENT_URI_UPLOADS,
                                    cv,
                                    ProviderTableMeta._ID + "=? AND " + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "=?",
                                    new String[]{String.valueOf(ocUpload.getUploadId()), ocUpload.getAccountName()}
                                   );

        Log_OC.d(TAG, "updateUpload returns with: " + result + " for file: " + ocUpload.getLocalPath());
        if (result != SINGLE_RESULT) {
            Log_OC.e(TAG, "Failed to update item " + ocUpload.getLocalPath() + " into upload db.");
        } else {
            notifyObserversNow();
        }

        return result;
    }

    private int updateUploadInternal(Cursor c, UploadStatus status, UploadResult result, String remotePath,
                                     String localPath) {

        int r = 0;
        while (c.moveToNext()) {
            // read upload object and update
            OCUpload upload = createOCUploadFromCursor(c);

            String path = c.getString(c.getColumnIndexOrThrow(ProviderTableMeta.UPLOADS_LOCAL_PATH));
            Log_OC.v(
                TAG,
                "Updating " + path + " with status:" + status + " and result:"
                    + (result == null ? "null" : result.toString()) + " (old:"
                    + upload.toFormattedString() + ')');

            upload.setUploadStatus(status);
            upload.setLastResult(result);
            upload.setRemotePath(remotePath);
            if (localPath != null) {
                upload.setLocalPath(localPath);
            }
            if (status == UploadStatus.UPLOAD_SUCCEEDED) {
                upload.setUploadEndTimestamp(Calendar.getInstance().getTimeInMillis());
            }

            // store update upload object to db
            r = updateUpload(upload);

        }

        return r;
    }

    /**
     * Update upload status of file uniquely referenced by id.
     *
     * @param id         upload id.
     * @param status     new status.
     * @param result     new result of upload operation
     * @param remotePath path of the file to upload in the ownCloud storage
     * @param localPath  path of the file to upload in the device storage
     * @return 1 if file status was updated, else 0.
     */
    private void updateUploadStatus(long id, UploadStatus status, UploadResult result, String remotePath,
                                   String localPath) {
        //Log_OC.v(TAG, "Updating "+filepath+" with uploadStatus="+status +" and result="+result);

        Cursor c = getDB().query(
            ProviderTableMeta.CONTENT_URI_UPLOADS,
            null,
            ProviderTableMeta._ID + "=?",
            new String[]{String.valueOf(id)},
            null
                                );

        if (c != null) {
            if (c.getCount() != SINGLE_RESULT) {
                Log_OC.e(TAG, c.getCount() + " items for id=" + id
                    + " available in UploadDb. Expected 1. Failed to update upload db.");
            } else {
                updateUploadInternal(c, status, result, remotePath, localPath);
            }
            c.close();
        } else {
            Log_OC.e(TAG, "Cursor is null");
        }

    }

    /**
     * Should be called when some value of this DB was changed. All observers are informed.
     */
    public void notifyObserversNow() {
        Log_OC.d(TAG, "notifyObserversNow");
        setChanged();
        notifyObservers();
    }

    /**
     * Remove an upload from the uploads list, known its target account and remote path.
     *
     * @param upload Upload instance to remove from persisted storage.
     * @return true when the upload was stored and could be removed.
     */
    public int removeUpload(@Nullable OCUpload upload) {
        if (upload == null) {
            return 0;
        } else {
            return removeUpload(upload.getUploadId());
        }
    }

    /**
     * Remove an upload from the uploads list, known its target account and remote path.
     *
     * @param id to remove from persisted storage.
     * @return true when the upload was stored and could be removed.
     */
    public int removeUpload(long id) {
        int result = getDB().delete(
            ProviderTableMeta.CONTENT_URI_UPLOADS,
            ProviderTableMeta._ID + "=?",
            new String[]{Long.toString(id)}
                                   );
        Log_OC.d(TAG, "delete returns " + result + " for upload with id " + id);
        if (result > 0) {
            notifyObserversNow();
        }
        return result;
    }

    /**
     * Remove an upload from the uploads list, known its target account and remote path.
     *
     * @param accountName Name of the OC account target of the upload to remove.
     * @param remotePath  Absolute path in the OC account target of the upload to remove.
     * @return true when one or more upload entries were removed
     */
    public int removeUpload(String accountName, String remotePath) {
        int result = getDB().delete(
            ProviderTableMeta.CONTENT_URI_UPLOADS,
            ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "=? AND " + ProviderTableMeta.UPLOADS_REMOTE_PATH + "=?",
            new String[]{accountName, remotePath}
                                   );
        Log_OC.d(TAG, "delete returns " + result + " for file " + remotePath + " in " + accountName);
        if (result > 0) {
            notifyObserversNow();
        }
        return result;
    }

    /**
     * Remove all the uploads of a given account from the uploads list.
     *
     * @param accountName Name of the OC account target of the uploads to remove.
     * @return true when one or more upload entries were removed
     */
    public int removeUploads(String accountName) {
        int result = getDB().delete(
            ProviderTableMeta.CONTENT_URI_UPLOADS,
            ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "=?",
            new String[]{accountName}
                                   );
        Log_OC.d(TAG, "delete returns " + result + " for uploads in " + accountName);
        if (result > 0) {
            notifyObserversNow();
        }
        return result;
    }

    public OCUpload[] getAllStoredUploads() {
        return getUploads(null, (String[]) null);
    }

    public @Nullable
    OCUpload getUploadById(long id) {
        OCUpload result = null;
        Cursor cursor = getDB().query(
            ProviderTableMeta.CONTENT_URI_UPLOADS,
            null,
            ProviderTableMeta._ID + "=?",
            new String[]{Long.toString(id)},
            "_id ASC");

        if (cursor != null) {
            if (cursor.moveToFirst()) {
                result = createOCUploadFromCursor(cursor);
            }
        }
        Log_OC.d(TAG, "Retrieve job " + result + " for id " + id);
        return result;
    }

    public List<OCUpload> getUploadsByIds(long[] uploadIds, String accountName) {
        final List<OCUpload> result = new ArrayList<>();

        final List<UploadEntity> entities = uploadDao.getUploadsByIds(uploadIds, accountName);
        entities.forEach(uploadEntity -> {
            OCUpload ocUpload = createOCUploadFromEntity(uploadEntity);
            if (ocUpload != null) {
                result.add(ocUpload);
            }
        });

        return result;
    }

    private OCUpload[] getUploads(@Nullable String selection, @Nullable String... selectionArgs) {
        final List<OCUpload> uploads = new ArrayList<>();
        long page = 0;
        long rowsRead;
        long rowsTotal = 0;
        long lastRowID = -1;

        do {
            final List<OCUpload> uploadsPage = getUploadPage(lastRowID, selection, selectionArgs);
            rowsRead = uploadsPage.size();
            rowsTotal += rowsRead;
            if (!uploadsPage.isEmpty()) {
                lastRowID = uploadsPage.get(uploadsPage.size() - 1).getUploadId();
            }
            Log_OC.v(TAG, String.format(Locale.ENGLISH,
                                        "getUploads() got %d rows from page %d, %d rows total so far, last ID %d",
                                        rowsRead,
                                        page,
                                        rowsTotal,
                                        lastRowID
                                       ));
            uploads.addAll(uploadsPage);
            page++;
        } while (rowsRead > 0);


        Log_OC.v(TAG, String.format(Locale.ENGLISH,
                                    "getUploads() returning %d (%d) rows after reading %d pages",
                                    rowsTotal,
                                    uploads.size(),
                                    page
                                   ));


        return uploads.toArray(new OCUpload[0]);
    }

    @NonNull
    private List<OCUpload> getUploadPage(final long afterId, @Nullable String selection, @Nullable String... selectionArgs) {
        return getUploadPage(QUERY_PAGE_SIZE, afterId, true, selection, selectionArgs);
    }

    @NonNull
    private List<OCUpload> getUploadPage(long limit, final long afterId, final boolean descending, @Nullable String selection, @Nullable String... selectionArgs) {
        List<OCUpload> uploads = new ArrayList<>();
        String pageSelection = selection;
        String[] pageSelectionArgs = selectionArgs;

        String idComparator;
        String sortDirection;
        if (descending) {
            sortDirection = "DESC";
            idComparator = "<";
        } else {
            sortDirection = "ASC";
            idComparator = ">";
        }

        if (afterId >= 0) {
            if (selection != null) {
                pageSelection = "(" + selection + ") AND _id " + idComparator + " ?";
            } else {
                pageSelection = "_id " + idComparator + " ?";
            }
            if (selectionArgs != null) {
                pageSelectionArgs = Arrays.copyOf(selectionArgs, selectionArgs.length + 1);
            } else {
                pageSelectionArgs = new String[1];
            }
            pageSelectionArgs[pageSelectionArgs.length - 1] = String.valueOf(afterId);
            Log_OC.d(TAG, String.format(Locale.ENGLISH, "QUERY: %s ROWID: %d", pageSelection, afterId));
        } else {
            Log_OC.d(TAG, String.format(Locale.ENGLISH, "QUERY: %s ROWID: %d", selection, afterId));
        }

        String sortOrder;
        if (limit > 0) {
            sortOrder = String.format(Locale.ENGLISH, "_id " + sortDirection + " LIMIT %d", limit);
        } else {
            sortOrder = String.format(Locale.ENGLISH, "_id " + sortDirection);
        }

        Cursor c = getDB().query(
            ProviderTableMeta.CONTENT_URI_UPLOADS,
            null,
            pageSelection,
            pageSelectionArgs,
            sortOrder);

        if (c != null) {
            if (c.moveToFirst()) {
                do {
                    OCUpload upload = createOCUploadFromCursor(c);
                    if (upload == null) {
                        Log_OC.e(TAG, "OCUpload could not be created from cursor");
                    } else {
                        uploads.add(upload);
                    }
                } while (c.moveToNext() && !c.isAfterLast());
            }
            c.close();
        }
        return uploads;
    }

    @Nullable
    private OCUpload createOCUploadFromEntity(UploadEntity entity) {
        if (entity == null) {
            return null;
        }
        initOCCapability();
        return UploadEntityKt.toOCUpload(entity, capability);
    }

    private OCUpload createOCUploadFromCursor(Cursor c) {
        initOCCapability();

        OCUpload upload = null;
        if (c != null) {
            String localPath = c.getString(c.getColumnIndexOrThrow(ProviderTableMeta.UPLOADS_LOCAL_PATH));

            String remotePath = c.getString(c.getColumnIndexOrThrow(ProviderTableMeta.UPLOADS_REMOTE_PATH));
            if (capability != null) {
                remotePath = AutoRename.INSTANCE.rename(remotePath, capability);
            }

            String accountName = c.getString(c.getColumnIndexOrThrow(ProviderTableMeta.UPLOADS_ACCOUNT_NAME));
            upload = new OCUpload(localPath, remotePath, accountName);

            upload.setFileSize(c.getLong(c.getColumnIndexOrThrow(ProviderTableMeta.UPLOADS_FILE_SIZE)));
            upload.setUploadId(c.getLong(c.getColumnIndexOrThrow(ProviderTableMeta._ID)));
            upload.setUploadStatus(
                UploadStatus.fromValue(c.getInt(c.getColumnIndexOrThrow(ProviderTableMeta.UPLOADS_STATUS)))
                                  );
            upload.setLocalAction(c.getInt(c.getColumnIndexOrThrow(ProviderTableMeta.UPLOADS_LOCAL_BEHAVIOUR)));
            upload.setNameCollisionPolicy(NameCollisionPolicy.deserialize(c.getInt(
                c.getColumnIndexOrThrow(ProviderTableMeta.UPLOADS_NAME_COLLISION_POLICY))));
            upload.setCreateRemoteFolder(c.getInt(
                c.getColumnIndexOrThrow(ProviderTableMeta.UPLOADS_IS_CREATE_REMOTE_FOLDER)) == 1);
            upload.setUploadEndTimestamp(c.getLong(c.getColumnIndexOrThrow(ProviderTableMeta.UPLOADS_UPLOAD_END_TIMESTAMP)));
            upload.setLastResult(UploadResult.fromValue(
                c.getInt(c.getColumnIndexOrThrow(ProviderTableMeta.UPLOADS_LAST_RESULT))));
            upload.setCreatedBy(c.getInt(c.getColumnIndexOrThrow(ProviderTableMeta.UPLOADS_CREATED_BY)));
            upload.setUseWifiOnly(c.getInt(c.getColumnIndexOrThrow(ProviderTableMeta.UPLOADS_IS_WIFI_ONLY)) == 1);
            upload.setWhileChargingOnly(c.getInt(c.getColumnIndexOrThrow(ProviderTableMeta.UPLOADS_IS_WHILE_CHARGING_ONLY))
                                            == 1);
            upload.setFolderUnlockToken(c.getString(c.getColumnIndexOrThrow(ProviderTableMeta.UPLOADS_FOLDER_UNLOCK_TOKEN)));
        }
        return upload;
    }

    public long[] getCurrentUploadIds(final @NonNull String accountName) {
        final var result = uploadDao.getAllIds(UploadStatus.UPLOAD_IN_PROGRESS.value, accountName);
        return result.stream()
            .mapToLong(Integer::longValue)
            .toArray();
    }

    public OCUpload[] getUploadsForAccount(final @NonNull String accountName) {
        return getUploads(ProviderTableMeta.UPLOADS_ACCOUNT_NAME + IS_EQUAL, accountName);
    }

    private ContentResolver getDB() {
        return contentResolver;
    }

    public void clearFailedButNotDelayedUploads() {
        User user = currentAccountProvider.getUser();
        final long deleted = getDB().delete(
            ProviderTableMeta.CONTENT_URI_UPLOADS,
            ProviderTableMeta.UPLOADS_STATUS + EQUAL + UploadStatus.UPLOAD_FAILED.value +
                AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
                ANGLE_BRACKETS + UploadResult.LOCK_FAILED.getValue() +
                AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
                ANGLE_BRACKETS + UploadResult.DELAYED_FOR_WIFI.getValue() +
                AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
                ANGLE_BRACKETS + UploadResult.DELAYED_FOR_CHARGING.getValue() +
                AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
                ANGLE_BRACKETS + UploadResult.DELAYED_IN_POWER_SAVE_MODE.getValue() +
                AND + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + IS_EQUAL,
            new String[]{user.getAccountName()}
                                           );
        Log_OC.d(TAG, "delete all failed uploads but those delayed for Wifi");
        if (deleted > 0) {
            notifyObserversNow();
        }
    }

    public void clearCancelledUploadsForCurrentAccount() {
        User user = currentAccountProvider.getUser();
        final long deleted = getDB().delete(
            ProviderTableMeta.CONTENT_URI_UPLOADS,
            ProviderTableMeta.UPLOADS_STATUS + EQUAL + UploadStatus.UPLOAD_CANCELLED.value + AND +
                ProviderTableMeta.UPLOADS_ACCOUNT_NAME + IS_EQUAL, new String[]{user.getAccountName()}
                                           );

        Log_OC.d(TAG, "delete all cancelled uploads");
        if (deleted > 0) {
            notifyObserversNow();
        }
    }

    public void clearSuccessfulUploads() {
        User user = currentAccountProvider.getUser();
        final long deleted = getDB().delete(
            ProviderTableMeta.CONTENT_URI_UPLOADS,
            ProviderTableMeta.UPLOADS_STATUS + EQUAL + UploadStatus.UPLOAD_SUCCEEDED.value + AND +
                ProviderTableMeta.UPLOADS_ACCOUNT_NAME + IS_EQUAL, new String[]{user.getAccountName()}
                                           );

        Log_OC.d(TAG, "delete all successful uploads");
        if (deleted > 0) {
            notifyObserversNow();
        }
    }

    /**
     * Updates the persistent upload database with upload result.
     */
    public void updateDatabaseUploadResult(RemoteOperationResult uploadResult, UploadFileOperation upload) {
        // result: success or fail notification
        Log_OC.d(TAG, "updateDatabaseUploadResult uploadResult: " + uploadResult + " upload: " + upload);

        if (uploadResult.isCancelled()) {
            removeUpload(
                upload.getUser().getAccountName(),
                upload.getRemotePath()
                        );
        } else {
            String localPath = (FileUploadWorker.LOCAL_BEHAVIOUR_MOVE == upload.getLocalBehaviour())
                ? upload.getStoragePath() : null;

            if (uploadResult.isSuccess()) {
                updateUploadStatus(
                    upload.getOCUploadId(),
                    UploadStatus.UPLOAD_SUCCEEDED,
                    UploadResult.UPLOADED,
                    upload.getRemotePath(),
                    localPath
                                  );
            } else if (uploadResult.getCode() == RemoteOperationResult.ResultCode.SYNC_CONFLICT &&
                new FileUploadHelper().isSameFileOnRemote(
                    upload.getUser(), new File(upload.getStoragePath()), upload.getRemotePath(), upload.getContext())) {

                updateUploadStatus(
                    upload.getOCUploadId(),
                    UploadStatus.UPLOAD_SUCCEEDED,
                    UploadResult.SAME_FILE_CONFLICT,
                    upload.getRemotePath(),
                    localPath
                                  );
            } else if (uploadResult.getCode() == RemoteOperationResult.ResultCode.LOCAL_FILE_NOT_FOUND) {
                updateUploadStatus(
                    upload.getOCUploadId(),
                    UploadStatus.UPLOAD_SUCCEEDED,
                    UploadResult.FILE_NOT_FOUND,
                    upload.getRemotePath(),
                    localPath
                                  );
            } else if (uploadResult.getCode() != RemoteOperationResult.ResultCode.USER_CANCELLED){
                updateUploadStatus(
                    upload.getOCUploadId(),
                    UploadStatus.UPLOAD_FAILED,
                    UploadResult.fromOperationResult(uploadResult),
                    upload.getRemotePath(),
                    localPath
                                  );
            }
        }
    }

    /**
     * Updates the persistent upload database with an upload now in progress.
     */
    public void updateDatabaseUploadStart(UploadFileOperation upload) {
        String localPath = (FileUploadWorker.LOCAL_BEHAVIOUR_MOVE == upload.getLocalBehaviour())
            ? upload.getStoragePath() : null;

        updateUploadStatus(
            upload.getOCUploadId(),
            UploadStatus.UPLOAD_IN_PROGRESS,
            UploadResult.UNKNOWN,
            upload.getRemotePath(),
            localPath
                          );
    }

    @VisibleForTesting
    public void removeAllUploads() {
        Log_OC.v(TAG, "Delete all uploads!");
        getDB().delete(
            ProviderTableMeta.CONTENT_URI_UPLOADS,
            "",
            new String[]{});
    }

    public int removeUserUploads(User user) {
        Log_OC.v(TAG, "Delete all uploads for account " + user.getAccountName());
        return getDB().delete(
            ProviderTableMeta.CONTENT_URI_UPLOADS,
            ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "=?",
            new String[]{user.getAccountName()});
    }

    public enum UploadStatus {

        /**
         * Upload currently in progress or scheduled to be executed.
         */
        UPLOAD_IN_PROGRESS(0),

        /**
         * Last upload failed.
         */
        UPLOAD_FAILED(1),

        /**
         * Upload was successful.
         */
        UPLOAD_SUCCEEDED(2),

        /**
         * Upload was cancelled by the user.
         */
        UPLOAD_CANCELLED(3);

        private final int value;

        UploadStatus(int value) {
            this.value = value;
        }

        public static UploadStatus fromValue(int value) {
            return switch (value) {
                case 0 -> UPLOAD_IN_PROGRESS;
                case 1 -> UPLOAD_FAILED;
                case 2 -> UPLOAD_SUCCEEDED;
                case 3 -> UPLOAD_CANCELLED;
                default -> null;
            };
        }

        public int getValue() {
            return value;
        }

    }
}
