package org.sigmah.offline.indexeddb;
/*
* #%L
* Sigmah
* %%
* Copyright (C) 2010 - 2016 URD
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* 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/gpl-3.0.html>.
* #L%
*/
import com.allen_sauer.gwt.log.client.Log;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import org.sigmah.offline.event.JavaScriptEvent;
import org.sigmah.offline.sync.UpdateDates;
import org.sigmah.shared.command.result.Authentication;
/**
* Main class for creating and opening IndexedDB databases.
*
* @author Raphaƫl Calabro (rcalabro@ideia.fr)
*/
public class IndexedDB {
private static final LinkedList<AlreadyOpenedDatabaseRequest> LISTENER_QUEUE = new LinkedList<AlreadyOpenedDatabaseRequest>();
private static State state = State.CLOSED;
private static Database<Store> userDatabase;
/**
* Verify if IndexedDB is supported by the current browser.
*
* @return <code>true</code> if supported, <code>false</code> otherwise.
*/
public static native boolean isSupported() /*-{
return typeof $wnd.indexedDB != 'undefined';
}-*/;
/**
* Open or create a database named with the e-mail address of the given user.
*
* @param authentication Information about the current user.
* @return A request to open the database.
*/
public static OpenDatabaseRequest<Store> openUserDatabase(Authentication authentication) {
return openUserDatabase(authentication.getUserEmail());
}
/**
* Removes the database named with the e-mail address of the given user.
*
* @param authentication Information about the current user.
* @return A request to delete the database.
*/
public static OpenDatabaseRequest deleteUserDatabase(Authentication authentication) {
closeDatabase();
return deleteUserDatabase(authentication.getUserEmail());
}
/**
* Open or create a database with the given name.
*
* @param email Name of the database to open or create.
* @return A request to open the database.
*/
private static OpenDatabaseRequest<Store> openUserDatabase(final String email) {
if (userDatabase != null) {
if (userDatabase.getName().equals(email)) {
return new AlreadyOpenedDatabaseRequest<Store>(userDatabase);
} else {
// Switching database.
state = State.CLOSED;
userDatabase.close();
userDatabase = null;
}
}
if (email == null) {
return new NoopDatabaseRequest<Store>();
}
if (!isSupported()) {
Log.warn("IndexedDB is not supported by this web browser.");
return new NoopDatabaseRequest<Store>();
}
if (!GWT.isProdMode()) {
Log.info("IndexedDB is unavailable in Hosted Mode.");
return new NoopDatabaseRequest<Store>();
}
switch (state) {
case CLOSED:
state = State.OPENING;
final IndexedDB indexedDB = new IndexedDB();
final NativeOpenDatabaseRequest<Store> openDatabaseRequest = indexedDB.open(email, Store.class);
openDatabaseRequest.addSuccessHandler(new JavaScriptEvent() {
@Override
public void onEvent(JavaScriptObject event) {
userDatabase = openDatabaseRequest.getResult();
if (userDatabase != null) {
state = State.OPENED;
} else {
state = State.ERROR;
}
for(final AlreadyOpenedDatabaseRequest<Store> listener : LISTENER_QUEUE) {
listener.setResult(userDatabase);
}
}
});
return openDatabaseRequest;
case OPENING:
final AlreadyOpenedDatabaseRequest<Store> listener = new AlreadyOpenedDatabaseRequest<Store>();
LISTENER_QUEUE.add(listener);
return listener;
case OPENED:
return new AlreadyOpenedDatabaseRequest<Store>(userDatabase);
default:
return new NoopDatabaseRequest<Store>();
}
}
/**
* Upgrade the given database.
*
* @param <S> Schema type.
* @param database Database to upgrade.
* @param event Version change event.
* @param name Name of the database.
*/
private static <S extends Enum<S> & Schema> void upgradeDatabase(final Database<S> database, final IDBVersionChangeEvent event, final String name) {
Log.info("Local IndexedDB database is being updated from version " + event.getOldVersion() + " to version " + Stores.getVersion(database.getSchema()) + '.');
UpdateDates.setDatabaseUpdateDate(name, null);
for (final String store : database.getObjectStoreNames()) {
database.deleteObjectStore(store);
}
final Set<S> stores = database.getObjectStores();
for (final S store : database.getSchema().getEnumConstants()) {
if (!stores.contains(store) && store.isEnabled()) {
final ObjectStore objectStore = database.createObjectStore(store, "id", store.isAutoIncrement());
for (final Map.Entry<String, String> index : store.getIndexes().entrySet()) {
final String indexName = index.getKey();
final boolean multiple = indexName.charAt(indexName.length() - 1) == 's';
objectStore.createIndex(indexName, index.getValue(), false, multiple);
}
}
}
}
/**
* Removes the database with the given name.
*
* @param email Name of the database to remove.
* @return A request to delete the database.
*/
private static OpenDatabaseRequest<Store> deleteUserDatabase(String email) {
if (email == null) {
return new NoopDatabaseRequest<Store>();
}
if (!isSupported()) {
Log.warn("IndexedDB is not supported by this web browser.");
return new NoopDatabaseRequest<Store>();
}
final IndexedDB indexedDB = new IndexedDB();
return indexedDB.deleteDatabase(email, Store.class);
}
/**
* Close and release the currently opened user database.
*/
public static void closeDatabase() {
if (userDatabase != null) {
userDatabase.close();
state = State.CLOSED;
}
}
/**
* Native instance of IndexedDB.
*/
private final NativeIndexedDB nativeIndexedDB;
/**
* Creates a new wrapper around IndexedDB.
*
* @throws UnsupportedOperationException If IndexedDB is not supported by the web browser.
*/
public IndexedDB() throws UnsupportedOperationException {
if (!isSupported()) {
throw new UnsupportedOperationException("IndexedDB is not supported by this web browser.");
}
this.nativeIndexedDB = NativeIndexedDB.getIndexedDB();
}
/**
* Create a native request to open a designated IndexedDB database.
*
* @param <S> Schema type.
* @param name Name of the database to open.
* @param stores Schema type class.
* @return An open database request.
*/
public <S extends Enum<S> & Schema> NativeOpenDatabaseRequest<S> open(final String name, final Class<S> stores) {
final NativeOpenDatabaseRequest<S> request = new NativeOpenDatabaseRequest<S>(nativeIndexedDB.open(name, Stores.getVersion(stores)), stores);
request.addUpgradeNeededHandler(new JavaScriptEvent<IDBVersionChangeEvent>() {
@Override
public void onEvent(IDBVersionChangeEvent event) {
upgradeDatabase(request.getResult(), event, name);
}
});
return request;
}
/**
* Create a native request to delete the given database.
*
* @param name Name of the database to delete.
* @return An open database request.
*/
public <S extends Enum<S> & Schema> NativeOpenDatabaseRequest<S> deleteDatabase(String name, final Class<S> stores) {
return new NativeOpenDatabaseRequest<S>(nativeIndexedDB.deleteDatabase(name), stores);
}
/**
* Database states.
*/
public static enum State {
CLOSED, OPENING, OPENED, ERROR;
}
}