/src/3rdparty/webkit/Source/WebCore/loader/icon/IconDatabase.cpp

https://bitbucket.org/ultra_iter/qt-vtl · C++ · 2250 lines · 1502 code · 470 blank · 278 comment · 293 complexity · ab102563e33e56369c45e7a1b2a9e9d7 MD5 · raw file

  1. /*
  2. * Copyright (C) 2006, 2007, 2008, 2009, 2011 Apple Inc. All rights reserved.
  3. * Copyright (C) 2007 Justin Haygood (jhaygood@reaktix.com)
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions
  7. * are met:
  8. * 1. Redistributions of source code must retain the above copyright
  9. * notice, this list of conditions and the following disclaimer.
  10. * 2. Redistributions in binary form must reproduce the above copyright
  11. * notice, this list of conditions and the following disclaimer in the
  12. * documentation and/or other materials provided with the distribution.
  13. *
  14. * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
  15. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  16. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  17. * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
  18. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  19. * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  20. * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  21. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
  22. * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  23. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  24. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  25. */
  26. #include "config.h"
  27. #include "IconDatabase.h"
  28. #if ENABLE(ICONDATABASE)
  29. #include "AutodrainedPool.h"
  30. #include "DocumentLoader.h"
  31. #include "FileSystem.h"
  32. #include "IconDatabaseClient.h"
  33. #include "IconRecord.h"
  34. #include "IntSize.h"
  35. #include "Logging.h"
  36. #include "SQLiteStatement.h"
  37. #include "SQLiteTransaction.h"
  38. #include "SuddenTermination.h"
  39. #include <wtf/CurrentTime.h>
  40. #include <wtf/MainThread.h>
  41. #include <wtf/StdLibExtras.h>
  42. #include <wtf/text/CString.h>
  43. // For methods that are meant to support API from the main thread - should not be called internally
  44. #define ASSERT_NOT_SYNC_THREAD() ASSERT(!m_syncThreadRunning || !IS_ICON_SYNC_THREAD())
  45. // For methods that are meant to support the sync thread ONLY
  46. #define IS_ICON_SYNC_THREAD() (m_syncThread == currentThread())
  47. #define ASSERT_ICON_SYNC_THREAD() ASSERT(IS_ICON_SYNC_THREAD())
  48. #if PLATFORM(QT) || PLATFORM(GTK)
  49. #define CAN_THEME_URL_ICON
  50. #endif
  51. namespace WebCore {
  52. static int databaseCleanupCounter = 0;
  53. // This version number is in the DB and marks the current generation of the schema
  54. // Currently, a mismatched schema causes the DB to be wiped and reset. This isn't
  55. // so bad during development but in the future, we would need to write a conversion
  56. // function to advance older released schemas to "current"
  57. static const int currentDatabaseVersion = 6;
  58. // Icons expire once every 4 days
  59. static const int iconExpirationTime = 60*60*24*4;
  60. static const int updateTimerDelay = 5;
  61. static bool checkIntegrityOnOpen = false;
  62. #ifndef NDEBUG
  63. static String urlForLogging(const String& url)
  64. {
  65. static unsigned urlTruncationLength = 120;
  66. if (url.length() < urlTruncationLength)
  67. return url;
  68. return url.substring(0, urlTruncationLength) + "...";
  69. }
  70. #endif
  71. class DefaultIconDatabaseClient : public IconDatabaseClient {
  72. public:
  73. virtual bool performImport() { return true; }
  74. virtual void didImportIconURLForPageURL(const String&) { }
  75. virtual void didImportIconDataForPageURL(const String&) { }
  76. virtual void didChangeIconForPageURL(const String&) { }
  77. virtual void didRemoveAllIcons() { }
  78. virtual void didFinishURLImport() { }
  79. };
  80. static IconDatabaseClient* defaultClient()
  81. {
  82. static IconDatabaseClient* defaultClient = new DefaultIconDatabaseClient();
  83. return defaultClient;
  84. }
  85. static inline bool pageCanHaveIcon(const String& pageURL)
  86. {
  87. return protocolIsInHTTPFamily(pageURL);
  88. }
  89. // ************************
  90. // *** Main Thread Only ***
  91. // ************************
  92. void IconDatabase::setClient(IconDatabaseClient* client)
  93. {
  94. // We don't allow a null client, because we never null check it anywhere in this code
  95. // Also don't allow a client change after the thread has already began
  96. // (setting the client should occur before the database is opened)
  97. ASSERT(client);
  98. ASSERT(!m_syncThreadRunning);
  99. if (!client || m_syncThreadRunning)
  100. return;
  101. m_client = client;
  102. }
  103. bool IconDatabase::open(const String& directory, const String& filename)
  104. {
  105. ASSERT_NOT_SYNC_THREAD();
  106. if (!m_isEnabled)
  107. return false;
  108. if (isOpen()) {
  109. LOG_ERROR("Attempt to reopen the IconDatabase which is already open. Must close it first.");
  110. return false;
  111. }
  112. m_databaseDirectory = directory.crossThreadString();
  113. // Formulate the full path for the database file
  114. m_completeDatabasePath = pathByAppendingComponent(m_databaseDirectory, filename);
  115. // Lock here as well as first thing in the thread so the thread doesn't actually commence until the createThread() call
  116. // completes and m_syncThreadRunning is properly set
  117. m_syncLock.lock();
  118. m_syncThread = createThread(IconDatabase::iconDatabaseSyncThreadStart, this, "WebCore: IconDatabase");
  119. m_syncThreadRunning = m_syncThread;
  120. m_syncLock.unlock();
  121. if (!m_syncThread)
  122. return false;
  123. return true;
  124. }
  125. void IconDatabase::close()
  126. {
  127. ASSERT_NOT_SYNC_THREAD();
  128. if (m_syncThreadRunning) {
  129. // Set the flag to tell the sync thread to wrap it up
  130. m_threadTerminationRequested = true;
  131. // Wake up the sync thread if it's waiting
  132. wakeSyncThread();
  133. // Wait for the sync thread to terminate
  134. waitForThreadCompletion(m_syncThread, 0);
  135. }
  136. m_syncThreadRunning = false;
  137. m_threadTerminationRequested = false;
  138. m_removeIconsRequested = false;
  139. m_syncDB.close();
  140. ASSERT(!isOpen());
  141. }
  142. void IconDatabase::removeAllIcons()
  143. {
  144. ASSERT_NOT_SYNC_THREAD();
  145. if (!isOpen())
  146. return;
  147. LOG(IconDatabase, "Requesting background thread to remove all icons");
  148. // Clear the in-memory record of every IconRecord, anything waiting to be read from disk, and anything waiting to be written to disk
  149. {
  150. MutexLocker locker(m_urlAndIconLock);
  151. // Clear the IconRecords for every page URL - RefCounting will cause the IconRecords themselves to be deleted
  152. // We don't delete the actual PageRecords because we have the "retain icon for url" count to keep track of
  153. HashMap<String, PageURLRecord*>::iterator iter = m_pageURLToRecordMap.begin();
  154. HashMap<String, PageURLRecord*>::iterator end = m_pageURLToRecordMap.end();
  155. for (; iter != end; ++iter)
  156. (*iter).second->setIconRecord(0);
  157. // Clear the iconURL -> IconRecord map
  158. m_iconURLToRecordMap.clear();
  159. // Clear all in-memory records of things that need to be synced out to disk
  160. {
  161. MutexLocker locker(m_pendingSyncLock);
  162. m_pageURLsPendingSync.clear();
  163. m_iconsPendingSync.clear();
  164. }
  165. // Clear all in-memory records of things that need to be read in from disk
  166. {
  167. MutexLocker locker(m_pendingReadingLock);
  168. m_pageURLsPendingImport.clear();
  169. m_pageURLsInterestedInIcons.clear();
  170. m_iconsPendingReading.clear();
  171. m_loadersPendingDecision.clear();
  172. }
  173. }
  174. m_removeIconsRequested = true;
  175. wakeSyncThread();
  176. }
  177. Image* IconDatabase::synchronousIconForPageURL(const String& pageURLOriginal, const IntSize& size)
  178. {
  179. ASSERT_NOT_SYNC_THREAD();
  180. // pageURLOriginal cannot be stored without being deep copied first.
  181. // We should go our of our way to only copy it if we have to store it
  182. if (!isOpen() || !pageCanHaveIcon(pageURLOriginal))
  183. return defaultIcon(size);
  184. MutexLocker locker(m_urlAndIconLock);
  185. String pageURLCopy; // Creates a null string for easy testing
  186. PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURLOriginal);
  187. if (!pageRecord) {
  188. pageURLCopy = pageURLOriginal.crossThreadString();
  189. pageRecord = getOrCreatePageURLRecord(pageURLCopy);
  190. }
  191. // If pageRecord is NULL, one of two things is true -
  192. // 1 - The initial url import is incomplete and this pageURL was marked to be notified once it is complete if an iconURL exists
  193. // 2 - The initial url import IS complete and this pageURL has no icon
  194. if (!pageRecord) {
  195. MutexLocker locker(m_pendingReadingLock);
  196. // Import is ongoing, there might be an icon. In this case, register to be notified when the icon comes in
  197. // If we ever reach this condition, we know we've already made the pageURL copy
  198. if (!m_iconURLImportComplete)
  199. m_pageURLsInterestedInIcons.add(pageURLCopy);
  200. return 0;
  201. }
  202. IconRecord* iconRecord = pageRecord->iconRecord();
  203. // If the initial URL import isn't complete, it's possible to have a PageURL record without an associated icon
  204. // In this case, the pageURL is already in the set to alert the client when the iconURL mapping is complete so
  205. // we can just bail now
  206. if (!m_iconURLImportComplete && !iconRecord)
  207. return 0;
  208. // The only way we should *not* have an icon record is if this pageURL is retained but has no icon yet - make sure of that
  209. ASSERT(iconRecord || m_retainedPageURLs.contains(pageURLOriginal));
  210. if (!iconRecord)
  211. return 0;
  212. // If it's a new IconRecord object that doesn't have its imageData set yet,
  213. // mark it to be read by the background thread
  214. if (iconRecord->imageDataStatus() == ImageDataStatusUnknown) {
  215. if (pageURLCopy.isNull())
  216. pageURLCopy = pageURLOriginal.crossThreadString();
  217. MutexLocker locker(m_pendingReadingLock);
  218. m_pageURLsInterestedInIcons.add(pageURLCopy);
  219. m_iconsPendingReading.add(iconRecord);
  220. wakeSyncThread();
  221. return 0;
  222. }
  223. // If the size parameter was (0, 0) that means the caller of this method just wanted the read from disk to be kicked off
  224. // and isn't actually interested in the image return value
  225. if (size == IntSize(0, 0))
  226. return 0;
  227. // PARANOID DISCUSSION: This method makes some assumptions. It returns a WebCore::image which the icon database might dispose of at anytime in the future,
  228. // and Images aren't ref counted. So there is no way for the client to guarantee continued existence of the image.
  229. // This has *always* been the case, but in practice clients would always create some other platform specific representation of the image
  230. // and drop the raw Image*. On Mac an NSImage, and on windows drawing into an HBITMAP.
  231. // The async aspect adds a huge question - what if the image is deleted before the platform specific API has a chance to create its own
  232. // representation out of it?
  233. // If an image is read in from the icondatabase, we do *not* overwrite any image data that exists in the in-memory cache.
  234. // This is because we make the assumption that anything in memory is newer than whatever is in the database.
  235. // So the only time the data will be set from the second thread is when it is INITIALLY being read in from the database, but we would never
  236. // delete the image on the secondary thread if the image already exists.
  237. return iconRecord->image(size);
  238. }
  239. void IconDatabase::readIconForPageURLFromDisk(const String& pageURL)
  240. {
  241. // The effect of asking for an Icon for a pageURL automatically queues it to be read from disk
  242. // if it hasn't already been set in memory. The special IntSize (0, 0) is a special way of telling
  243. // that method "I don't care about the actual Image, i just want you to make sure you're getting it from disk.
  244. synchronousIconForPageURL(pageURL, IntSize(0, 0));
  245. }
  246. String IconDatabase::synchronousIconURLForPageURL(const String& pageURLOriginal)
  247. {
  248. ASSERT_NOT_SYNC_THREAD();
  249. // Cannot do anything with pageURLOriginal that would end up storing it without deep copying first
  250. // Also, in the case we have a real answer for the caller, we must deep copy that as well
  251. if (!isOpen() || !pageCanHaveIcon(pageURLOriginal))
  252. return String();
  253. MutexLocker locker(m_urlAndIconLock);
  254. PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURLOriginal);
  255. if (!pageRecord)
  256. pageRecord = getOrCreatePageURLRecord(pageURLOriginal.crossThreadString());
  257. // If pageRecord is NULL, one of two things is true -
  258. // 1 - The initial url import is incomplete and this pageURL has already been marked to be notified once it is complete if an iconURL exists
  259. // 2 - The initial url import IS complete and this pageURL has no icon
  260. if (!pageRecord)
  261. return String();
  262. // Possible the pageRecord is around because it's a retained pageURL with no iconURL, so we have to check
  263. return pageRecord->iconRecord() ? pageRecord->iconRecord()->iconURL().threadsafeCopy() : String();
  264. }
  265. #ifdef CAN_THEME_URL_ICON
  266. static inline void loadDefaultIconRecord(IconRecord* defaultIconRecord)
  267. {
  268. defaultIconRecord->loadImageFromResource("urlIcon");
  269. }
  270. #else
  271. static inline void loadDefaultIconRecord(IconRecord* defaultIconRecord)
  272. {
  273. static const unsigned char defaultIconData[] = { 0x4D, 0x4D, 0x00, 0x2A, 0x00, 0x00, 0x03, 0x32, 0x80, 0x00, 0x20, 0x50, 0x38, 0x24, 0x16, 0x0D, 0x07, 0x84, 0x42, 0x61, 0x50, 0xB8,
  274. 0x64, 0x08, 0x18, 0x0D, 0x0A, 0x0B, 0x84, 0xA2, 0xA1, 0xE2, 0x08, 0x5E, 0x39, 0x28, 0xAF, 0x48, 0x24, 0xD3, 0x53, 0x9A, 0x37, 0x1D, 0x18, 0x0E, 0x8A, 0x4B, 0xD1, 0x38,
  275. 0xB0, 0x7C, 0x82, 0x07, 0x03, 0x82, 0xA2, 0xE8, 0x6C, 0x2C, 0x03, 0x2F, 0x02, 0x82, 0x41, 0xA1, 0xE2, 0xF8, 0xC8, 0x84, 0x68, 0x6D, 0x1C, 0x11, 0x0A, 0xB7, 0xFA, 0x91,
  276. 0x6E, 0xD1, 0x7F, 0xAF, 0x9A, 0x4E, 0x87, 0xFB, 0x19, 0xB0, 0xEA, 0x7F, 0xA4, 0x95, 0x8C, 0xB7, 0xF9, 0xA9, 0x0A, 0xA9, 0x7F, 0x8C, 0x88, 0x66, 0x96, 0xD4, 0xCA, 0x69,
  277. 0x2F, 0x00, 0x81, 0x65, 0xB0, 0x29, 0x90, 0x7C, 0xBA, 0x2B, 0x21, 0x1E, 0x5C, 0xE6, 0xB4, 0xBD, 0x31, 0xB6, 0xE7, 0x7A, 0xBF, 0xDD, 0x6F, 0x37, 0xD3, 0xFD, 0xD8, 0xF2,
  278. 0xB6, 0xDB, 0xED, 0xAC, 0xF7, 0x03, 0xC5, 0xFE, 0x77, 0x53, 0xB6, 0x1F, 0xE6, 0x24, 0x8B, 0x1D, 0xFE, 0x26, 0x20, 0x9E, 0x1C, 0xE0, 0x80, 0x65, 0x7A, 0x18, 0x02, 0x01,
  279. 0x82, 0xC5, 0xA0, 0xC0, 0xF1, 0x89, 0xBA, 0x23, 0x30, 0xAD, 0x1F, 0xE7, 0xE5, 0x5B, 0x6D, 0xFE, 0xE7, 0x78, 0x3E, 0x1F, 0xEE, 0x97, 0x8B, 0xE7, 0x37, 0x9D, 0xCF, 0xE7,
  280. 0x92, 0x8B, 0x87, 0x0B, 0xFC, 0xA0, 0x8E, 0x68, 0x3F, 0xC6, 0x27, 0xA6, 0x33, 0xFC, 0x36, 0x5B, 0x59, 0x3F, 0xC1, 0x02, 0x63, 0x3B, 0x74, 0x00, 0x03, 0x07, 0x0B, 0x61,
  281. 0x00, 0x20, 0x60, 0xC9, 0x08, 0x00, 0x1C, 0x25, 0x9F, 0xE0, 0x12, 0x8A, 0xD5, 0xFE, 0x6B, 0x4F, 0x35, 0x9F, 0xED, 0xD7, 0x4B, 0xD9, 0xFE, 0x8A, 0x59, 0xB8, 0x1F, 0xEC,
  282. 0x56, 0xD3, 0xC1, 0xFE, 0x63, 0x4D, 0xF2, 0x83, 0xC6, 0xB6, 0x1B, 0xFC, 0x34, 0x68, 0x61, 0x3F, 0xC1, 0xA6, 0x25, 0xEB, 0xFC, 0x06, 0x58, 0x5C, 0x3F, 0xC0, 0x03, 0xE4,
  283. 0xC3, 0xFC, 0x04, 0x0F, 0x1A, 0x6F, 0xE0, 0xE0, 0x20, 0xF9, 0x61, 0x7A, 0x02, 0x28, 0x2B, 0xBC, 0x46, 0x25, 0xF3, 0xFC, 0x66, 0x3D, 0x99, 0x27, 0xF9, 0x7E, 0x6B, 0x1D,
  284. 0xC7, 0xF9, 0x2C, 0x5E, 0x1C, 0x87, 0xF8, 0xC0, 0x4D, 0x9A, 0xE7, 0xF8, 0xDA, 0x51, 0xB2, 0xC1, 0x68, 0xF2, 0x64, 0x1F, 0xE1, 0x50, 0xED, 0x0A, 0x04, 0x23, 0x79, 0x8A,
  285. 0x7F, 0x82, 0xA3, 0x39, 0x80, 0x7F, 0x80, 0xC2, 0xB1, 0x5E, 0xF7, 0x04, 0x2F, 0xB2, 0x10, 0x02, 0x86, 0x63, 0xC9, 0xCC, 0x07, 0xBF, 0x87, 0xF8, 0x4A, 0x38, 0xAF, 0xC1,
  286. 0x88, 0xF8, 0x66, 0x1F, 0xE1, 0xD9, 0x08, 0xD4, 0x8F, 0x25, 0x5B, 0x4A, 0x49, 0x97, 0x87, 0x39, 0xFE, 0x25, 0x12, 0x10, 0x68, 0xAA, 0x4A, 0x2F, 0x42, 0x29, 0x12, 0x69,
  287. 0x9F, 0xE1, 0xC1, 0x00, 0x67, 0x1F, 0xE1, 0x58, 0xED, 0x00, 0x83, 0x23, 0x49, 0x82, 0x7F, 0x81, 0x21, 0xE0, 0xFC, 0x73, 0x21, 0x00, 0x50, 0x7D, 0x2B, 0x84, 0x03, 0x83,
  288. 0xC2, 0x1B, 0x90, 0x06, 0x69, 0xFE, 0x23, 0x91, 0xAE, 0x50, 0x9A, 0x49, 0x32, 0xC2, 0x89, 0x30, 0xE9, 0x0A, 0xC4, 0xD9, 0xC4, 0x7F, 0x94, 0xA6, 0x51, 0xDE, 0x7F, 0x9D,
  289. 0x07, 0x89, 0xF6, 0x7F, 0x91, 0x85, 0xCA, 0x88, 0x25, 0x11, 0xEE, 0x50, 0x7C, 0x43, 0x35, 0x21, 0x60, 0xF1, 0x0D, 0x82, 0x62, 0x39, 0x07, 0x2C, 0x20, 0xE0, 0x80, 0x72,
  290. 0x34, 0x17, 0xA1, 0x80, 0xEE, 0xF0, 0x89, 0x24, 0x74, 0x1A, 0x2C, 0x93, 0xB3, 0x78, 0xCC, 0x52, 0x9D, 0x6A, 0x69, 0x56, 0xBB, 0x0D, 0x85, 0x69, 0xE6, 0x7F, 0x9E, 0x27,
  291. 0xB9, 0xFD, 0x50, 0x54, 0x47, 0xF9, 0xCC, 0x78, 0x9F, 0x87, 0xF9, 0x98, 0x70, 0xB9, 0xC2, 0x91, 0x2C, 0x6D, 0x1F, 0xE1, 0xE1, 0x00, 0xBF, 0x02, 0xC1, 0xF5, 0x18, 0x84,
  292. 0x01, 0xE1, 0x48, 0x8C, 0x42, 0x07, 0x43, 0xC9, 0x76, 0x7F, 0x8B, 0x04, 0xE4, 0xDE, 0x35, 0x95, 0xAB, 0xB0, 0xF0, 0x5C, 0x55, 0x23, 0xF9, 0x7E, 0x7E, 0x9F, 0xE4, 0x0C,
  293. 0xA7, 0x55, 0x47, 0xC7, 0xF9, 0xE6, 0xCF, 0x1F, 0xE7, 0x93, 0x35, 0x52, 0x54, 0x63, 0x19, 0x46, 0x73, 0x1F, 0xE2, 0x61, 0x08, 0xF0, 0x82, 0xE1, 0x80, 0x92, 0xF9, 0x20,
  294. 0xC0, 0x28, 0x18, 0x0A, 0x05, 0xA1, 0xA2, 0xF8, 0x6E, 0xDB, 0x47, 0x49, 0xFE, 0x3E, 0x17, 0xB6, 0x61, 0x13, 0x1A, 0x29, 0x26, 0xA9, 0xFE, 0x7F, 0x92, 0x70, 0x69, 0xFE,
  295. 0x4C, 0x2F, 0x55, 0x01, 0xF1, 0x54, 0xD4, 0x35, 0x49, 0x4A, 0x69, 0x59, 0x83, 0x81, 0x58, 0x76, 0x9F, 0xE2, 0x20, 0xD6, 0x4C, 0x9B, 0xA0, 0x48, 0x1E, 0x0B, 0xB7, 0x48,
  296. 0x58, 0x26, 0x11, 0x06, 0x42, 0xE8, 0xA4, 0x40, 0x17, 0x27, 0x39, 0x00, 0x60, 0x2D, 0xA4, 0xC3, 0x2C, 0x7F, 0x94, 0x56, 0xE4, 0xE1, 0x77, 0x1F, 0xE5, 0xB9, 0xD7, 0x66,
  297. 0x1E, 0x07, 0xB3, 0x3C, 0x63, 0x1D, 0x35, 0x49, 0x0E, 0x63, 0x2D, 0xA2, 0xF1, 0x12, 0x60, 0x1C, 0xE0, 0xE0, 0x52, 0x1B, 0x8B, 0xAC, 0x38, 0x0E, 0x07, 0x03, 0x60, 0x28,
  298. 0x1C, 0x0E, 0x87, 0x00, 0xF0, 0x66, 0x27, 0x11, 0xA2, 0xC1, 0x02, 0x5A, 0x1C, 0xE4, 0x21, 0x83, 0x1F, 0x13, 0x86, 0xFA, 0xD2, 0x55, 0x1D, 0xD6, 0x61, 0xBC, 0x77, 0xD3,
  299. 0xE6, 0x91, 0xCB, 0x4C, 0x90, 0xA6, 0x25, 0xB8, 0x2F, 0x90, 0xC5, 0xA9, 0xCE, 0x12, 0x07, 0x02, 0x91, 0x1B, 0x9F, 0x68, 0x00, 0x16, 0x76, 0x0D, 0xA1, 0x00, 0x08, 0x06,
  300. 0x03, 0x81, 0xA0, 0x20, 0x1A, 0x0D, 0x06, 0x80, 0x30, 0x24, 0x12, 0x89, 0x20, 0x98, 0x4A, 0x1F, 0x0F, 0x21, 0xA0, 0x9E, 0x36, 0x16, 0xC2, 0x88, 0xE6, 0x48, 0x9B, 0x83,
  301. 0x31, 0x1C, 0x55, 0x1E, 0x43, 0x59, 0x1A, 0x56, 0x1E, 0x42, 0xF0, 0xFA, 0x4D, 0x1B, 0x9B, 0x08, 0xDC, 0x5B, 0x02, 0xA1, 0x30, 0x7E, 0x3C, 0xEE, 0x5B, 0xA6, 0xDD, 0xB8,
  302. 0x6D, 0x5B, 0x62, 0xB7, 0xCD, 0xF3, 0x9C, 0xEA, 0x04, 0x80, 0x80, 0x00, 0x00, 0x0E, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x10, 0x00, 0x00, 0x01, 0x01,
  303. 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x10, 0x00, 0x00, 0x01, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x03, 0xE0, 0x01, 0x03, 0x00, 0x03, 0x00, 0x00,
  304. 0x00, 0x01, 0x00, 0x05, 0x00, 0x00, 0x01, 0x06, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x01, 0x11, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
  305. 0x00, 0x08, 0x01, 0x15, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x01, 0x16, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x01, 0x17,
  306. 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x29, 0x01, 0x1A, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xE8, 0x01, 0x1B, 0x00, 0x05, 0x00, 0x00,
  307. 0x00, 0x01, 0x00, 0x00, 0x03, 0xF0, 0x01, 0x1C, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x28, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02,
  308. 0x00, 0x00, 0x01, 0x52, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0A,
  309. 0xFC, 0x80, 0x00, 0x00, 0x27, 0x10, 0x00, 0x0A, 0xFC, 0x80, 0x00, 0x00, 0x27, 0x10 };
  310. DEFINE_STATIC_LOCAL(RefPtr<SharedBuffer>, defaultIconBuffer, (SharedBuffer::create(defaultIconData, sizeof(defaultIconData))));
  311. defaultIconRecord->setImageData(defaultIconBuffer);
  312. }
  313. #endif
  314. Image* IconDatabase::defaultIcon(const IntSize& size)
  315. {
  316. ASSERT_NOT_SYNC_THREAD();
  317. if (!m_defaultIconRecord) {
  318. m_defaultIconRecord = IconRecord::create("urlIcon");
  319. loadDefaultIconRecord(m_defaultIconRecord.get());
  320. }
  321. return m_defaultIconRecord->image(size);
  322. }
  323. void IconDatabase::retainIconForPageURL(const String& pageURLOriginal)
  324. {
  325. ASSERT_NOT_SYNC_THREAD();
  326. // Cannot do anything with pageURLOriginal that would end up storing it without deep copying first
  327. if (!isEnabled() || !pageCanHaveIcon(pageURLOriginal))
  328. return;
  329. MutexLocker locker(m_urlAndIconLock);
  330. PageURLRecord* record = m_pageURLToRecordMap.get(pageURLOriginal);
  331. String pageURL;
  332. if (!record) {
  333. pageURL = pageURLOriginal.crossThreadString();
  334. record = new PageURLRecord(pageURL);
  335. m_pageURLToRecordMap.set(pageURL, record);
  336. }
  337. if (!record->retain()) {
  338. if (pageURL.isNull())
  339. pageURL = pageURLOriginal.crossThreadString();
  340. // This page just had its retain count bumped from 0 to 1 - Record that fact
  341. m_retainedPageURLs.add(pageURL);
  342. // If we read the iconURLs yet, we want to avoid any pageURL->iconURL lookups and the pageURLsPendingDeletion is moot,
  343. // so we bail here and skip those steps
  344. if (!m_iconURLImportComplete)
  345. return;
  346. MutexLocker locker(m_pendingSyncLock);
  347. // If this pageURL waiting to be sync'ed, update the sync record
  348. // This saves us in the case where a page was ready to be deleted from the database but was just retained - so theres no need to delete it!
  349. if (!m_privateBrowsingEnabled && m_pageURLsPendingSync.contains(pageURL)) {
  350. LOG(IconDatabase, "Bringing %s back from the brink", pageURL.ascii().data());
  351. m_pageURLsPendingSync.set(pageURL, record->snapshot());
  352. }
  353. }
  354. }
  355. void IconDatabase::releaseIconForPageURL(const String& pageURLOriginal)
  356. {
  357. ASSERT_NOT_SYNC_THREAD();
  358. // Cannot do anything with pageURLOriginal that would end up storing it without deep copying first
  359. if (!isEnabled() || !pageCanHaveIcon(pageURLOriginal))
  360. return;
  361. MutexLocker locker(m_urlAndIconLock);
  362. // Check if this pageURL is actually retained
  363. if (!m_retainedPageURLs.contains(pageURLOriginal)) {
  364. LOG_ERROR("Attempting to release icon for URL %s which is not retained", urlForLogging(pageURLOriginal).ascii().data());
  365. return;
  366. }
  367. // Get its retain count - if it's retained, we'd better have a PageURLRecord for it
  368. PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURLOriginal);
  369. ASSERT(pageRecord);
  370. LOG(IconDatabase, "Releasing pageURL %s to a retain count of %i", urlForLogging(pageURLOriginal).ascii().data(), pageRecord->retainCount() - 1);
  371. ASSERT(pageRecord->retainCount() > 0);
  372. // If it still has a positive retain count, store the new count and bail
  373. if (pageRecord->release())
  374. return;
  375. // This pageRecord has now been fully released. Do the appropriate cleanup
  376. LOG(IconDatabase, "No more retainers for PageURL %s", urlForLogging(pageURLOriginal).ascii().data());
  377. m_pageURLToRecordMap.remove(pageURLOriginal);
  378. m_retainedPageURLs.remove(pageURLOriginal);
  379. // Grab the iconRecord for later use (and do a sanity check on it for kicks)
  380. IconRecord* iconRecord = pageRecord->iconRecord();
  381. ASSERT(!iconRecord || (iconRecord && m_iconURLToRecordMap.get(iconRecord->iconURL()) == iconRecord));
  382. {
  383. MutexLocker locker(m_pendingReadingLock);
  384. // Since this pageURL is going away, there's no reason anyone would ever be interested in its read results
  385. if (!m_iconURLImportComplete)
  386. m_pageURLsPendingImport.remove(pageURLOriginal);
  387. m_pageURLsInterestedInIcons.remove(pageURLOriginal);
  388. // If this icon is down to it's last retainer, we don't care about reading it in from disk anymore
  389. if (iconRecord && iconRecord->hasOneRef()) {
  390. m_iconURLToRecordMap.remove(iconRecord->iconURL());
  391. m_iconsPendingReading.remove(iconRecord);
  392. }
  393. }
  394. // Mark stuff for deletion from the database only if we're not in private browsing
  395. if (!m_privateBrowsingEnabled) {
  396. MutexLocker locker(m_pendingSyncLock);
  397. m_pageURLsPendingSync.set(pageURLOriginal.crossThreadString(), pageRecord->snapshot(true));
  398. // If this page is the last page to refer to a particular IconRecord, that IconRecord needs to
  399. // be marked for deletion
  400. if (iconRecord && iconRecord->hasOneRef())
  401. m_iconsPendingSync.set(iconRecord->iconURL(), iconRecord->snapshot(true));
  402. }
  403. delete pageRecord;
  404. if (isOpen())
  405. scheduleOrDeferSyncTimer();
  406. }
  407. void IconDatabase::setIconDataForIconURL(PassRefPtr<SharedBuffer> dataOriginal, const String& iconURLOriginal)
  408. {
  409. ASSERT_NOT_SYNC_THREAD();
  410. // Cannot do anything with dataOriginal or iconURLOriginal that would end up storing them without deep copying first
  411. if (!isOpen() || iconURLOriginal.isEmpty())
  412. return;
  413. RefPtr<SharedBuffer> data = dataOriginal ? dataOriginal->copy() : PassRefPtr<SharedBuffer>(0);
  414. String iconURL = iconURLOriginal.crossThreadString();
  415. Vector<String> pageURLs;
  416. {
  417. MutexLocker locker(m_urlAndIconLock);
  418. // If this icon was pending a read, remove it from that set because this new data should override what is on disk
  419. RefPtr<IconRecord> icon = m_iconURLToRecordMap.get(iconURL);
  420. if (icon) {
  421. MutexLocker locker(m_pendingReadingLock);
  422. m_iconsPendingReading.remove(icon.get());
  423. } else
  424. icon = getOrCreateIconRecord(iconURL);
  425. // Update the data and set the time stamp
  426. icon->setImageData(data.release());
  427. icon->setTimestamp((int)currentTime());
  428. // Copy the current retaining pageURLs - if any - to notify them of the change
  429. pageURLs.appendRange(icon->retainingPageURLs().begin(), icon->retainingPageURLs().end());
  430. // Mark the IconRecord as requiring an update to the database only if private browsing is disabled
  431. if (!m_privateBrowsingEnabled) {
  432. MutexLocker locker(m_pendingSyncLock);
  433. m_iconsPendingSync.set(iconURL, icon->snapshot());
  434. }
  435. if (icon->hasOneRef()) {
  436. ASSERT(icon->retainingPageURLs().isEmpty());
  437. LOG(IconDatabase, "Icon for icon url %s is about to be destroyed - removing mapping for it", urlForLogging(icon->iconURL()).ascii().data());
  438. m_iconURLToRecordMap.remove(icon->iconURL());
  439. }
  440. }
  441. // Send notification out regarding all PageURLs that retain this icon
  442. // But not if we're on the sync thread because that implies this mapping
  443. // comes from the initial import which we don't want notifications for
  444. if (!IS_ICON_SYNC_THREAD()) {
  445. // Start the timer to commit this change - or further delay the timer if it was already started
  446. scheduleOrDeferSyncTimer();
  447. // Informal testing shows that draining the autorelease pool every 25 iterations is about as low as we can go
  448. // before performance starts to drop off, but we don't want to increase this number because then accumulated memory usage will go up
  449. AutodrainedPool pool(25);
  450. for (unsigned i = 0; i < pageURLs.size(); ++i) {
  451. LOG(IconDatabase, "Dispatching notification that retaining pageURL %s has a new icon", urlForLogging(pageURLs[i]).ascii().data());
  452. m_client->didChangeIconForPageURL(pageURLs[i]);
  453. pool.cycle();
  454. }
  455. }
  456. }
  457. void IconDatabase::setIconURLForPageURL(const String& iconURLOriginal, const String& pageURLOriginal)
  458. {
  459. ASSERT_NOT_SYNC_THREAD();
  460. // Cannot do anything with iconURLOriginal or pageURLOriginal that would end up storing them without deep copying first
  461. ASSERT(!iconURLOriginal.isEmpty());
  462. if (!isOpen() || !pageCanHaveIcon(pageURLOriginal))
  463. return;
  464. String iconURL, pageURL;
  465. {
  466. MutexLocker locker(m_urlAndIconLock);
  467. PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURLOriginal);
  468. // If the urls already map to each other, bail.
  469. // This happens surprisingly often, and seems to cream iBench performance
  470. if (pageRecord && pageRecord->iconRecord() && pageRecord->iconRecord()->iconURL() == iconURLOriginal)
  471. return;
  472. pageURL = pageURLOriginal.crossThreadString();
  473. iconURL = iconURLOriginal.crossThreadString();
  474. if (!pageRecord) {
  475. pageRecord = new PageURLRecord(pageURL);
  476. m_pageURLToRecordMap.set(pageURL, pageRecord);
  477. }
  478. RefPtr<IconRecord> iconRecord = pageRecord->iconRecord();
  479. // Otherwise, set the new icon record for this page
  480. pageRecord->setIconRecord(getOrCreateIconRecord(iconURL));
  481. // If the current icon has only a single ref left, it is about to get wiped out.
  482. // Remove it from the in-memory records and don't bother reading it in from disk anymore
  483. if (iconRecord && iconRecord->hasOneRef()) {
  484. ASSERT(iconRecord->retainingPageURLs().size() == 0);
  485. LOG(IconDatabase, "Icon for icon url %s is about to be destroyed - removing mapping for it", urlForLogging(iconRecord->iconURL()).ascii().data());
  486. m_iconURLToRecordMap.remove(iconRecord->iconURL());
  487. MutexLocker locker(m_pendingReadingLock);
  488. m_iconsPendingReading.remove(iconRecord.get());
  489. }
  490. // And mark this mapping to be added to the database
  491. if (!m_privateBrowsingEnabled) {
  492. MutexLocker locker(m_pendingSyncLock);
  493. m_pageURLsPendingSync.set(pageURL, pageRecord->snapshot());
  494. // If the icon is on its last ref, mark it for deletion
  495. if (iconRecord && iconRecord->hasOneRef())
  496. m_iconsPendingSync.set(iconRecord->iconURL(), iconRecord->snapshot(true));
  497. }
  498. }
  499. // Since this mapping is new, send the notification out - but not if we're on the sync thread because that implies this mapping
  500. // comes from the initial import which we don't want notifications for
  501. if (!IS_ICON_SYNC_THREAD()) {
  502. // Start the timer to commit this change - or further delay the timer if it was already started
  503. scheduleOrDeferSyncTimer();
  504. LOG(IconDatabase, "Dispatching notification that we changed an icon mapping for url %s", urlForLogging(pageURL).ascii().data());
  505. AutodrainedPool pool;
  506. m_client->didChangeIconForPageURL(pageURL);
  507. }
  508. }
  509. IconLoadDecision IconDatabase::synchronousLoadDecisionForIconURL(const String& iconURL, DocumentLoader* notificationDocumentLoader)
  510. {
  511. ASSERT_NOT_SYNC_THREAD();
  512. if (!isOpen() || iconURL.isEmpty())
  513. return IconLoadNo;
  514. // If we have a IconRecord, it should also have its timeStamp marked because there is only two times when we create the IconRecord:
  515. // 1 - When we read the icon urls from disk, getting the timeStamp at the same time
  516. // 2 - When we get a new icon from the loader, in which case the timestamp is set at that time
  517. {
  518. MutexLocker locker(m_urlAndIconLock);
  519. if (IconRecord* icon = m_iconURLToRecordMap.get(iconURL)) {
  520. LOG(IconDatabase, "Found expiration time on a present icon based on existing IconRecord");
  521. return (int)currentTime() - icon->getTimestamp() > iconExpirationTime ? IconLoadYes : IconLoadNo;
  522. }
  523. }
  524. // If we don't have a record for it, but we *have* imported all iconURLs from disk, then we should load it now
  525. MutexLocker readingLocker(m_pendingReadingLock);
  526. if (m_iconURLImportComplete)
  527. return IconLoadYes;
  528. // Otherwise - since we refuse to perform I/O on the main thread to find out for sure - we return the answer that says
  529. // "You might be asked to load this later, so flag that"
  530. LOG(IconDatabase, "Don't know if we should load %s or not - adding %p to the set of document loaders waiting on a decision", iconURL.ascii().data(), notificationDocumentLoader);
  531. if (notificationDocumentLoader)
  532. m_loadersPendingDecision.add(notificationDocumentLoader);
  533. return IconLoadUnknown;
  534. }
  535. bool IconDatabase::synchronousIconDataKnownForIconURL(const String& iconURL)
  536. {
  537. ASSERT_NOT_SYNC_THREAD();
  538. MutexLocker locker(m_urlAndIconLock);
  539. if (IconRecord* icon = m_iconURLToRecordMap.get(iconURL))
  540. return icon->imageDataStatus() != ImageDataStatusUnknown;
  541. return false;
  542. }
  543. void IconDatabase::setEnabled(bool enabled)
  544. {
  545. ASSERT_NOT_SYNC_THREAD();
  546. if (!enabled && isOpen())
  547. close();
  548. m_isEnabled = enabled;
  549. }
  550. bool IconDatabase::isEnabled() const
  551. {
  552. ASSERT_NOT_SYNC_THREAD();
  553. return m_isEnabled;
  554. }
  555. void IconDatabase::setPrivateBrowsingEnabled(bool flag)
  556. {
  557. m_privateBrowsingEnabled = flag;
  558. }
  559. bool IconDatabase::isPrivateBrowsingEnabled() const
  560. {
  561. return m_privateBrowsingEnabled;
  562. }
  563. void IconDatabase::delayDatabaseCleanup()
  564. {
  565. ++databaseCleanupCounter;
  566. if (databaseCleanupCounter == 1)
  567. LOG(IconDatabase, "Database cleanup is now DISABLED");
  568. }
  569. void IconDatabase::allowDatabaseCleanup()
  570. {
  571. if (--databaseCleanupCounter < 0)
  572. databaseCleanupCounter = 0;
  573. if (databaseCleanupCounter == 0)
  574. LOG(IconDatabase, "Database cleanup is now ENABLED");
  575. }
  576. void IconDatabase::checkIntegrityBeforeOpening()
  577. {
  578. checkIntegrityOnOpen = true;
  579. }
  580. size_t IconDatabase::pageURLMappingCount()
  581. {
  582. MutexLocker locker(m_urlAndIconLock);
  583. return m_pageURLToRecordMap.size();
  584. }
  585. size_t IconDatabase::retainedPageURLCount()
  586. {
  587. MutexLocker locker(m_urlAndIconLock);
  588. return m_retainedPageURLs.size();
  589. }
  590. size_t IconDatabase::iconRecordCount()
  591. {
  592. MutexLocker locker(m_urlAndIconLock);
  593. return m_iconURLToRecordMap.size();
  594. }
  595. size_t IconDatabase::iconRecordCountWithData()
  596. {
  597. MutexLocker locker(m_urlAndIconLock);
  598. size_t result = 0;
  599. HashMap<String, IconRecord*>::iterator i = m_iconURLToRecordMap.begin();
  600. HashMap<String, IconRecord*>::iterator end = m_iconURLToRecordMap.end();
  601. for (; i != end; ++i)
  602. result += ((*i).second->imageDataStatus() == ImageDataStatusPresent);
  603. return result;
  604. }
  605. IconDatabase::IconDatabase()
  606. : m_syncTimer(this, &IconDatabase::syncTimerFired)
  607. , m_syncThreadRunning(false)
  608. , m_isEnabled(false)
  609. , m_privateBrowsingEnabled(false)
  610. , m_threadTerminationRequested(false)
  611. , m_removeIconsRequested(false)
  612. , m_iconURLImportComplete(false)
  613. , m_disabledSuddenTerminationForSyncThread(false)
  614. , m_initialPruningComplete(false)
  615. , m_client(defaultClient())
  616. , m_imported(false)
  617. , m_isImportedSet(false)
  618. {
  619. LOG(IconDatabase, "Creating IconDatabase %p", this);
  620. ASSERT(isMainThread());
  621. }
  622. IconDatabase::~IconDatabase()
  623. {
  624. ASSERT_NOT_REACHED();
  625. }
  626. void IconDatabase::notifyPendingLoadDecisionsOnMainThread(void* context)
  627. {
  628. static_cast<IconDatabase*>(context)->notifyPendingLoadDecisions();
  629. }
  630. void IconDatabase::notifyPendingLoadDecisions()
  631. {
  632. ASSERT_NOT_SYNC_THREAD();
  633. // This method should only be called upon completion of the initial url import from the database
  634. ASSERT(m_iconURLImportComplete);
  635. LOG(IconDatabase, "Notifying all DocumentLoaders that were waiting on a load decision for thier icons");
  636. HashSet<RefPtr<DocumentLoader> >::iterator i = m_loadersPendingDecision.begin();
  637. HashSet<RefPtr<DocumentLoader> >::iterator end = m_loadersPendingDecision.end();
  638. for (; i != end; ++i)
  639. if ((*i)->refCount() > 1)
  640. (*i)->iconLoadDecisionAvailable();
  641. m_loadersPendingDecision.clear();
  642. }
  643. void IconDatabase::wakeSyncThread()
  644. {
  645. MutexLocker locker(m_syncLock);
  646. if (!m_disabledSuddenTerminationForSyncThread) {
  647. m_disabledSuddenTerminationForSyncThread = true;
  648. // The following is balanced by the call to enableSuddenTermination in the
  649. // syncThreadMainLoop function.
  650. // FIXME: It would be better to only disable sudden termination if we have
  651. // something to write, not just if we have something to read.
  652. disableSuddenTermination();
  653. }
  654. m_syncCondition.signal();
  655. }
  656. void IconDatabase::scheduleOrDeferSyncTimer()
  657. {
  658. ASSERT_NOT_SYNC_THREAD();
  659. if (!m_syncTimer.isActive()) {
  660. // The following is balanced by the call to enableSuddenTermination in the
  661. // syncTimerFired function.
  662. disableSuddenTermination();
  663. }
  664. m_syncTimer.startOneShot(updateTimerDelay);
  665. }
  666. void IconDatabase::syncTimerFired(Timer<IconDatabase>*)
  667. {
  668. ASSERT_NOT_SYNC_THREAD();
  669. wakeSyncThread();
  670. // The following is balanced by the call to disableSuddenTermination in the
  671. // scheduleOrDeferSyncTimer function.
  672. enableSuddenTermination();
  673. }
  674. // ******************
  675. // *** Any Thread ***
  676. // ******************
  677. bool IconDatabase::isOpen() const
  678. {
  679. MutexLocker locker(m_syncLock);
  680. return m_syncDB.isOpen();
  681. }
  682. String IconDatabase::databasePath() const
  683. {
  684. MutexLocker locker(m_syncLock);
  685. return m_completeDatabasePath.threadsafeCopy();
  686. }
  687. String IconDatabase::defaultDatabaseFilename()
  688. {
  689. DEFINE_STATIC_LOCAL(String, defaultDatabaseFilename, ("WebpageIcons.db"));
  690. return defaultDatabaseFilename.threadsafeCopy();
  691. }
  692. // Unlike getOrCreatePageURLRecord(), getOrCreateIconRecord() does not mark the icon as "interested in import"
  693. PassRefPtr<IconRecord> IconDatabase::getOrCreateIconRecord(const String& iconURL)
  694. {
  695. // Clients of getOrCreateIconRecord() are required to acquire the m_urlAndIconLock before calling this method
  696. ASSERT(!m_urlAndIconLock.tryLock());
  697. if (IconRecord* icon = m_iconURLToRecordMap.get(iconURL))
  698. return icon;
  699. RefPtr<IconRecord> newIcon = IconRecord::create(iconURL);
  700. m_iconURLToRecordMap.set(iconURL, newIcon.get());
  701. return newIcon.release();
  702. }
  703. // This method retrieves the existing PageURLRecord, or creates a new one and marks it as "interested in the import" for later notification
  704. PageURLRecord* IconDatabase::getOrCreatePageURLRecord(const String& pageURL)
  705. {
  706. // Clients of getOrCreatePageURLRecord() are required to acquire the m_urlAndIconLock before calling this method
  707. ASSERT(!m_urlAndIconLock.tryLock());
  708. if (!pageCanHaveIcon(pageURL))
  709. return 0;
  710. PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURL);
  711. MutexLocker locker(m_pendingReadingLock);
  712. if (!m_iconURLImportComplete) {
  713. // If the initial import of all URLs hasn't completed and we have no page record, we assume we *might* know about this later and create a record for it
  714. if (!pageRecord) {
  715. LOG(IconDatabase, "Creating new PageURLRecord for pageURL %s", urlForLogging(pageURL).ascii().data());
  716. pageRecord = new PageURLRecord(pageURL);
  717. m_pageURLToRecordMap.set(pageURL, pageRecord);
  718. }
  719. // If the pageRecord for this page does not have an iconRecord attached to it, then it is a new pageRecord still awaiting the initial import
  720. // Mark the URL as "interested in the result of the import" then bail
  721. if (!pageRecord->iconRecord()) {
  722. m_pageURLsPendingImport.add(pageURL);
  723. return 0;
  724. }
  725. }
  726. // We've done the initial import of all URLs known in the database. If this record doesn't exist now, it never will
  727. return pageRecord;
  728. }
  729. // ************************
  730. // *** Sync Thread Only ***
  731. // ************************
  732. void IconDatabase::importIconURLForPageURL(const String& iconURL, const String& pageURL)
  733. {
  734. ASSERT_ICON_SYNC_THREAD();
  735. // This function is only for setting actual existing url mappings so assert that neither of these URLs are empty
  736. ASSERT(!iconURL.isEmpty());
  737. ASSERT(!pageURL.isEmpty());
  738. ASSERT(pageCanHaveIcon(pageURL));
  739. setIconURLForPageURLInSQLDatabase(iconURL, pageURL);
  740. }
  741. void IconDatabase::importIconDataForIconURL(PassRefPtr<SharedBuffer> data, const String& iconURL)
  742. {
  743. ASSERT_ICON_SYNC_THREAD();
  744. ASSERT(!iconURL.isEmpty());
  745. writeIconSnapshotToSQLDatabase(IconSnapshot(iconURL, (int)currentTime(), data.get()));
  746. }
  747. bool IconDatabase::shouldStopThreadActivity() const
  748. {
  749. ASSERT_ICON_SYNC_THREAD();
  750. return m_threadTerminationRequested || m_removeIconsRequested;
  751. }
  752. void* IconDatabase::iconDatabaseSyncThreadStart(void* vIconDatabase)
  753. {
  754. IconDatabase* iconDB = static_cast<IconDatabase*>(vIconDatabase);
  755. return iconDB->iconDatabaseSyncThread();
  756. }
  757. void* IconDatabase::iconDatabaseSyncThread()
  758. {
  759. // The call to create this thread might not complete before the thread actually starts, so we might fail this ASSERT_ICON_SYNC_THREAD() because the pointer
  760. // to our thread structure hasn't been filled in yet.
  761. // To fix this, the main thread acquires this lock before creating us, then releases the lock after creation is complete. A quick lock/unlock cycle here will
  762. // prevent us from running before that call completes
  763. m_syncLock.lock();
  764. m_syncLock.unlock();
  765. ASSERT_ICON_SYNC_THREAD();
  766. LOG(IconDatabase, "(THREAD) IconDatabase sync thread started");
  767. #ifndef NDEBUG
  768. double startTime = currentTime();
  769. #endif
  770. // Need to create the database path if it doesn't already exist
  771. makeAllDirectories(m_databaseDirectory);
  772. // Existence of a journal file is evidence of a previous crash/force quit and automatically qualifies
  773. // us to do an integrity check
  774. String journalFilename = m_completeDatabasePath + "-journal";
  775. if (!checkIntegrityOnOpen) {
  776. AutodrainedPool pool;
  777. checkIntegrityOnOpen = fileExists(journalFilename);
  778. }
  779. {
  780. MutexLocker locker(m_syncLock);
  781. if (!m_syncDB.open(m_completeDatabasePath)) {
  782. LOG_ERROR("Unable to open icon database at path %s - %s", m_completeDatabasePath.ascii().data(), m_syncDB.lastErrorMsg());
  783. return 0;
  784. }
  785. }
  786. if (shouldStopThreadActivity())
  787. return syncThreadMainLoop();
  788. #ifndef NDEBUG
  789. double timeStamp = currentTime();
  790. LOG(IconDatabase, "(THREAD) Open took %.4f seconds", timeStamp - startTime);
  791. #endif
  792. performOpenInitialization();
  793. if (shouldStopThreadActivity())
  794. return syncThreadMainLoop();
  795. #ifndef NDEBUG
  796. double newStamp = currentTime();
  797. LOG(IconDatabase, "(THREAD) performOpenInitialization() took %.4f seconds, now %.4f seconds from thread start", newStamp - timeStamp, newStamp - startTime);
  798. timeStamp = newStamp;
  799. #endif
  800. if (!imported()) {
  801. LOG(IconDatabase, "(THREAD) Performing Safari2 import procedure");
  802. SQLiteTransaction importTransaction(m_syncDB);
  803. importTransaction.begin();
  804. // Commit the transaction only if the import completes (the import should be atomic)
  805. if (m_client->performImport()) {
  806. setImported(true);
  807. importTransaction.commit();
  808. } else {
  809. LOG(IconDatabase, "(THREAD) Safari 2 import was cancelled");
  810. importTransaction.rollback();
  811. }
  812. if (shouldStopThreadActivity())
  813. return syncThreadMainLoop();
  814. #ifndef NDEBUG
  815. newStamp = currentTime();
  816. LOG(IconDatabase, "(THREAD) performImport() took %.4f seconds, now %.4f seconds from thread start", newStamp - timeStamp, newStamp - startTime);
  817. timeStamp = newStamp;
  818. #endif
  819. }
  820. // Uncomment the following line to simulate a long lasting URL import (*HUGE* icon databases, or network home directories)
  821. // while (currentTime() - timeStamp < 10);
  822. // Read in URL mappings from the database
  823. LOG(IconDatabase, "(THREAD) Starting iconURL import");
  824. performURLImport();
  825. if (shouldStopThreadActivity())
  826. return syncThreadMainLoop();
  827. #ifndef NDEBUG
  828. newStamp = currentTime();
  829. LOG(IconDatabase, "(THREAD) performURLImport() took %.4f seconds. Entering main loop %.4f seconds from thread start", newStamp - timeStamp, newStamp - startTime);
  830. #endif
  831. LOG(IconDatabase, "(THREAD) Beginning sync");
  832. return syncThreadMainLoop();
  833. }
  834. static int databaseVersionNumber(SQLiteDatabase& db)
  835. {
  836. return SQLiteStatement(db, "SELECT value FROM IconDatabaseInfo WHERE key = 'Version';").getColumnInt(0);
  837. }
  838. static bool isValidDatabase(SQLiteDatabase& db)
  839. {
  840. // These four tables should always exist in a valid db
  841. if (!db.tableExists("IconInfo") || !db.tableExists("IconData") || !db.tableExists("PageURL") || !db.tableExists("IconDatabaseInfo"))
  842. return false;
  843. if (databaseVersionNumber(db) < currentDatabaseVersion) {
  844. LOG(IconDatabase, "DB version is not found or below expected valid version");
  845. return false;
  846. }
  847. return true;
  848. }
  849. static void createDatabaseTables(SQLiteDatabase& db)
  850. {
  851. if (!db.executeCommand("CREATE TABLE PageURL (url TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,iconID INTEGER NOT NULL ON CONFLICT FAIL);")) {
  852. LOG_ERROR("Could not create PageURL table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
  853. db.close();
  854. return;
  855. }
  856. if (!db.executeCommand("CREATE INDEX PageURLIndex ON PageURL (url);")) {
  857. LOG_ERROR("Could not create PageURL index in database (%i) - %s", db.lastError(), db.lastErrorMsg());
  858. db.close();
  859. return;
  860. }
  861. if (!db.executeCommand("CREATE TABLE IconInfo (iconID INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE ON CONFLICT REPLACE, url TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT FAIL, stamp INTEGER);")) {
  862. LOG_ERROR("Could not create IconInfo table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
  863. db.close();
  864. return;
  865. }
  866. if (!db.executeCommand("CREATE INDEX IconInfoIndex ON IconInfo (url, iconID);")) {
  867. LOG_ERROR("Could not create PageURL index in database (%i) - %s", db.lastError(), db.lastErrorMsg());
  868. db.close();
  869. return;
  870. }
  871. if (!db.executeCommand("CREATE TABLE IconData (iconID INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE ON CONFLICT REPLACE, data BLOB);")) {
  872. LOG_ERROR("Could not create IconData table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
  873. db.close();
  874. return;
  875. }
  876. if (!db.executeCommand("CREATE INDEX IconDataIndex ON IconData (iconID);")) {
  877. LOG_ERROR("Could not create PageURL index in database (%i) - %s", db.lastError(), db.lastErrorMsg());
  878. db.close();
  879. return;
  880. }
  881. if (!db.executeCommand("CREATE TABLE IconDatabaseInfo (key TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,value TEXT NOT NULL ON CONFLICT FAIL);")) {
  882. LOG_ERROR("Could not create IconDatabaseInfo table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
  883. db.close();
  884. return;
  885. }
  886. if (!db.executeCommand(String("INSERT INTO IconDatabaseInfo VALUES ('Version', ") + String::number(currentDatabaseVersion) + ");")) {
  887. LOG_ERROR("Could not insert icon database version into IconDatabaseInfo table (%i) - %s", db.lastError(), db.lastErrorMsg());
  888. db.close();
  889. return;
  890. }
  891. }
  892. void IconDatabase::performOpenInitialization()
  893. {
  894. ASSERT_ICON_SYNC_THREAD();
  895. if (!isOpen())
  896. return;
  897. if (checkIntegrityOnOpen) {
  898. checkIntegrityOnOpen = false;
  899. if (!checkIntegrity()) {
  900. LOG(IconDatabase, "Integrity check was bad - dumping IconDatabase");
  901. m_syncDB.close();
  902. {
  903. MutexLocker locker(m_syncLock);
  904. // Should've been consumed by SQLite, delete just to make sure we don't see it again in the future;
  905. deleteFile(m_completeDatabasePath + "-journal");
  906. deleteFile(m_completeDatabasePath);
  907. }
  908. // Reopen the write database, creating it from scratch
  909. if (!m_syncDB.open(m_completeDatabasePath)) {
  910. LOG_ERROR("Unable to open icon database at path %s - %s", m_completeDatabasePath.ascii().data(), m_syncDB.lastErrorMsg());
  911. return;
  912. }
  913. }
  914. }
  915. int version = databaseVersionNumber(m_syncDB);
  916. if (version > currentDatabaseVersion) {
  917. LOG(IconDatabase, "Database version number %i is greater than our current version number %i - closing the database to prevent overwriting newer versions", version, currentDatabaseVersion);
  918. m_syncDB.close();
  919. m_threadTerminationRequested = true;
  920. return;
  921. }
  922. if (!isValidDatabase(m_syncDB)) {
  923. LOG(IconDatabase, "%s is missing or in an invalid state - reconstructing", m_completeDatabasePath.ascii().data());
  924. m_syncDB.clearAllTables();
  925. createDatabaseTables(m_syncDB);
  926. }
  927. // Reduce sqlite RAM cache size from default 2000 pages (~1.5kB per page). 3MB of cache for icon database is overkill
  928. if (!SQLiteStatement(m_syncDB, "PRAGMA cache_size = 200;").executeCommand())
  929. LOG_ERROR("SQLite database could not set cache_size");
  930. // Tell backup software (i.e., Time Machine) to never back up the icon database, because
  931. // it's a large file that changes frequently, thus using a lot of backup disk space, and
  932. // it's unlikely that many users would be upset about it not being backed up. We could
  933. // make this configurable on a per-client basis some day if some clients don't want this.
  934. if (canExcludeFromBackup() && !wasExcludedFromBackup() && excludeFromBackup(m_completeDatabasePath))
  935. setWasExcludedFromBackup();
  936. }
  937. bool IconDatabase::checkIntegrity()
  938. {
  939. ASSERT_ICON_SYNC_THREAD();
  940. SQLiteStatement integrity(m_syncDB, "PRAGMA integrity_check;");
  941. if (integrity.prepare() != SQLResultOk) {
  942. LOG_ERROR("checkIntegrity failed to execute");
  943. return false;
  944. }
  945. int resultCode = integrity.step();
  946. if (resultCode == SQLResultOk)
  947. return true;
  948. if (resultCode != SQLResultRow)
  949. return false;
  950. int columns = integrity.columnCount();
  951. if (columns != 1) {
  952. LOG_ERROR("Received %i columns performing integrity check, should be 1", columns);
  953. return false;
  954. }
  955. String resultText = integrity.getColumnText(0);
  956. // A successful, no-error integrity check will be "ok" - all other strings imply failure
  957. if (resultText == "ok")
  958. return true;
  959. LOG_ERROR("Icon database integrity check failed - \n%s", resultText.ascii().data());
  960. return false;
  961. }
  962. void IconDatabase::performURLImport()
  963. {
  964. ASSERT_ICON_SYNC_THREAD();
  965. SQLiteStatement query(m_syncDB, "SELECT PageURL.url, IconInfo.url, IconInfo.stamp FROM PageURL INNER JOIN IconInfo ON PageURL.iconID=IconInfo.iconID;");
  966. if (query.prepare() != SQLResultOk) {
  967. LOG_ERROR("Unable to prepare icon url import query");
  968. return;
  969. }
  970. // Informal testing shows that draining the autorelease pool every 25 iterations is about as low as we can go
  971. // before performance starts to drop off, but we don't want to increase this number because then accumulated memory usage will go up
  972. AutodrainedPool pool(25);
  973. int result = query.step();
  974. while (result == SQLResultRow) {
  975. String pageURL = query.getColumnText(0);
  976. String iconURL = query.getColumnText(1);
  977. {
  978. MutexLocker locker(m_urlAndIconLock);
  979. PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURL);
  980. // If the pageRecord doesn't exist in this map, then no one has retained this pageURL
  981. // If the s_databaseCleanupCounter count is non-zero, then we're not supposed to be pruning the database in any manner,
  982. // so go ahead and actually create a pageURLRecord for this url even though it's not retained.
  983. // If database cleanup *is* allowed, we don't want to bother pulling in a page url from disk that noone is actually interested
  984. // in - we'll prune it later instead!
  985. if (!pageRecord && databaseCleanupCounter && pageCanHaveIcon(pageURL)) {
  986. pageRecord = new PageURLRecord(pageURL);
  987. m_pageURLToRecordMap.set(pageURL, pageRecord);
  988. }
  989. if (pageRecord) {
  990. IconRecord* currentIcon = pageRecord->iconRecord();
  991. if (!currentIcon || currentIcon->iconURL() != iconURL) {
  992. pageRecord->setIconRecord(getOrCreateIconRecord(iconURL));
  993. currentIcon = pageRecord->iconRecord();
  994. }
  995. // Regardless, the time stamp from disk still takes precedence. Until we read this icon from disk, we didn't think we'd seen it before
  996. // so we marked the timestamp as "now", but it's really much older
  997. currentIcon->setTimestamp(query.getColumnInt(2));
  998. }
  999. }
  1000. // FIXME: Currently the WebKit API supports 1 type of notification that is sent whenever we get an Icon URL for a Page URL. We might want to re-purpose it to work for
  1001. // getting the actually icon itself also (so each pageurl would get this notification twice) or we might want to add a second type of notification -
  1002. // one for the URL and one for the Image itself
  1003. // Note that WebIconDatabase is not neccessarily API so we might be able to make this change
  1004. {
  1005. MutexLocker locker(m_pendingReadingLock);
  1006. if (m_pageURLsPendingImport.contains(pageURL)) {
  1007. dispatchDidImportIconURLForPageURLOnMainThread(pageURL);
  1008. m_pageURLsPendingImport.remove(pageURL);
  1009. pool.cycle();
  1010. }
  1011. }
  1012. // Stop the import at any time of the thread has been asked to shutdown
  1013. if (shouldStopThreadActivity()) {
  1014. LOG(IconDatabase, "IconDatabase asked to terminate during performURLImport()");
  1015. return;
  1016. }
  1017. result = query.step();
  1018. }
  1019. if (result != SQLResultDone)
  1020. LOG(IconDatabase, "Error reading page->icon url mappings from database");
  1021. // Clear the m_pageURLsPendingImport set - either the page URLs ended up with an iconURL (that we'll notify about) or not,
  1022. // but after m_iconURLImportComplete is set to true, we don't care about this set anymore
  1023. Vector<String> urls;
  1024. {
  1025. MutexLocker locker(m_pendingReadingLock);
  1026. urls.appendRange(m_pageURLsPendingImport.begin(), m_pageURLsPendingImport.end());
  1027. m_pageURLsPendingImport.clear();
  1028. m_iconURLImportComplete = true;
  1029. }
  1030. Vector<String> urlsToNotify;
  1031. // Loop through the urls pending import
  1032. // Remove unretained ones if database cleanup is allowed
  1033. // Keep a set of ones that are retained and pending notification
  1034. {
  1035. MutexLocker locker(m_urlAndIconLock);
  1036. for (unsigned i = 0; i < urls.size(); ++i) {
  1037. if (!m_retainedPageURLs.contains(urls[i])) {
  1038. PageURLRecord* record = m_pageURLToRecordMap.get(urls[i]);
  1039. if (record && !databaseCleanupCounter) {
  1040. m_pageURLToRecordMap.remove(urls[i]);
  1041. IconRecord* iconRecord = record->iconRecord();
  1042. // If this page is the only remaining retainer of its icon, mark that icon for deletion and don't bother
  1043. // reading anything related to it
  1044. if (iconRecord && iconRecord->hasOneRef()) {
  1045. m_iconURLToRecordMap.remove(iconRecord->iconURL());
  1046. {
  1047. MutexLocker locker(m_pendingReadingLock);
  1048. m_pageURLsInterestedInIcons.remove(urls[i]);
  1049. m_iconsPendingReading.remove(iconRecord);
  1050. }
  1051. {
  1052. MutexLocker locker(m_pendingSyncLock);
  1053. m_iconsPendingSync.set(iconRecord->iconURL(), iconRecord->snapshot(true));
  1054. }
  1055. }
  1056. delete record;
  1057. }
  1058. } else {
  1059. urlsToNotify.append(urls[i]);
  1060. }
  1061. }
  1062. }
  1063. LOG(IconDatabase, "Notifying %lu interested page URLs that their icon URL is known due to the import", static_cast<unsigned long>(urlsToNotify.size()));
  1064. // Now that we don't hold any locks, perform the actual notifications
  1065. for (unsigned i = 0; i < urlsToNotify.size(); ++i) {
  1066. LOG(IconDatabase, "Notifying icon info known for pageURL %s", urlsToNotify[i].ascii().data());
  1067. dispatchDidImportIconURLForPageURLOnMainThread(urlsToNotify[i]);
  1068. if (shouldStopThreadActivity())
  1069. return;
  1070. pool.cycle();
  1071. }
  1072. // Notify the client that the URL import is complete in case it's managing its own pending notifications.
  1073. dispatchDidFinishURLImportOnMainThread();
  1074. // Notify all DocumentLoaders that were waiting for an icon load decision on the main thread
  1075. callOnMainThread(notifyPendingLoadDecisionsOnMainThread, this);
  1076. }
  1077. void* IconDatabase::syncThreadMainLoop()
  1078. {
  1079. ASSERT_ICON_SYNC_THREAD();
  1080. bool shouldReenableSuddenTermination = false;
  1081. m_syncLock.lock();
  1082. // It's possible thread termination is requested before the main loop even starts - in that case, just skip straight to cleanup
  1083. while (!m_threadTerminationRequested) {
  1084. m_syncLock.unlock();
  1085. #ifndef NDEBUG
  1086. double timeStamp = currentTime();
  1087. #endif
  1088. LOG(IconDatabase, "(THREAD) Main work loop starting");
  1089. // If we should remove all icons, do it now. This is an uninteruptible procedure that we will always do before quitting if it is requested
  1090. if (m_removeIconsRequested) {
  1091. removeAllIconsOnThread();
  1092. m_removeIconsRequested = false;
  1093. }
  1094. // Then, if the thread should be quitting, quit now!
  1095. if (m_threadTerminationRequested)
  1096. break;
  1097. bool didAnyWork = true;
  1098. while (didAnyWork) {
  1099. bool didWrite = writeToDatabase();
  1100. if (shouldStopThreadActivity())
  1101. break;
  1102. didAnyWork = readFromDatabase();
  1103. if (shouldStopThreadActivity())
  1104. break;
  1105. // Prune unretained icons after the first time we sync anything out to the database
  1106. // This way, pruning won't be the only operation we perform to the database by itself
  1107. // We also don't want to bother doing this if the thread should be terminating (the user is quitting)
  1108. // or if private browsing is enabled
  1109. // We also don't want to prune if the m_databaseCleanupCounter count is non-zero - that means someone
  1110. // has asked to delay pruning
  1111. static bool prunedUnretainedIcons = false;
  1112. if (didWrite && !m_privateBrowsingEnabled && !prunedUnretainedIcons && !databaseCleanupCounter) {
  1113. #ifndef NDEBUG
  1114. double time = currentTime();
  1115. #endif
  1116. LOG(IconDatabase, "(THREAD) Starting pruneUnretainedIcons()");
  1117. pruneUnretainedIcons();
  1118. LOG(IconDatabase, "(THREAD) pruneUnretainedIcons() took %.4f seconds", currentTime() - time);
  1119. // If pruneUnretainedIcons() returned early due to requested thread termination, its still okay
  1120. // to mark prunedUnretainedIcons true because we're about to terminate anyway
  1121. prunedUnretainedIcons = true;
  1122. }
  1123. didAnyWork = didAnyWork || didWrite;
  1124. if (shouldStopThreadActivity())
  1125. break;
  1126. }
  1127. #ifndef NDEBUG
  1128. double newstamp = currentTime();
  1129. LOG(IconDatabase, "(THREAD) Main work loop ran for %.4f seconds, %s requested to terminate", newstamp - timeStamp, shouldStopThreadActivity() ? "was" : "was not");
  1130. #endif
  1131. m_syncLock.lock();
  1132. // There is some condition that is asking us to stop what we're doing now and handle a special case
  1133. // This is either removing all icons, or shutting down the thread to quit the app
  1134. // We handle those at the top of this main loop so continue to jump back up there
  1135. if (shouldStopThreadActivity())
  1136. continue;
  1137. if (shouldReenableSuddenTermination) {
  1138. // The following is balanced by the call to disableSuddenTermination in the
  1139. // wakeSyncThread function. Any time we wait on the condition, we also have
  1140. // to enableSuddenTermation, after doing the next batch of work.
  1141. ASSERT(m_disabledSuddenTerminationForSyncThread);
  1142. enableSuddenTermination();
  1143. m_disabledSuddenTerminationForSyncThread = false;
  1144. }
  1145. m_syncCondition.wait(m_syncLock);
  1146. shouldReenableSuddenTermination = true;
  1147. }
  1148. m_syncLock.unlock();
  1149. // Thread is terminating at this point
  1150. cleanupSyncThread();
  1151. if (shouldReenableSuddenTermination) {
  1152. // The following is balanced by the call to disableSuddenTermination in the
  1153. // wakeSyncThread function. Any time we wait on the condition, we also have
  1154. // to enableSuddenTermation, after doing the next batch of work.
  1155. ASSERT(m_disabledSuddenTerminationForSyncThread);
  1156. enableSuddenTermination();
  1157. m_disabledSuddenTerminationForSyncThread = false;
  1158. }
  1159. return 0;
  1160. }
  1161. bool IconDatabase::readFromDatabase()
  1162. {
  1163. ASSERT_ICON_SYNC_THREAD();
  1164. #ifndef NDEBUG
  1165. double timeStamp = currentTime();
  1166. #endif
  1167. bool didAnyWork = false;
  1168. // We'll make a copy of the sets of things that need to be read. Then we'll verify at the time of updating the record that it still wants to be updated
  1169. // This way we won't hold the lock for a long period of time
  1170. Vector<IconRecord*> icons;
  1171. {
  1172. MutexLocker locker(m_pendingReadingLock);
  1173. icons.appendRange(m_iconsPendingReading.begin(), m_iconsPendingReading.end());
  1174. }
  1175. // Keep track of icons we actually read to notify them of the new icon
  1176. HashSet<String> urlsToNotify;
  1177. for (unsigned i = 0; i < icons.size(); ++i) {
  1178. didAnyWork = true;
  1179. RefPtr<SharedBuffer> imageData = getImageDataForIconURLFromSQLDatabase(icons[i]->iconURL());
  1180. // Verify this icon still wants to be read from disk
  1181. {
  1182. MutexLocker urlLocker(m_urlAndIconLock);
  1183. {
  1184. MutexLocker readLocker(m_pendingReadingLock);
  1185. if (m_iconsPendingReading.contains(icons[i])) {
  1186. // Set the new data
  1187. icons[i]->setImageData(imageData.release());
  1188. // Remove this icon from the set that needs to be read
  1189. m_iconsPendingReading.remove(icons[i]);
  1190. // We have a set of all Page URLs that retain this icon as well as all PageURLs waiting for an icon
  1191. // We want to find the intersection of these two sets to notify them
  1192. // Check the sizes of these two sets to minimize the number of iterations
  1193. const HashSet<String>* outerHash;
  1194. const HashSet<String>* innerHash;
  1195. if (icons[i]->retainingPageURLs().size() > m_pageURLsInterestedInIcons.size()) {
  1196. outerHash = &m_pageURLsInterestedInIcons;
  1197. innerHash = &(icons[i]->retainingPageURLs());
  1198. } else {
  1199. innerHash = &m_pageURLsInterestedInIcons;
  1200. outerHash = &(icons[i]->retainingPageURLs());
  1201. }
  1202. HashSet<String>::const_iterator iter = outerHash->begin();
  1203. HashSet<String>::const_iterator end = outerHash->end();
  1204. for (; iter != end; ++iter) {
  1205. if (innerHash->contains(*iter)) {
  1206. LOG(IconDatabase, "%s is interesting in the icon we just read. Adding it to the list and removing it from the interested set", urlForLogging(*iter).ascii().data());
  1207. urlsToNotify.add(*iter);
  1208. }
  1209. // If we ever get to the point were we've seen every url interested in this icon, break early
  1210. if (urlsToNotify.size() == m_pageURLsInterestedInIcons.size())
  1211. break;
  1212. }
  1213. // We don't need to notify a PageURL twice, so all the ones we're about to notify can be removed from the interested set
  1214. if (urlsToNotify.size() == m_pageURLsInterestedInIcons.size())
  1215. m_pageURLsInterestedInIcons.clear();
  1216. else {
  1217. iter = urlsToNotify.begin();
  1218. end = urlsToNotify.end();
  1219. for (; iter != end; ++iter)
  1220. m_pageURLsInterestedInIcons.remove(*iter);
  1221. }
  1222. }
  1223. }
  1224. }
  1225. if (shouldStopThreadActivity())
  1226. return didAnyWork;
  1227. // Informal testing shows that draining the autorelease pool every 25 iterations is about as low as we can go
  1228. // before performance starts to drop off, but we don't want to increase this number because then accumulated memory usage will go up
  1229. AutodrainedPool pool(25);
  1230. // Now that we don't hold any locks, perform the actual notifications
  1231. HashSet<String>::iterator iter = urlsToNotify.begin();
  1232. HashSet<String>::iterator end = urlsToNotify.end();
  1233. for (unsigned iteration = 0; iter != end; ++iter, ++iteration) {
  1234. LOG(IconDatabase, "Notifying icon received for pageURL %s", urlForLogging(*iter).ascii().data());
  1235. dispatchDidImportIconDataForPageURLOnMainThread(*iter);
  1236. if (shouldStopThreadActivity())
  1237. return didAnyWork;
  1238. pool.cycle();
  1239. }
  1240. LOG(IconDatabase, "Done notifying %i pageURLs who just received their icons", urlsToNotify.size());
  1241. urlsToNotify.clear();
  1242. if (shouldStopThreadActivity())
  1243. return didAnyWork;
  1244. }
  1245. LOG(IconDatabase, "Reading from database took %.4f seconds", currentTime() - timeStamp);
  1246. return didAnyWork;
  1247. }
  1248. bool IconDatabase::writeToDatabase()
  1249. {
  1250. ASSERT_ICON_SYNC_THREAD();
  1251. #ifndef NDEBUG
  1252. double timeStamp = currentTime();
  1253. #endif
  1254. bool didAnyWork = false;
  1255. // We can copy the current work queue then clear it out - If any new work comes in while we're writing out,
  1256. // we'll pick it up on the next pass. This greatly simplifies the locking strategy for this method and remains cohesive with changes
  1257. // asked for by the database on the main thread
  1258. {
  1259. MutexLocker locker(m_urlAndIconLock);
  1260. Vector<IconSnapshot> iconSnapshots;
  1261. Vector<PageURLSnapshot> pageSnapshots;
  1262. {
  1263. MutexLocker locker(m_pendingSyncLock);
  1264. iconSnapshots.appendRange(m_iconsPendingSync.begin().values(), m_iconsPendingSync.end().values());
  1265. m_iconsPendingSync.clear();
  1266. pageSnapshots.appendRange(m_pageURLsPendingSync.begin().values(), m_pageURLsPendingSync.end().values());
  1267. m_pageURLsPendingSync.clear();
  1268. }
  1269. if (iconSnapshots.size() || pageSnapshots.size())
  1270. didAnyWork = true;
  1271. SQLiteTransaction syncTransaction(m_syncDB);
  1272. syncTransaction.begin();
  1273. for (unsigned i = 0; i < iconSnapshots.size(); ++i) {
  1274. writeIconSnapshotToSQLDatabase(iconSnapshots[i]);
  1275. LOG(IconDatabase, "Wrote IconRecord for IconURL %s with timeStamp of %i to the DB", urlForLogging(iconSnapshots[i].iconURL()).ascii().data(), iconSnapshots[i].timestamp());
  1276. }
  1277. for (unsigned i = 0; i < pageSnapshots.size(); ++i) {
  1278. // If the icon URL is empty, this page is meant to be deleted
  1279. // ASSERTs are sanity checks to make sure the mappings exist if they should and don't if they shouldn't
  1280. if (pageSnapshots[i].iconURL().isEmpty())
  1281. removePageURLFromSQLDatabase(pageSnapshots[i].pageURL());
  1282. else
  1283. setIconURLForPageURLInSQLDatabase(pageSnapshots[i].iconURL(), pageSnapshots[i].pageURL());
  1284. LOG(IconDatabase, "Committed IconURL for PageURL %s to database", urlForLogging(pageSnapshots[i].pageURL()).ascii().data());
  1285. }
  1286. syncTransaction.commit();
  1287. }
  1288. // Check to make sure there are no dangling PageURLs - If there are, we want to output one log message but not spam the console potentially every few seconds
  1289. if (didAnyWork)
  1290. checkForDanglingPageURLs(false);
  1291. LOG(IconDatabase, "Updating the database took %.4f seconds", currentTime() - timeStamp);
  1292. return didAnyWork;
  1293. }
  1294. void IconDatabase::pruneUnretainedIcons()
  1295. {
  1296. ASSERT_ICON_SYNC_THREAD();
  1297. if (!isOpen())
  1298. return;
  1299. // This method should only be called once per run
  1300. ASSERT(!m_initialPruningComplete);
  1301. // This method relies on having read in all page URLs from the database earlier.
  1302. ASSERT(m_iconURLImportComplete);
  1303. // Get the known PageURLs from the db, and record the ID of any that are not in the retain count set.
  1304. Vector<int64_t> pageIDsToDelete;
  1305. SQLiteStatement pageSQL(m_syncDB, "SELECT rowid, url FROM PageURL;");
  1306. pageSQL.prepare();
  1307. int result;
  1308. while ((result = pageSQL.step()) == SQLResultRow) {
  1309. MutexLocker locker(m_urlAndIconLock);
  1310. if (!m_pageURLToRecordMap.contains(pageSQL.getColumnText(1)))
  1311. pageIDsToDelete.append(pageSQL.getColumnInt64(0));
  1312. }
  1313. if (result != SQLResultDone)
  1314. LOG_ERROR("Error reading PageURL table from on-disk DB");
  1315. pageSQL.finalize();
  1316. // Delete page URLs that were in the table, but not in our retain count set.
  1317. size_t numToDelete = pageIDsToDelete.size();
  1318. if (numToDelete) {
  1319. SQLiteTransaction pruningTransaction(m_syncDB);
  1320. pruningTransaction.begin();
  1321. SQLiteStatement pageDeleteSQL(m_syncDB, "DELETE FROM PageURL WHERE rowid = (?);");
  1322. pageDeleteSQL.prepare();
  1323. for (size_t i = 0; i < numToDelete; ++i) {
  1324. #if OS(WINDOWS)
  1325. LOG(IconDatabase, "Pruning page with rowid %I64i from disk", static_cast<long long>(pageIDsToDelete[i]));
  1326. #else
  1327. LOG(IconDatabase, "Pruning page with rowid %lli from disk", static_cast<long long>(pageIDsToDelete[i]));
  1328. #endif
  1329. pageDeleteSQL.bindInt64(1, pageIDsToDelete[i]);
  1330. int result = pageDeleteSQL.step();
  1331. if (result != SQLResultDone)
  1332. #if OS(WINDOWS)
  1333. LOG_ERROR("Unabled to delete page with id %I64i from disk", static_cast<long long>(pageIDsToDelete[i]));
  1334. #else
  1335. LOG_ERROR("Unabled to delete page with id %lli from disk", static_cast<long long>(pageIDsToDelete[i]));
  1336. #endif
  1337. pageDeleteSQL.reset();
  1338. // If the thread was asked to terminate, we should commit what pruning we've done so far, figuring we can
  1339. // finish the rest later (hopefully)
  1340. if (shouldStopThreadActivity()) {
  1341. pruningTransaction.commit();
  1342. return;
  1343. }
  1344. }
  1345. pruningTransaction.commit();
  1346. pageDeleteSQL.finalize();
  1347. }
  1348. // Deleting unreferenced icons from the Icon tables has to be atomic -
  1349. // If the user quits while these are taking place, they might have to wait. Thankfully this will rarely be an issue
  1350. // A user on a network home directory with a wildly inconsistent database might see quite a pause...
  1351. SQLiteTransaction pruningTransaction(m_syncDB);
  1352. pruningTransaction.begin();
  1353. // Wipe Icons that aren't retained
  1354. if (!m_syncDB.executeCommand("DELETE FROM IconData WHERE iconID NOT IN (SELECT iconID FROM PageURL);"))
  1355. LOG_ERROR("Failed to execute SQL to prune unretained icons from the on-disk IconData table");
  1356. if (!m_syncDB.executeCommand("DELETE FROM IconInfo WHERE iconID NOT IN (SELECT iconID FROM PageURL);"))
  1357. LOG_ERROR("Failed to execute SQL to prune unretained icons from the on-disk IconInfo table");
  1358. pruningTransaction.commit();
  1359. checkForDanglingPageURLs(true);
  1360. m_initialPruningComplete = true;
  1361. }
  1362. void IconDatabase::checkForDanglingPageURLs(bool pruneIfFound)
  1363. {
  1364. ASSERT_ICON_SYNC_THREAD();
  1365. // This check can be relatively expensive so we don't do it in a release build unless the caller has asked us to prune any dangling
  1366. // entries. We also don't want to keep performing this check and reporting this error if it has already found danglers before so we
  1367. // keep track of whether we've found any. We skip the check in the release build pretending to have already found danglers already.
  1368. #ifndef NDEBUG
  1369. static bool danglersFound = true;
  1370. #else
  1371. static bool danglersFound = false;
  1372. #endif
  1373. if ((pruneIfFound || !danglersFound) && SQLiteStatement(m_syncDB, "SELECT url FROM PageURL WHERE PageURL.iconID NOT IN (SELECT iconID FROM IconInfo) LIMIT 1;").returnsAtLeastOneResult()) {
  1374. danglersFound = true;
  1375. LOG(IconDatabase, "Dangling PageURL entries found");
  1376. if (pruneIfFound && !m_syncDB.executeCommand("DELETE FROM PageURL WHERE iconID NOT IN (SELECT iconID FROM IconInfo);"))
  1377. LOG(IconDatabase, "Unable to prune dangling PageURLs");
  1378. }
  1379. }
  1380. void IconDatabase::removeAllIconsOnThread()
  1381. {
  1382. ASSERT_ICON_SYNC_THREAD();
  1383. LOG(IconDatabase, "Removing all icons on the sync thread");
  1384. // Delete all the prepared statements so they can start over
  1385. deleteAllPreparedStatements();
  1386. // To reset the on-disk database, we'll wipe all its tables then vacuum it
  1387. // This is easier and safer than closing it, deleting the file, and recreating from scratch
  1388. m_syncDB.clearAllTables();
  1389. m_syncDB.runVacuumCommand();
  1390. createDatabaseTables(m_syncDB);
  1391. LOG(IconDatabase, "Dispatching notification that we removed all icons");
  1392. dispatchDidRemoveAllIconsOnMainThread();
  1393. }
  1394. void IconDatabase::deleteAllPreparedStatements()
  1395. {
  1396. ASSERT_ICON_SYNC_THREAD();
  1397. m_setIconIDForPageURLStatement.clear();
  1398. m_removePageURLStatement.clear();
  1399. m_getIconIDForIconURLStatement.clear();
  1400. m_getImageDataForIconURLStatement.clear();
  1401. m_addIconToIconInfoStatement.clear();
  1402. m_addIconToIconDataStatement.clear();
  1403. m_getImageDataStatement.clear();
  1404. m_deletePageURLsForIconURLStatement.clear();
  1405. m_deleteIconFromIconInfoStatement.clear();
  1406. m_deleteIconFromIconDataStatement.clear();
  1407. m_updateIconInfoStatement.clear();
  1408. m_updateIconDataStatement.clear();
  1409. m_setIconInfoStatement.clear();
  1410. m_setIconDataStatement.clear();
  1411. }
  1412. void* IconDatabase::cleanupSyncThread()
  1413. {
  1414. ASSERT_ICON_SYNC_THREAD();
  1415. #ifndef NDEBUG
  1416. double timeStamp = currentTime();
  1417. #endif
  1418. // If the removeIcons flag is set, remove all icons from the db.
  1419. if (m_removeIconsRequested)
  1420. removeAllIconsOnThread();
  1421. // Sync remaining icons out
  1422. LOG(IconDatabase, "(THREAD) Doing final writeout and closure of sync thread");
  1423. writeToDatabase();
  1424. // Close the database
  1425. MutexLocker locker(m_syncLock);
  1426. m_databaseDirectory = String();
  1427. m_completeDatabasePath = String();
  1428. deleteAllPreparedStatements();
  1429. m_syncDB.close();
  1430. #ifndef NDEBUG
  1431. LOG(IconDatabase, "(THREAD) Final closure took %.4f seconds", currentTime() - timeStamp);
  1432. #endif
  1433. m_syncThreadRunning = false;
  1434. return 0;
  1435. }
  1436. bool IconDatabase::imported()
  1437. {
  1438. ASSERT_ICON_SYNC_THREAD();
  1439. if (m_isImportedSet)
  1440. return m_imported;
  1441. SQLiteStatement query(m_syncDB, "SELECT IconDatabaseInfo.value FROM IconDatabaseInfo WHERE IconDatabaseInfo.key = \"ImportedSafari2Icons\";");
  1442. if (query.prepare() != SQLResultOk) {
  1443. LOG_ERROR("Unable to prepare imported statement");
  1444. return false;
  1445. }
  1446. int result = query.step();
  1447. if (result == SQLResultRow)
  1448. result = query.getColumnInt(0);
  1449. else {
  1450. if (result != SQLResultDone)
  1451. LOG_ERROR("imported statement failed");
  1452. result = 0;
  1453. }
  1454. m_isImportedSet = true;
  1455. return m_imported = result;
  1456. }
  1457. void IconDatabase::setImported(bool import)
  1458. {
  1459. ASSERT_ICON_SYNC_THREAD();
  1460. m_imported = import;
  1461. m_isImportedSet = true;
  1462. String queryString = import ?
  1463. "INSERT INTO IconDatabaseInfo (key, value) VALUES (\"ImportedSafari2Icons\", 1);" :
  1464. "INSERT INTO IconDatabaseInfo (key, value) VALUES (\"ImportedSafari2Icons\", 0);";
  1465. SQLiteStatement query(m_syncDB, queryString);
  1466. if (query.prepare() != SQLResultOk) {
  1467. LOG_ERROR("Unable to prepare set imported statement");
  1468. return;
  1469. }
  1470. if (query.step() != SQLResultDone)
  1471. LOG_ERROR("set imported statement failed");
  1472. }
  1473. // readySQLiteStatement() handles two things
  1474. // 1 - If the SQLDatabase& argument is different, the statement must be destroyed and remade. This happens when the user
  1475. // switches to and from private browsing
  1476. // 2 - Lazy construction of the Statement in the first place, in case we've never made this query before
  1477. inline void readySQLiteStatement(OwnPtr<SQLiteStatement>& statement, SQLiteDatabase& db, const String& str)
  1478. {
  1479. if (statement && (statement->database() != &db || statement->isExpired())) {
  1480. if (statement->isExpired())
  1481. LOG(IconDatabase, "SQLiteStatement associated with %s is expired", str.ascii().data());
  1482. statement.clear();
  1483. }
  1484. if (!statement) {
  1485. statement = adoptPtr(new SQLiteStatement(db, str));
  1486. if (statement->prepare() != SQLResultOk)
  1487. LOG_ERROR("Preparing statement %s failed", str.ascii().data());
  1488. }
  1489. }
  1490. void IconDatabase::setIconURLForPageURLInSQLDatabase(const String& iconURL, const String& pageURL)
  1491. {
  1492. ASSERT_ICON_SYNC_THREAD();
  1493. int64_t iconID = getIconIDForIconURLFromSQLDatabase(iconURL);
  1494. if (!iconID)
  1495. iconID = addIconURLToSQLDatabase(iconURL);
  1496. if (!iconID) {
  1497. LOG_ERROR("Failed to establish an ID for iconURL %s", urlForLogging(iconURL).ascii().data());
  1498. ASSERT(false);
  1499. return;
  1500. }
  1501. setIconIDForPageURLInSQLDatabase(iconID, pageURL);
  1502. }
  1503. void IconDatabase::setIconIDForPageURLInSQLDatabase(int64_t iconID, const String& pageURL)
  1504. {
  1505. ASSERT_ICON_SYNC_THREAD();
  1506. readySQLiteStatement(m_setIconIDForPageURLStatement, m_syncDB, "INSERT INTO PageURL (url, iconID) VALUES ((?), ?);");
  1507. m_setIconIDForPageURLStatement->bindText(1, pageURL);
  1508. m_setIconIDForPageURLStatement->bindInt64(2, iconID);
  1509. int result = m_setIconIDForPageURLStatement->step();
  1510. if (result != SQLResultDone) {
  1511. ASSERT(false);
  1512. LOG_ERROR("setIconIDForPageURLQuery failed for url %s", urlForLogging(pageURL).ascii().data());
  1513. }
  1514. m_setIconIDForPageURLStatement->reset();
  1515. }
  1516. void IconDatabase::removePageURLFromSQLDatabase(const String& pageURL)
  1517. {
  1518. ASSERT_ICON_SYNC_THREAD();
  1519. readySQLiteStatement(m_removePageURLStatement, m_syncDB, "DELETE FROM PageURL WHERE url = (?);");
  1520. m_removePageURLStatement->bindText(1, pageURL);
  1521. if (m_removePageURLStatement->step() != SQLResultDone)
  1522. LOG_ERROR("removePageURLFromSQLDatabase failed for url %s", urlForLogging(pageURL).ascii().data());
  1523. m_removePageURLStatement->reset();
  1524. }
  1525. int64_t IconDatabase::getIconIDForIconURLFromSQLDatabase(const String& iconURL)
  1526. {
  1527. ASSERT_ICON_SYNC_THREAD();
  1528. readySQLiteStatement(m_getIconIDForIconURLStatement, m_syncDB, "SELECT IconInfo.iconID FROM IconInfo WHERE IconInfo.url = (?);");
  1529. m_getIconIDForIconURLStatement->bindText(1, iconURL);
  1530. int64_t result = m_getIconIDForIconURLStatement->step();
  1531. if (result == SQLResultRow)
  1532. result = m_getIconIDForIconURLStatement->getColumnInt64(0);
  1533. else {
  1534. if (result != SQLResultDone)
  1535. LOG_ERROR("getIconIDForIconURLFromSQLDatabase failed for url %s", urlForLogging(iconURL).ascii().data());
  1536. result = 0;
  1537. }
  1538. m_getIconIDForIconURLStatement->reset();
  1539. return result;
  1540. }
  1541. int64_t IconDatabase::addIconURLToSQLDatabase(const String& iconURL)
  1542. {
  1543. ASSERT_ICON_SYNC_THREAD();
  1544. // There would be a transaction here to make sure these two inserts are atomic
  1545. // In practice the only caller of this method is always wrapped in a transaction itself so placing another
  1546. // here is unnecessary
  1547. readySQLiteStatement(m_addIconToIconInfoStatement, m_syncDB, "INSERT INTO IconInfo (url, stamp) VALUES (?, 0);");
  1548. m_addIconToIconInfoStatement->bindText(1, iconURL);
  1549. int result = m_addIconToIconInfoStatement->step();
  1550. m_addIconToIconInfoStatement->reset();
  1551. if (result != SQLResultDone) {
  1552. LOG_ERROR("addIconURLToSQLDatabase failed to insert %s into IconInfo", urlForLogging(iconURL).ascii().data());
  1553. return 0;
  1554. }
  1555. int64_t iconID = m_syncDB.lastInsertRowID();
  1556. readySQLiteStatement(m_addIconToIconDataStatement, m_syncDB, "INSERT INTO IconData (iconID, data) VALUES (?, ?);");
  1557. m_addIconToIconDataStatement->bindInt64(1, iconID);
  1558. result = m_addIconToIconDataStatement->step();
  1559. m_addIconToIconDataStatement->reset();
  1560. if (result != SQLResultDone) {
  1561. LOG_ERROR("addIconURLToSQLDatabase failed to insert %s into IconData", urlForLogging(iconURL).ascii().data());
  1562. return 0;
  1563. }
  1564. return iconID;
  1565. }
  1566. PassRefPtr<SharedBuffer> IconDatabase::getImageDataForIconURLFromSQLDatabase(const String& iconURL)
  1567. {
  1568. ASSERT_ICON_SYNC_THREAD();
  1569. RefPtr<SharedBuffer> imageData;
  1570. readySQLiteStatement(m_getImageDataForIconURLStatement, m_syncDB, "SELECT IconData.data FROM IconData WHERE IconData.iconID IN (SELECT iconID FROM IconInfo WHERE IconInfo.url = (?));");
  1571. m_getImageDataForIconURLStatement->bindText(1, iconURL);
  1572. int result = m_getImageDataForIconURLStatement->step();
  1573. if (result == SQLResultRow) {
  1574. Vector<char> data;
  1575. m_getImageDataForIconURLStatement->getColumnBlobAsVector(0, data);
  1576. imageData = SharedBuffer::create(data.data(), data.size());
  1577. } else if (result != SQLResultDone)
  1578. LOG_ERROR("getImageDataForIconURLFromSQLDatabase failed for url %s", urlForLogging(iconURL).ascii().data());
  1579. m_getImageDataForIconURLStatement->reset();
  1580. return imageData.release();
  1581. }
  1582. void IconDatabase::removeIconFromSQLDatabase(const String& iconURL)
  1583. {
  1584. ASSERT_ICON_SYNC_THREAD();
  1585. if (iconURL.isEmpty())
  1586. return;
  1587. // There would be a transaction here to make sure these removals are atomic
  1588. // In practice the only caller of this method is always wrapped in a transaction itself so placing another here is unnecessary
  1589. // It's possible this icon is not in the database because of certain rapid browsing patterns (such as a stress test) where the
  1590. // icon is marked to be added then marked for removal before it is ever written to disk. No big deal, early return
  1591. int64_t iconID = getIconIDForIconURLFromSQLDatabase(iconURL);
  1592. if (!iconID)
  1593. return;
  1594. readySQLiteStatement(m_deletePageURLsForIconURLStatement, m_syncDB, "DELETE FROM PageURL WHERE PageURL.iconID = (?);");
  1595. m_deletePageURLsForIconURLStatement->bindInt64(1, iconID);
  1596. if (m_deletePageURLsForIconURLStatement->step() != SQLResultDone)
  1597. LOG_ERROR("m_deletePageURLsForIconURLStatement failed for url %s", urlForLogging(iconURL).ascii().data());
  1598. readySQLiteStatement(m_deleteIconFromIconInfoStatement, m_syncDB, "DELETE FROM IconInfo WHERE IconInfo.iconID = (?);");
  1599. m_deleteIconFromIconInfoStatement->bindInt64(1, iconID);
  1600. if (m_deleteIconFromIconInfoStatement->step() != SQLResultDone)
  1601. LOG_ERROR("m_deleteIconFromIconInfoStatement failed for url %s", urlForLogging(iconURL).ascii().data());
  1602. readySQLiteStatement(m_deleteIconFromIconDataStatement, m_syncDB, "DELETE FROM IconData WHERE IconData.iconID = (?);");
  1603. m_deleteIconFromIconDataStatement->bindInt64(1, iconID);
  1604. if (m_deleteIconFromIconDataStatement->step() != SQLResultDone)
  1605. LOG_ERROR("m_deleteIconFromIconDataStatement failed for url %s", urlForLogging(iconURL).ascii().data());
  1606. m_deletePageURLsForIconURLStatement->reset();
  1607. m_deleteIconFromIconInfoStatement->reset();
  1608. m_deleteIconFromIconDataStatement->reset();
  1609. }
  1610. void IconDatabase::writeIconSnapshotToSQLDatabase(const IconSnapshot& snapshot)
  1611. {
  1612. ASSERT_ICON_SYNC_THREAD();
  1613. if (snapshot.iconURL().isEmpty())
  1614. return;
  1615. // A nulled out timestamp and data means this icon is destined to be deleted - do that instead of writing it out
  1616. if (!snapshot.timestamp() && !snapshot.data()) {
  1617. LOG(IconDatabase, "Removing %s from on-disk database", urlForLogging(snapshot.iconURL()).ascii().data());
  1618. removeIconFromSQLDatabase(snapshot.iconURL());
  1619. return;
  1620. }
  1621. // There would be a transaction here to make sure these removals are atomic
  1622. // In practice the only caller of this method is always wrapped in a transaction itself so placing another here is unnecessary
  1623. // Get the iconID for this url
  1624. int64_t iconID = getIconIDForIconURLFromSQLDatabase(snapshot.iconURL());
  1625. // If there is already an iconID in place, update the database.
  1626. // Otherwise, insert new records
  1627. if (iconID) {
  1628. readySQLiteStatement(m_updateIconInfoStatement, m_syncDB, "UPDATE IconInfo SET stamp = ?, url = ? WHERE iconID = ?;");
  1629. m_updateIconInfoStatement->bindInt64(1, snapshot.timestamp());
  1630. m_updateIconInfoStatement->bindText(2, snapshot.iconURL());
  1631. m_updateIconInfoStatement->bindInt64(3, iconID);
  1632. if (m_updateIconInfoStatement->step() != SQLResultDone)
  1633. LOG_ERROR("Failed to update icon info for url %s", urlForLogging(snapshot.iconURL()).ascii().data());
  1634. m_updateIconInfoStatement->reset();
  1635. readySQLiteStatement(m_updateIconDataStatement, m_syncDB, "UPDATE IconData SET data = ? WHERE iconID = ?;");
  1636. m_updateIconDataStatement->bindInt64(2, iconID);
  1637. // If we *have* image data, bind it to this statement - Otherwise bind "null" for the blob data,
  1638. // signifying that this icon doesn't have any data
  1639. if (snapshot.data() && snapshot.data()->size())
  1640. m_updateIconDataStatement->bindBlob(1, snapshot.data()->data(), snapshot.data()->size());
  1641. else
  1642. m_updateIconDataStatement->bindNull(1);
  1643. if (m_updateIconDataStatement->step() != SQLResultDone)
  1644. LOG_ERROR("Failed to update icon data for url %s", urlForLogging(snapshot.iconURL()).ascii().data());
  1645. m_updateIconDataStatement->reset();
  1646. } else {
  1647. readySQLiteStatement(m_setIconInfoStatement, m_syncDB, "INSERT INTO IconInfo (url,stamp) VALUES (?, ?);");
  1648. m_setIconInfoStatement->bindText(1, snapshot.iconURL());
  1649. m_setIconInfoStatement->bindInt64(2, snapshot.timestamp());
  1650. if (m_setIconInfoStatement->step() != SQLResultDone)
  1651. LOG_ERROR("Failed to set icon info for url %s", urlForLogging(snapshot.iconURL()).ascii().data());
  1652. m_setIconInfoStatement->reset();
  1653. int64_t iconID = m_syncDB.lastInsertRowID();
  1654. readySQLiteStatement(m_setIconDataStatement, m_syncDB, "INSERT INTO IconData (iconID, data) VALUES (?, ?);");
  1655. m_setIconDataStatement->bindInt64(1, iconID);
  1656. // If we *have* image data, bind it to this statement - Otherwise bind "null" for the blob data,
  1657. // signifying that this icon doesn't have any data
  1658. if (snapshot.data() && snapshot.data()->size())
  1659. m_setIconDataStatement->bindBlob(2, snapshot.data()->data(), snapshot.data()->size());
  1660. else
  1661. m_setIconDataStatement->bindNull(2);
  1662. if (m_setIconDataStatement->step() != SQLResultDone)
  1663. LOG_ERROR("Failed to set icon data for url %s", urlForLogging(snapshot.iconURL()).ascii().data());
  1664. m_setIconDataStatement->reset();
  1665. }
  1666. }
  1667. bool IconDatabase::wasExcludedFromBackup()
  1668. {
  1669. ASSERT_ICON_SYNC_THREAD();
  1670. return SQLiteStatement(m_syncDB, "SELECT value FROM IconDatabaseInfo WHERE key = 'ExcludedFromBackup';").getColumnInt(0);
  1671. }
  1672. void IconDatabase::setWasExcludedFromBackup()
  1673. {
  1674. ASSERT_ICON_SYNC_THREAD();
  1675. SQLiteStatement(m_syncDB, "INSERT INTO IconDatabaseInfo (key, value) VALUES ('ExcludedFromBackup', 1)").executeCommand();
  1676. }
  1677. class ClientWorkItem {
  1678. public:
  1679. ClientWorkItem(IconDatabaseClient* client)
  1680. : m_client(client)
  1681. { }
  1682. virtual void performWork() = 0;
  1683. virtual ~ClientWorkItem() { }
  1684. protected:
  1685. IconDatabaseClient* m_client;
  1686. };
  1687. class ImportedIconURLForPageURLWorkItem : public ClientWorkItem {
  1688. public:
  1689. ImportedIconURLForPageURLWorkItem(IconDatabaseClient* client, const String& pageURL)
  1690. : ClientWorkItem(client)
  1691. , m_pageURL(new String(pageURL.threadsafeCopy()))
  1692. { }
  1693. virtual ~ImportedIconURLForPageURLWorkItem()
  1694. {
  1695. delete m_pageURL;
  1696. }
  1697. virtual void performWork()
  1698. {
  1699. ASSERT(m_client);
  1700. m_client->didImportIconURLForPageURL(*m_pageURL);
  1701. m_client = 0;
  1702. }
  1703. private:
  1704. String* m_pageURL;
  1705. };
  1706. class ImportedIconDataForPageURLWorkItem : public ClientWorkItem {
  1707. public:
  1708. ImportedIconDataForPageURLWorkItem(IconDatabaseClient* client, const String& pageURL)
  1709. : ClientWorkItem(client)
  1710. , m_pageURL(new String(pageURL.threadsafeCopy()))
  1711. { }
  1712. virtual ~ImportedIconDataForPageURLWorkItem()
  1713. {
  1714. delete m_pageURL;
  1715. }
  1716. virtual void performWork()
  1717. {
  1718. ASSERT(m_client);
  1719. m_client->didImportIconDataForPageURL(*m_pageURL);
  1720. m_client = 0;
  1721. }
  1722. private:
  1723. String* m_pageURL;
  1724. };
  1725. class RemovedAllIconsWorkItem : public ClientWorkItem {
  1726. public:
  1727. RemovedAllIconsWorkItem(IconDatabaseClient* client)
  1728. : ClientWorkItem(client)
  1729. { }
  1730. virtual void performWork()
  1731. {
  1732. ASSERT(m_client);
  1733. m_client->didRemoveAllIcons();
  1734. m_client = 0;
  1735. }
  1736. };
  1737. class FinishedURLImport : public ClientWorkItem {
  1738. public:
  1739. FinishedURLImport(IconDatabaseClient* client)
  1740. : ClientWorkItem(client)
  1741. { }
  1742. virtual void performWork()
  1743. {
  1744. ASSERT(m_client);
  1745. m_client->didFinishURLImport();
  1746. m_client = 0;
  1747. }
  1748. };
  1749. static void performWorkItem(void* context)
  1750. {
  1751. ClientWorkItem* item = static_cast<ClientWorkItem*>(context);
  1752. item->performWork();
  1753. delete item;
  1754. }
  1755. void IconDatabase::dispatchDidImportIconURLForPageURLOnMainThread(const String& pageURL)
  1756. {
  1757. ASSERT_ICON_SYNC_THREAD();
  1758. ImportedIconURLForPageURLWorkItem* work = new ImportedIconURLForPageURLWorkItem(m_client, pageURL);
  1759. callOnMainThread(performWorkItem, work);
  1760. }
  1761. void IconDatabase::dispatchDidImportIconDataForPageURLOnMainThread(const String& pageURL)
  1762. {
  1763. ASSERT_ICON_SYNC_THREAD();
  1764. ImportedIconDataForPageURLWorkItem* work = new ImportedIconDataForPageURLWorkItem(m_client, pageURL);
  1765. callOnMainThread(performWorkItem, work);
  1766. }
  1767. void IconDatabase::dispatchDidRemoveAllIconsOnMainThread()
  1768. {
  1769. ASSERT_ICON_SYNC_THREAD();
  1770. RemovedAllIconsWorkItem* work = new RemovedAllIconsWorkItem(m_client);
  1771. callOnMainThread(performWorkItem, work);
  1772. }
  1773. void IconDatabase::dispatchDidFinishURLImportOnMainThread()
  1774. {
  1775. ASSERT_ICON_SYNC_THREAD();
  1776. FinishedURLImport* work = new FinishedURLImport(m_client);
  1777. callOnMainThread(performWorkItem, work);
  1778. }
  1779. } // namespace WebCore
  1780. #endif // ENABLE(ICONDATABASE)