1/* 2 * Copyright 2010, The Android Open Source Project 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * * Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * * Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26#include "config.h" 27#include "GeolocationPositionCache.h" 28 29#if ENABLE(GEOLOCATION) 30 31#include "CrossThreadTask.h" 32#include "Geoposition.h" 33#include "SQLValue.h" 34#include "SQLiteDatabase.h" 35#include "SQLiteFileSystem.h" 36#include "SQLiteStatement.h" 37#include "SQLiteTransaction.h" 38#include <wtf/PassOwnPtr.h> 39#include <wtf/Threading.h> 40 41using namespace WTF; 42 43namespace WebCore { 44 45static int numUsers = 0; 46 47GeolocationPositionCache* GeolocationPositionCache::instance() 48{ 49 DEFINE_STATIC_LOCAL(GeolocationPositionCache*, instance, (0)); 50 if (!instance) 51 instance = new GeolocationPositionCache(); 52 return instance; 53} 54 55GeolocationPositionCache::GeolocationPositionCache() 56 : m_threadId(0) 57{ 58} 59 60void GeolocationPositionCache::addUser() 61{ 62 ASSERT(numUsers >= 0); 63 MutexLocker databaseLock(m_databaseFileMutex); 64 if (!numUsers && !m_threadId && !m_databaseFile.isNull()) { 65 startBackgroundThread(); 66 MutexLocker lock(m_cachedPositionMutex); 67 if (!m_cachedPosition) 68 triggerReadFromDatabase(); 69 } 70 ++numUsers; 71} 72 73void GeolocationPositionCache::removeUser() 74{ 75 MutexLocker lock(m_cachedPositionMutex); 76 --numUsers; 77 ASSERT(numUsers >= 0); 78 if (!numUsers && m_cachedPosition && m_threadId) 79 triggerWriteToDatabase(); 80} 81 82void GeolocationPositionCache::setDatabasePath(const String& path) 83{ 84 static const char* databaseName = "CachedGeoposition.db"; 85 String newFile = SQLiteFileSystem::appendDatabaseFileNameToPath(path, databaseName); 86 MutexLocker lock(m_databaseFileMutex); 87 if (m_databaseFile != newFile) { 88 m_databaseFile = newFile; 89 if (numUsers && !m_threadId) { 90 startBackgroundThread(); 91 if (!m_cachedPosition) 92 triggerReadFromDatabase(); 93 } 94 } 95} 96 97void GeolocationPositionCache::setCachedPosition(Geoposition* cachedPosition) 98{ 99 MutexLocker lock(m_cachedPositionMutex); 100 m_cachedPosition = cachedPosition; 101} 102 103Geoposition* GeolocationPositionCache::cachedPosition() 104{ 105 MutexLocker lock(m_cachedPositionMutex); 106 return m_cachedPosition.get(); 107} 108 109void GeolocationPositionCache::startBackgroundThread() 110{ 111 // FIXME: Consider sharing this thread with other background tasks. 112 m_threadId = createThread(threadEntryPoint, this, "WebCore: Geolocation cache"); 113} 114 115void* GeolocationPositionCache::threadEntryPoint(void* object) 116{ 117 static_cast<GeolocationPositionCache*>(object)->threadEntryPointImpl(); 118 return 0; 119} 120 121void GeolocationPositionCache::threadEntryPointImpl() 122{ 123 while (OwnPtr<ScriptExecutionContext::Task> task = m_queue.waitForMessage()) { 124 // We don't need a ScriptExecutionContext in the callback, so pass 0 here. 125 task->performTask(0); 126 } 127} 128 129void GeolocationPositionCache::triggerReadFromDatabase() 130{ 131 m_queue.append(createCallbackTask(&GeolocationPositionCache::readFromDatabase, this)); 132} 133 134void GeolocationPositionCache::readFromDatabase(ScriptExecutionContext*, GeolocationPositionCache* cache) 135{ 136 cache->readFromDatabaseImpl(); 137} 138 139void GeolocationPositionCache::readFromDatabaseImpl() 140{ 141 SQLiteDatabase database; 142 { 143 MutexLocker lock(m_databaseFileMutex); 144 if (!database.open(m_databaseFile)) 145 return; 146 } 147 148 // Create the table here, such that even if we've just created the 149 // DB, the commands below should succeed. 150 if (!database.executeCommand("CREATE TABLE IF NOT EXISTS CachedPosition (" 151 "latitude REAL NOT NULL, " 152 "longitude REAL NOT NULL, " 153 "altitude REAL, " 154 "accuracy REAL NOT NULL, " 155 "altitudeAccuracy REAL, " 156 "heading REAL, " 157 "speed REAL, " 158 "timestamp INTEGER NOT NULL)")) 159 return; 160 161 SQLiteStatement statement(database, "SELECT * FROM CachedPosition"); 162 if (statement.prepare() != SQLResultOk) 163 return; 164 165 if (statement.step() != SQLResultRow) 166 return; 167 168 bool providesAltitude = statement.getColumnValue(2).type() != SQLValue::NullValue; 169 bool providesAltitudeAccuracy = statement.getColumnValue(4).type() != SQLValue::NullValue; 170 bool providesHeading = statement.getColumnValue(5).type() != SQLValue::NullValue; 171 bool providesSpeed = statement.getColumnValue(6).type() != SQLValue::NullValue; 172 RefPtr<Coordinates> coordinates = Coordinates::create(statement.getColumnDouble(0), // latitude 173 statement.getColumnDouble(1), // longitude 174 providesAltitude, statement.getColumnDouble(2), // altitude 175 statement.getColumnDouble(3), // accuracy 176 providesAltitudeAccuracy, statement.getColumnDouble(4), // altitudeAccuracy 177 providesHeading, statement.getColumnDouble(5), // heading 178 providesSpeed, statement.getColumnDouble(6)); // speed 179 DOMTimeStamp timestamp = statement.getColumnInt64(7); // timestamp 180 181 // A position may have been set since we called triggerReadFromDatabase(). 182 MutexLocker lock(m_cachedPositionMutex); 183 if (m_cachedPosition) 184 return; 185 m_cachedPosition = Geoposition::create(coordinates.release(), timestamp); 186} 187 188void GeolocationPositionCache::triggerWriteToDatabase() 189{ 190 m_queue.append(createCallbackTask(writeToDatabase, this)); 191} 192 193void GeolocationPositionCache::writeToDatabase(ScriptExecutionContext*, GeolocationPositionCache* cache) 194{ 195 cache->writeToDatabaseImpl(); 196} 197 198void GeolocationPositionCache::writeToDatabaseImpl() 199{ 200 SQLiteDatabase database; 201 { 202 MutexLocker lock(m_databaseFileMutex); 203 if (!database.open(m_databaseFile)) 204 return; 205 } 206 207 RefPtr<Geoposition> cachedPosition; 208 { 209 MutexLocker lock(m_cachedPositionMutex); 210 if (m_cachedPosition) 211 cachedPosition = m_cachedPosition->threadSafeCopy(); 212 } 213 214 SQLiteTransaction transaction(database); 215 216 if (!database.executeCommand("DELETE FROM CachedPosition")) 217 return; 218 219 SQLiteStatement statement(database, "INSERT INTO CachedPosition (" 220 "latitude, " 221 "longitude, " 222 "altitude, " 223 "accuracy, " 224 "altitudeAccuracy, " 225 "heading, " 226 "speed, " 227 "timestamp) " 228 "VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); 229 if (statement.prepare() != SQLResultOk) 230 return; 231 232 statement.bindDouble(1, cachedPosition->coords()->latitude()); 233 statement.bindDouble(2, cachedPosition->coords()->longitude()); 234 if (cachedPosition->coords()->canProvideAltitude()) 235 statement.bindDouble(3, cachedPosition->coords()->altitude()); 236 else 237 statement.bindNull(3); 238 statement.bindDouble(4, cachedPosition->coords()->accuracy()); 239 if (cachedPosition->coords()->canProvideAltitudeAccuracy()) 240 statement.bindDouble(5, cachedPosition->coords()->altitudeAccuracy()); 241 else 242 statement.bindNull(5); 243 if (cachedPosition->coords()->canProvideHeading()) 244 statement.bindDouble(6, cachedPosition->coords()->heading()); 245 else 246 statement.bindNull(6); 247 if (cachedPosition->coords()->canProvideSpeed()) 248 statement.bindDouble(7, cachedPosition->coords()->speed()); 249 else 250 statement.bindNull(7); 251 statement.bindInt64(8, cachedPosition->timestamp()); 252 253 if (!statement.executeCommand()) 254 return; 255 256 transaction.commit(); 257} 258 259} // namespace WebCore 260 261#endif // ENABLE(GEOLOCATION) 262