com.google.firebase.database.core.RepoManager Maven / Gradle / Ivy
/*
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.firebase.database.core;
import com.google.firebase.FirebaseApp;
import com.google.firebase.database.DatabaseException;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.InternalHelpers;
import io.netty.util.concurrent.FastThreadLocal;
import java.util.HashMap;
import java.util.Map;
public class RepoManager {
private static final RepoManager instance;
static {
instance = new RepoManager();
}
private final Map> repos = new HashMap<>();
public RepoManager() {}
/**
* Used for legacy unit tests. The public API should go through FirebaseDatabase which calls
* createRepo.
*/
public static Repo getRepo(Context ctx, RepoInfo info) throws DatabaseException {
return instance.getLocalRepo(ctx, info);
}
public static Repo createRepo(Context ctx, RepoInfo info, FirebaseDatabase database)
throws DatabaseException {
return instance.createLocalRepo(ctx, info, database);
}
public static void destroy(Context ctx) {
try {
instance.destroyInternal(ctx);
} finally {
ctx.stop();
// Clean up Netty thread locals, which will also clean up any dangling threadDeathWatcher
// daemons. See https://github.com/netty/netty/issues/7310 for more context.
FastThreadLocal.removeAll();
}
}
public static void interrupt(Context ctx) {
instance.interruptInternal(ctx);
}
public static void interrupt(final Repo repo) {
repo.scheduleNow(
new Runnable() {
@Override
public void run() {
repo.interrupt();
}
});
}
public static void resume(final Repo repo) {
repo.scheduleNow(
new Runnable() {
@Override
public void run() {
repo.resume();
}
});
}
public static void resume(Context ctx) {
instance.resumeInternal(ctx);
}
private Repo getLocalRepo(Context ctx, RepoInfo info) throws DatabaseException {
ctx.freeze(); // No-op if it's already frozen
String repoHash = "https://" + info.host + "/" + info.namespace;
synchronized (repos) {
if (!repos.containsKey(ctx) || !repos.get(ctx).containsKey(repoHash)) {
// Calling this should create the repo.
InternalHelpers.createDatabaseForTests(
FirebaseApp.getInstance(), info, (DatabaseConfig) ctx);
}
return repos.get(ctx).get(repoHash);
}
}
private Repo createLocalRepo(Context ctx, RepoInfo info, FirebaseDatabase database)
throws DatabaseException {
ctx.freeze(); // No-op if it's already frozen
String repoHash = "https://" + info.host + "/" + info.namespace;
synchronized (repos) {
if (!repos.containsKey(ctx)) {
Map innerMap = new HashMap<>();
repos.put(ctx, innerMap);
}
Map innerMap = repos.get(ctx);
if (!innerMap.containsKey(repoHash)) {
Repo repo = new Repo(info, ctx, database);
innerMap.put(repoHash, repo);
return repo;
} else {
throw new IllegalStateException("createLocalRepo() called for existing repo.");
}
}
}
private void interruptInternal(final Context ctx) {
RunLoop runLoop = ctx.getRunLoop();
if (runLoop != null) {
runLoop.scheduleNow(
new Runnable() {
@Override
public void run() {
synchronized (repos) {
boolean allEmpty = true;
if (repos.containsKey(ctx)) {
for (Repo repo : repos.get(ctx).values()) {
repo.interrupt();
allEmpty = allEmpty && !repo.hasListeners();
}
if (allEmpty) {
ctx.stop();
}
}
}
}
});
}
}
private void destroyInternal(final Context ctx) {
RunLoop runLoop = ctx.getRunLoop();
if (runLoop != null) {
// RunLoop gets initialized before any Repo is created. Therefore we can assume that when
// the RunLoop is not present, there's nothing to clean up.
runLoop.scheduleNow(new Runnable() {
@Override
public void run() {
synchronized (repos) {
if (repos.containsKey(ctx)) {
for (Repo repo : repos.get(ctx).values()) {
repo.interrupt();
}
}
}
}
});
}
}
private void resumeInternal(final Context ctx) {
RunLoop runLoop = ctx.getRunLoop();
if (runLoop != null) {
runLoop.scheduleNow(
new Runnable() {
@Override
public void run() {
synchronized (repos) {
if (repos.containsKey(ctx)) {
for (Repo repo : repos.get(ctx).values()) {
repo.resume();
}
}
}
}
});
}
}
}