// Copyright (c) 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import org.apache.cordova.CordovaArgs; import org.apache.cordova.CordovaResourceApi; import org.apache.cordova.CordovaResourceApi.OpenForReadResult; import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaPlugin; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.net.Uri; import android.util.Log; public class ChromeStorage extends CordovaPlugin { private static final String LOG_TAG = "ChromeStorage"; @Override public boolean execute(String action, CordovaArgs args, final CallbackContext callbackContext) throws JSONException { if ("get".equals(action)) { get(args, callbackContext); return true; } else if ("getBytesInUse".equals(action)) { getBytesInUse(args, callbackContext); return true; } else if ("set".equals(action)) { set(args, callbackContext); return true; } else if ("remove".equals(action)) { remove(args, callbackContext); return true; } else if ("clear".equals(action)) { clear(args, callbackContext); return true; } return false; } private static List<String> toStringList(JSONArray array) throws JSONException { if (array == null) { return null; } List<String> list = new ArrayList<String>(); for (int i = 0, l = array.length(); i < l; i++) { list.add(array.get(i).toString()); } return list; } private Uri getStorageFile(String namespace) { String fileName = "chromestorage_" + namespace; File f = cordova.getActivity().getFileStreamPath(fileName); return webView.getResourceApi().remapUri(Uri.fromFile(f)); } private JSONObject getStorage(String namespace) throws IOException, JSONException { JSONObject oldMap = new JSONObject(); CordovaResourceApi resourceApi = webView.getResourceApi(); try { OpenForReadResult readResult = resourceApi.openForRead(getStorageFile(namespace)); ByteArrayOutputStream readBytes = new ByteArrayOutputStream((int)readResult.length); resourceApi.copyResource(readResult, readBytes); byte[] bytes = readBytes.toByteArray(); String content = (new String(bytes)).trim(); if (content.length() > 0) { oldMap = new JSONObject(content); } } catch (FileNotFoundException e) { //Suppress the file not found exception } return oldMap; } private void setStorage(String namespace, JSONObject map) throws IOException { OutputStream outputStream = webView.getResourceApi().openOutputStream(getStorageFile(namespace)); try { outputStream.write(map.toString().getBytes()); } finally { outputStream.close(); } } private JSONObject getStoredValuesForKeys(CordovaArgs args, boolean useDefaultValues) { JSONObject ret = new JSONObject(); try { String namespace = args.getString(0); JSONObject jsonObject = (JSONObject) args.optJSONObject(1); JSONArray jsonArray = args.optJSONArray(1); boolean isNull = args.isNull(1); List<String> keys = new ArrayList<String>(); if (jsonObject != null) { keys = toStringList(jsonObject.names()); // Ensure default values of keys are maintained if (useDefaultValues) { ret = jsonObject; } } else if (jsonArray != null) { keys = toStringList(jsonArray); } else if (isNull) { keys = null; } if (keys != null && keys.isEmpty()) { ret = new JSONObject(); } else { JSONObject storage = getStorage(namespace); if (keys == null) { // return the whole storage if the key given is null ret = storage; } else { // return the storage for the keys specified for (String key : keys) { if (storage.has(key)) { Object value = storage.get(key); ret.put(key, value); } } } } } catch (JSONException e) { Log.e(LOG_TAG, "Storage is corrupted!", e); ret = null; } catch (IOException e) { Log.e(LOG_TAG, "Could not retrieve storage", e); ret = null; } return ret; } private void get(final CordovaArgs args, final CallbackContext callbackContext) { cordova.getThreadPool().execute(new Runnable() { @Override public void run() { JSONObject storage = getStoredValuesForKeys(args, /*useDefaultValues*/ true); if (storage == null) { callbackContext.error("Could not retrieve storage"); } else { callbackContext.success(storage); } } }); } private void getBytesInUse(final CordovaArgs args, final CallbackContext callbackContext) { cordova.getThreadPool().execute(new Runnable() { @Override public void run() { //Don't use default values as the keys that don't have values in storage don't affect size JSONObject storage = getStoredValuesForKeys(args, /*useDefaultValues*/ false); if (storage == null) { callbackContext.error("Could not retrieve storage"); } else { callbackContext.success(storage.toString().getBytes().length); } } }); } private void set(final CordovaArgs args, final CallbackContext callbackContext) { cordova.getThreadPool().execute(new Runnable() { @Override public void run() { try { String namespace = args.getString(0); JSONObject jsonObject = (JSONObject) args.getJSONObject(1); JSONArray keyArray = jsonObject.names(); JSONObject oldValues = new JSONObject(); if (keyArray != null) { List<String> keys = toStringList(keyArray); JSONObject storage = getStorage(namespace); for (String key : keys) { Object oldValue = storage.opt(key); if(oldValue != null) { oldValues.put(key, oldValue); } storage.put(key, jsonObject.get(key)); } setStorage(namespace, storage); } callbackContext.success(oldValues); } catch (Exception e) { Log.e(LOG_TAG, "Could not update storage", e); callbackContext.error("Could not update storage"); } } }); } private void remove(final CordovaArgs args, final CallbackContext callbackContext) { cordova.getThreadPool().execute(new Runnable() { @Override public void run() { try { String namespace = args.getString(0); JSONObject jsonObject = (JSONObject) args.optJSONObject(1); JSONArray jsonArray = args.optJSONArray(1); boolean isNull = args.isNull(1); List<String> keys = new ArrayList<String>(); JSONObject oldValues = new JSONObject(); if (jsonObject != null) { keys = toStringList(jsonObject.names()); } else if (jsonArray != null) { keys = toStringList(jsonArray); } else if (isNull) { keys = null; } if (keys != null && !keys.isEmpty()) { JSONObject storage = getStorage(namespace); for(String key : keys) { Object oldValue = storage.opt(key); if(oldValue != null) { oldValues.put(key, oldValue); } storage.remove(key); } setStorage(namespace, storage); } callbackContext.success(oldValues); } catch (Exception e) { Log.e(LOG_TAG, "Could not update storage", e); callbackContext.error("Could not update storage"); } } }); } private void clear(final CordovaArgs args, final CallbackContext callbackContext) { cordova.getThreadPool().execute(new Runnable() { @Override public void run() { try { String namespace = args.getString(0); JSONObject oldValues = getStorage(namespace); setStorage(namespace, new JSONObject()); callbackContext.success(oldValues); } catch (Exception e) { Log.e(LOG_TAG, "Could not clear storage", e); callbackContext.error("Could not update storage"); } } }); } }