/* * Copyright 2010, The Android Open Source Project * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "GeolocationPositionCache.h" #if ENABLE(GEOLOCATION) #include "CrossThreadTask.h" #include "Geoposition.h" #include "SQLValue.h" #include "SQLiteDatabase.h" #include "SQLiteFileSystem.h" #include "SQLiteStatement.h" #include "SQLiteTransaction.h" #include #include using namespace WTF; namespace WebCore { static int numUsers = 0; GeolocationPositionCache* GeolocationPositionCache::instance() { DEFINE_STATIC_LOCAL(GeolocationPositionCache*, instance, (0)); if (!instance) instance = new GeolocationPositionCache(); return instance; } GeolocationPositionCache::GeolocationPositionCache() : m_threadId(0) { } void GeolocationPositionCache::addUser() { ASSERT(numUsers >= 0); MutexLocker databaseLock(m_databaseFileMutex); if (!numUsers && !m_threadId && !m_databaseFile.isNull()) { startBackgroundThread(); MutexLocker lock(m_cachedPositionMutex); if (!m_cachedPosition) triggerReadFromDatabase(); } ++numUsers; } void GeolocationPositionCache::removeUser() { MutexLocker lock(m_cachedPositionMutex); --numUsers; ASSERT(numUsers >= 0); if (!numUsers && m_cachedPosition && m_threadId) triggerWriteToDatabase(); } void GeolocationPositionCache::setDatabasePath(const String& path) { static const char* databaseName = "CachedGeoposition.db"; String newFile = SQLiteFileSystem::appendDatabaseFileNameToPath(path, databaseName); MutexLocker lock(m_databaseFileMutex); if (m_databaseFile != newFile) { m_databaseFile = newFile; if (numUsers && !m_threadId) { startBackgroundThread(); if (!m_cachedPosition) triggerReadFromDatabase(); } } } void GeolocationPositionCache::setCachedPosition(Geoposition* cachedPosition) { MutexLocker lock(m_cachedPositionMutex); m_cachedPosition = cachedPosition; } Geoposition* GeolocationPositionCache::cachedPosition() { MutexLocker lock(m_cachedPositionMutex); return m_cachedPosition.get(); } void GeolocationPositionCache::startBackgroundThread() { // FIXME: Consider sharing this thread with other background tasks. m_threadId = createThread(threadEntryPoint, this, "WebCore: Geolocation cache"); } void* GeolocationPositionCache::threadEntryPoint(void* object) { static_cast(object)->threadEntryPointImpl(); return 0; } void GeolocationPositionCache::threadEntryPointImpl() { while (OwnPtr task = m_queue.waitForMessage()) { // We don't need a ScriptExecutionContext in the callback, so pass 0 here. task->performTask(0); } } void GeolocationPositionCache::triggerReadFromDatabase() { m_queue.append(createCallbackTask(&GeolocationPositionCache::readFromDatabase, this)); } void GeolocationPositionCache::readFromDatabase(ScriptExecutionContext*, GeolocationPositionCache* cache) { cache->readFromDatabaseImpl(); } void GeolocationPositionCache::readFromDatabaseImpl() { SQLiteDatabase database; { MutexLocker lock(m_databaseFileMutex); if (!database.open(m_databaseFile)) return; } // Create the table here, such that even if we've just created the // DB, the commands below should succeed. if (!database.executeCommand("CREATE TABLE IF NOT EXISTS CachedPosition (" "latitude REAL NOT NULL, " "longitude REAL NOT NULL, " "altitude REAL, " "accuracy REAL NOT NULL, " "altitudeAccuracy REAL, " "heading REAL, " "speed REAL, " "timestamp INTEGER NOT NULL)")) return; SQLiteStatement statement(database, "SELECT * FROM CachedPosition"); if (statement.prepare() != SQLResultOk) return; if (statement.step() != SQLResultRow) return; bool providesAltitude = statement.getColumnValue(2).type() != SQLValue::NullValue; bool providesAltitudeAccuracy = statement.getColumnValue(4).type() != SQLValue::NullValue; bool providesHeading = statement.getColumnValue(5).type() != SQLValue::NullValue; bool providesSpeed = statement.getColumnValue(6).type() != SQLValue::NullValue; RefPtr coordinates = Coordinates::create(statement.getColumnDouble(0), // latitude statement.getColumnDouble(1), // longitude providesAltitude, statement.getColumnDouble(2), // altitude statement.getColumnDouble(3), // accuracy providesAltitudeAccuracy, statement.getColumnDouble(4), // altitudeAccuracy providesHeading, statement.getColumnDouble(5), // heading providesSpeed, statement.getColumnDouble(6)); // speed DOMTimeStamp timestamp = statement.getColumnInt64(7); // timestamp // A position may have been set since we called triggerReadFromDatabase(). MutexLocker lock(m_cachedPositionMutex); if (m_cachedPosition) return; m_cachedPosition = Geoposition::create(coordinates.release(), timestamp); } void GeolocationPositionCache::triggerWriteToDatabase() { m_queue.append(createCallbackTask(writeToDatabase, this)); } void GeolocationPositionCache::writeToDatabase(ScriptExecutionContext*, GeolocationPositionCache* cache) { cache->writeToDatabaseImpl(); } void GeolocationPositionCache::writeToDatabaseImpl() { SQLiteDatabase database; { MutexLocker lock(m_databaseFileMutex); if (!database.open(m_databaseFile)) return; } RefPtr cachedPosition; { MutexLocker lock(m_cachedPositionMutex); if (m_cachedPosition) cachedPosition = m_cachedPosition->threadSafeCopy(); } SQLiteTransaction transaction(database); if (!database.executeCommand("DELETE FROM CachedPosition")) return; SQLiteStatement statement(database, "INSERT INTO CachedPosition (" "latitude, " "longitude, " "altitude, " "accuracy, " "altitudeAccuracy, " "heading, " "speed, " "timestamp) " "VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); if (statement.prepare() != SQLResultOk) return; statement.bindDouble(1, cachedPosition->coords()->latitude()); statement.bindDouble(2, cachedPosition->coords()->longitude()); if (cachedPosition->coords()->canProvideAltitude()) statement.bindDouble(3, cachedPosition->coords()->altitude()); else statement.bindNull(3); statement.bindDouble(4, cachedPosition->coords()->accuracy()); if (cachedPosition->coords()->canProvideAltitudeAccuracy()) statement.bindDouble(5, cachedPosition->coords()->altitudeAccuracy()); else statement.bindNull(5); if (cachedPosition->coords()->canProvideHeading()) statement.bindDouble(6, cachedPosition->coords()->heading()); else statement.bindNull(6); if (cachedPosition->coords()->canProvideSpeed()) statement.bindDouble(7, cachedPosition->coords()->speed()); else statement.bindNull(7); statement.bindInt64(8, cachedPosition->timestamp()); if (!statement.executeCommand()) return; transaction.commit(); } } // namespace WebCore #endif // ENABLE(GEOLOCATION)