chromium/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/LevelDBPersistedDataStorage.java

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.tab.state;

import androidx.annotation.MainThread;

import org.jni_zero.CalledByNative;
import org.jni_zero.NativeMethods;

import org.chromium.base.Callback;
import org.chromium.base.ResettersForTesting;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.content_public.browser.BrowserContextHandle;

/** Provides key -> byte[] mapping storage with namespace support for PersistedData */
public class LevelDBPersistedDataStorage implements PersistedDataStorage {
    private static boolean sSkipNativeAssertionsForTesting;
    private long mNativePersistedStateDB;
    private String mNamespace;

    /**
     * @param profile corresponding to LevelDBPersistedDataStorage instance
     *        (LevelDBPersistedDataStorage is per-profile)
     * @param namespace unique namespace which will be prepended to all keys
     */
    LevelDBPersistedDataStorage(Profile profile, String namespace) {
        assert !profile.isOffTheRecord()
                : "LevelDBPersistedTabDataStorage is not supported for incognito profiles";
        mNamespace = namespace;
        LevelDBPersistedDataStorageJni.get().init(this, profile);
        makeNativeAssertion();
    }

    @Override
    public void save(String key, byte[] data) {
        makeNativeAssertion();
        LevelDBPersistedDataStorageJni.get()
                .save(mNativePersistedStateDB, getMasterKey(key), data, null);
    }

    private String getMasterKey(String key) {
        return String.format("%s_%s", mNamespace, key);
    }

    @MainThread
    public void saveForTesting(String key, byte[] data, Runnable onComplete) {
        makeNativeAssertion();
        LevelDBPersistedDataStorageJni.get()
                .save(mNativePersistedStateDB, getMasterKey(key), data, onComplete);
    }

    @Override
    public void load(String key, Callback<byte[]> callback) {
        makeNativeAssertion();
        LevelDBPersistedDataStorageJni.get()
                .load(mNativePersistedStateDB, getMasterKey(key), callback);
    }

    @Override
    public void delete(String key) {
        makeNativeAssertion();
        LevelDBPersistedDataStorageJni.get()
                .delete(mNativePersistedStateDB, getMasterKey(key), null);
    }

    @MainThread
    public void deleteForTesting(String key, Runnable onComplete) {
        makeNativeAssertion();
        LevelDBPersistedDataStorageJni.get()
                .delete(mNativePersistedStateDB, getMasterKey(key), onComplete);
    }

    @Override
    public void performMaintenance(String[] keysToKeep, String dataId) {
        makeNativeAssertion();
        LevelDBPersistedDataStorageJni.get()
                .performMaintenance(
                        mNativePersistedStateDB,
                        getMasterKeysToKeep(keysToKeep, dataId),
                        dataId,
                        null);
    }

    protected void performMaintenanceForTesting(
            String[] keysToKeep, String dataId, Runnable onComplete) {
        makeNativeAssertion();
        LevelDBPersistedDataStorageJni.get()
                .performMaintenance(
                        mNativePersistedStateDB,
                        getMasterKeysToKeep(keysToKeep, dataId),
                        dataId,
                        onComplete);
    }

    private String[] getMasterKeysToKeep(String[] keysToKeep, String dataId) {
        String[] masterKeysToKeep = new String[keysToKeep.length];
        for (int i = 0; i < keysToKeep.length; i++) {
            masterKeysToKeep[i] = getMasterKey(keysToKeep[i]);
        }
        return masterKeysToKeep;
    }

    public void destroy() {
        makeNativeAssertion();
        LevelDBPersistedDataStorageJni.get().destroy(mNativePersistedStateDB);
    }

    @CalledByNative
    private void setNativePtr(long nativePtr) {
        if (!sSkipNativeAssertionsForTesting) {
            assert nativePtr != 0;
            assert mNativePersistedStateDB == 0;
        }
        mNativePersistedStateDB = nativePtr;
    }

    private void makeNativeAssertion() {
        if (!sSkipNativeAssertionsForTesting) {
            assert mNativePersistedStateDB != 0;
        }
    }

    public static void setSkipNativeAssertionsForTesting(boolean skipNativeAssertionsForTesting) {
        sSkipNativeAssertionsForTesting = skipNativeAssertionsForTesting;
        ResettersForTesting.register(() -> sSkipNativeAssertionsForTesting = false);
    }

    @NativeMethods
    public interface Natives {
        void init(LevelDBPersistedDataStorage caller, BrowserContextHandle handle);

        void destroy(long nativePersistedStateDB);

        void save(long nativePersistedStateDB, String key, byte[] data, Runnable onComplete);

        void load(long nativePersistedStateDB, String key, Callback<byte[]> callback);

        void delete(long nativePersistedStateDB, String key, Runnable onComplete);

        void performMaintenance(
                long nativePersistedStateDB,
                String[] keysToKeep,
                String dataId,
                Runnable onComplete);
    }
}