Home | History | Annotate | Download | only in page
      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 
     41 using namespace WTF;
     42 
     43 namespace WebCore {
     44 
     45 static int numUsers = 0;
     46 
     47 GeolocationPositionCache* GeolocationPositionCache::instance()
     48 {
     49     DEFINE_STATIC_LOCAL(GeolocationPositionCache*, instance, (0));
     50     if (!instance)
     51         instance = new GeolocationPositionCache();
     52     return instance;
     53 }
     54 
     55 GeolocationPositionCache::GeolocationPositionCache()
     56     : m_threadId(0)
     57 {
     58 }
     59 
     60 void 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 
     73 void 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 
     82 void 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 
     97 void GeolocationPositionCache::setCachedPosition(Geoposition* cachedPosition)
     98 {
     99     MutexLocker lock(m_cachedPositionMutex);
    100     m_cachedPosition = cachedPosition;
    101 }
    102 
    103 Geoposition* GeolocationPositionCache::cachedPosition()
    104 {
    105     MutexLocker lock(m_cachedPositionMutex);
    106     return m_cachedPosition.get();
    107 }
    108 
    109 void GeolocationPositionCache::startBackgroundThread()
    110 {
    111     // FIXME: Consider sharing this thread with other background tasks.
    112     m_threadId = createThread(threadEntryPoint, this, "WebCore: Geolocation cache");
    113 }
    114 
    115 void* GeolocationPositionCache::threadEntryPoint(void* object)
    116 {
    117     static_cast<GeolocationPositionCache*>(object)->threadEntryPointImpl();
    118     return 0;
    119 }
    120 
    121 void 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 
    129 void GeolocationPositionCache::triggerReadFromDatabase()
    130 {
    131     m_queue.append(createCallbackTask(&GeolocationPositionCache::readFromDatabase, this));
    132 }
    133 
    134 void GeolocationPositionCache::readFromDatabase(ScriptExecutionContext*, GeolocationPositionCache* cache)
    135 {
    136     cache->readFromDatabaseImpl();
    137 }
    138 
    139 void 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 
    188 void GeolocationPositionCache::triggerWriteToDatabase()
    189 {
    190     m_queue.append(createCallbackTask(writeToDatabase, this));
    191 }
    192 
    193 void GeolocationPositionCache::writeToDatabase(ScriptExecutionContext*, GeolocationPositionCache* cache)
    194 {
    195     cache->writeToDatabaseImpl();
    196 }
    197 
    198 void 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