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 
instance()47 GeolocationPositionCache* GeolocationPositionCache::instance()
48 {
49     DEFINE_STATIC_LOCAL(GeolocationPositionCache*, instance, (0));
50     if (!instance)
51         instance = new GeolocationPositionCache();
52     return instance;
53 }
54 
GeolocationPositionCache()55 GeolocationPositionCache::GeolocationPositionCache()
56     : m_threadId(0)
57 {
58 }
59 
addUser()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 
removeUser()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 
setDatabasePath(const String & path)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 
setCachedPosition(Geoposition * cachedPosition)97 void GeolocationPositionCache::setCachedPosition(Geoposition* cachedPosition)
98 {
99     MutexLocker lock(m_cachedPositionMutex);
100     m_cachedPosition = cachedPosition;
101 }
102 
cachedPosition()103 Geoposition* GeolocationPositionCache::cachedPosition()
104 {
105     MutexLocker lock(m_cachedPositionMutex);
106     return m_cachedPosition.get();
107 }
108 
startBackgroundThread()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 
threadEntryPoint(void * object)115 void* GeolocationPositionCache::threadEntryPoint(void* object)
116 {
117     static_cast<GeolocationPositionCache*>(object)->threadEntryPointImpl();
118     return 0;
119 }
120 
threadEntryPointImpl()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 
triggerReadFromDatabase()129 void GeolocationPositionCache::triggerReadFromDatabase()
130 {
131     m_queue.append(createCallbackTask(&GeolocationPositionCache::readFromDatabase, AllowCrossThreadAccess(this)));
132 }
133 
readFromDatabase(ScriptExecutionContext *,GeolocationPositionCache * cache)134 void GeolocationPositionCache::readFromDatabase(ScriptExecutionContext*, GeolocationPositionCache* cache)
135 {
136     cache->readFromDatabaseImpl();
137 }
138 
readFromDatabaseImpl()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 
triggerWriteToDatabase()188 void GeolocationPositionCache::triggerWriteToDatabase()
189 {
190     m_queue.append(createCallbackTask(writeToDatabase, AllowCrossThreadAccess(this)));
191 }
192 
writeToDatabase(ScriptExecutionContext *,GeolocationPositionCache * cache)193 void GeolocationPositionCache::writeToDatabase(ScriptExecutionContext*, GeolocationPositionCache* cache)
194 {
195     cache->writeToDatabaseImpl();
196 }
197 
writeToDatabaseImpl()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