/*
GNU GENERAL PUBLIC LICENSE
Copyright (C) 2006 The Lobo Project
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
verion 2 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 library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Contact info: lobochief@users.sourceforge.net
*/
/*
* Created on Mar 12, 2005
*/
package org.lobobrowser.store;
import static org.jooq.impl.DSL.using;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidClassException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jooq.DSLContext;
import org.lobobrowser.security.LocalSecurityPolicy;
import org.lobobrowser.security.StoreHostPermission;
/**
* * @author J. H. S.
*/
public class StorageManager implements Runnable {
private static final Logger logger = Logger.getLogger(StorageManager.class.getName());
private static final long HOST_STORE_QUOTA = 200 * 1024;
// Note that the installer makes assumptions about these names.
private static final String HOST_STORE_DIR = "HostStore";
private static final String CACHE_DIR = "cache";
private static final String CONTENT_DIR = "content";
private static final String SETTINGS_DIR = "settings";
private static final StorageManager instance = new StorageManager();
private final File storeDirectory;
private final File cacheRootDirectory;
public final String userDBPath;
public static StorageManager getInstance() {
return instance;
}
private StorageManager() {
this.storeDirectory = LocalSecurityPolicy.STORE_DIRECTORY;
this.cacheRootDirectory = new File(this.storeDirectory, CACHE_DIR);
if (!this.storeDirectory.exists()) {
this.storeDirectory.mkdirs();
}
userDBPath = new File(storeDirectory, "user.h2").getAbsolutePath();
}
private DSLContext userDB;
private Connection dbConnection;
public synchronized DSLContext getDB() {
if (userDB == null) {
try {
Class.forName("org.h2.Driver");
dbConnection = DriverManager.getConnection("jdbc:h2:" + userDBPath, "sa", "");
userDB = using(dbConnection);
initDB(userDB);
} catch (SQLException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
return userDB;
}
private void initDB(final DSLContext userDB) {
final int tableCount = getTableCount(userDB);
if (tableCount == 0) {
final InputStream schemaStream = getClass().getResourceAsStream("/info/gngr/schema.sql");
try (
final Scanner scanner = new Scanner(schemaStream, "UTF-8")) {
final String text = scanner.useDelimiter("\\A").next();
userDB.execute(text);
}
}
}
private static int getTableCount(final DSLContext userDB) {
// TODO: https://stackoverflow.com/questions/24741761/how-to-check-if-a-table-exists-in-jooq
final int tableCount =
userDB
.selectCount()
.from("INFORMATION_SCHEMA.TABLES")
.where("TABLE_SCHEMA = 'PUBLIC'")
.fetchOne().value1();
return tableCount;
}
private boolean threadStarted = false;
private void ensureThreadStarted() {
if (!this.threadStarted) {
synchronized (this) {
if (!this.threadStarted) {
final Thread t = new Thread(this, "StorageManager");
t.setDaemon(true);
t.setPriority(Thread.MIN_PRIORITY);
t.start();
this.threadStarted = true;
}
}
}
}
public File getAppHome() {
return this.storeDirectory;
}
private static final String NO_HOST = "$NO_HOST$";
public File getCacheHostDirectory(String hostName) throws IOException {
CacheManager.getInstance();
final File cacheDir = this.getCacheRoot();
if ((hostName == null) || "".equals(hostName)) {
hostName = NO_HOST;
}
return new File(cacheDir, normalizedFileName(hostName));
}
public File getContentCacheFile(final String hostName, final String fileName) throws IOException {
final File domainDir = this.getCacheHostDirectory(hostName);
final File xamjDir = new File(domainDir, CONTENT_DIR);
return new File(xamjDir, fileName);
}
public File getCacheRoot() {
return this.cacheRootDirectory;
}
private final Map<String, RestrictedStore> restrictedStoreCache = new HashMap<>();
/**
* @param hostName
* should be canonicalized to lower case
*/
public RestrictedStore getRestrictedStore(String hostName, final boolean createIfNotExists) throws IOException {
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(StoreHostPermission.forHost(hostName));
}
if ((hostName == null) || "".equals(hostName)) {
hostName = NO_HOST;
}
final String normHost = hostName;
RestrictedStore store;
synchronized (this) {
store = this.restrictedStoreCache.get(normHost);
if (store == null) {
store = AccessController.doPrivileged(new PrivilegedAction<RestrictedStore>() {
// Reason: Since we are checking StoreHostPermission previously,
// this is fine.
public RestrictedStore run() {
final File hostStoreDir = new File(storeDirectory, HOST_STORE_DIR);
final File domainDir = new File(hostStoreDir, normalizedFileName(normHost));
if (!createIfNotExists && !domainDir.exists()) {
return null;
}
try {
return new RestrictedStore(domainDir, HOST_STORE_QUOTA);
} catch (final IOException ioe) {
throw new IllegalStateException(ioe);
}
}
});
if (store != null) {
this.restrictedStoreCache.put(normHost, store);
}
}
}
if (store != null) {
this.ensureThreadStarted();
}
return store;
}
public File getSettingsDirectory() {
return new File(this.storeDirectory, SETTINGS_DIR);
}
public void saveSettings(final String name, final Serializable data) throws IOException {
final File dir = this.getSettingsDirectory();
if (!dir.exists()) {
dir.mkdirs();
}
final File file = new File(dir, name);
try (
final OutputStream out = new FileOutputStream(file);
final BufferedOutputStream bos = new BufferedOutputStream(out);
final ObjectOutputStream oos = new ObjectOutputStream(bos);) {
oos.writeObject(data);
oos.flush();
}
}
public Serializable retrieveSettings(final String name, final ClassLoader classLoader) throws IOException, ClassNotFoundException {
final File dir = this.getSettingsDirectory();
if (!dir.exists()) {
return null;
}
final File file = new File(dir, name);
if (!file.exists()) {
return null;
}
try (
final InputStream in = new FileInputStream(file);
final BufferedInputStream bin = new BufferedInputStream(in);
final ObjectInputStream ois = new ClassLoaderObjectInputStream(bin, classLoader);) {
return (Serializable) ois.readObject();
} catch (final InvalidClassException ice) {
ice.printStackTrace();
return null;
}
}
// public Collection getBroadRestrictedStores(String hostName) throws
// IOException {
// SecurityManager sm = System.getSecurityManager();
// if(sm != null) {
// sm.checkPermission(HostPermission.forHost(hostName));
// }
// File hostStoreDir = new File(this.settingsDirectory, HOST_STORE_DIR);
// if(hostName == null || "".equals(hostName)) {
// hostName = NO_HOST;
// File domainDir = new File(hostStoreDir, normalizedFileName(hostName));
// return Collections.singleton(new RestrictedStore(domainDir,
// HOST_STORE_QUOTA));
// }
// else {
// Collection restrictedStores = new LinkedList();
// File[] domainDirs = hostStoreDir.listFiles(new
// CookieHostFilenameFilter(hostName));
// if(domainDirs != null) {
// for(int i = 0; i < domainDirs.length; i++) {
// restrictedStores.add(new RestrictedStore(domainDirs[i], HOST_STORE_QUOTA));
// }
// }
// return restrictedStores;
// }
// }
static String normalizedFileName(final String hostName) {
return hostName;
}
static String getHostName(final String fileName) {
return fileName;
}
private static final int MANAGED_STORE_UPDATE_DELAY = 1000 * 60 * 5; /* 5 minutes */
public void run() {
for (;;) {
try {
Thread.sleep(MANAGED_STORE_UPDATE_DELAY);
RestrictedStore[] stores;
synchronized (this) {
stores = this.restrictedStoreCache.values().toArray(new RestrictedStore[0]);
}
for (final RestrictedStore store : stores) {
Thread.yield();
store.updateSizeFile();
}
} catch (final Exception err) {
logger.log(Level.SEVERE, "run()", err);
try {
Thread.sleep(MANAGED_STORE_UPDATE_DELAY);
} catch (final java.lang.InterruptedException ie) {
// Ignore this time.
}
}
}
}
public synchronized void shutdown() {
if (dbConnection != null) {
try {
dbConnection.close();
} catch (final SQLException e) {
// Since we are shutting down, we shouldn't bubble any exception, to let other modules shutdown gracefully
e.printStackTrace();
}
}
}
}