DatabaseEngine.cpp
Go to the documentation of this file.
1 /*
2  *=BEGIN SONGBIRD GPL
3  *
4  * This file is part of the Songbird web player.
5  *
6  * Copyright(c) 2005-2010 POTI, Inc.
7  * http://www.songbirdnest.com
8  *
9  * This file may be licensed under the terms of of the
10  * GNU General Public License Version 2 (the ``GPL'').
11  *
12  * Software distributed under the License is distributed
13  * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
14  * express or implied. See the GPL for the specific language
15  * governing rights and limitations.
16  *
17  * You should have received a copy of the GPL along with this
18  * program. If not, go to http://www.gnu.org/licenses/gpl.html
19  * or write to the Free Software Foundation, Inc.,
20  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21  *
22  *=END SONGBIRD GPL
23  */
24 
30 // INCLUDES ===================================================================
31 #include "DatabaseEngine.h"
33 
34 #include <nsCOMPtr.h>
35 #include <nsIAppStartup.h>
36 #include <nsIFile.h>
37 #include <nsILocalFile.h>
38 #include <nsStringGlue.h>
39 #include <nsIObserverService.h>
40 #include <nsISimpleEnumerator.h>
41 #include <nsDirectoryServiceDefs.h>
42 #include <nsAppDirectoryServiceDefs.h>
43 #include <nsDirectoryServiceUtils.h>
44 #include <nsUnicharUtils.h>
45 #include <nsIURI.h>
46 #include <nsNetUtil.h>
47 #include <sbLockUtils.h>
48 #include <nsIPrefService.h>
49 #include <nsIPrefBranch.h>
50 #include <nsXPFEComponentsCID.h>
51 #include <prsystem.h>
52 
53 #include <vector>
54 #include <algorithm>
55 #include <prmem.h>
56 #include <prtypes.h>
57 #include <assert.h>
58 #include <prprf.h>
59 #include <locale.h>
60 
61 #include <nsIScriptError.h>
62 #include <nsIConsoleService.h>
63 #include <nsIConsoleMessage.h>
64 
65 #include <nsCOMArray.h>
66 
67 #ifdef METRICS_ENABLED
68 #include <sbIMetrics.h>
69 #endif
70 #include <sbIPrompter.h>
71 #include <sbMemoryUtils.h>
72 #include <sbStringBundle.h>
74 #include <sbDebugUtils.h>
75 
76 #if defined(_WIN32)
77  #include <windows.h>
78  #include <direct.h>
79 #else
80 // on UNIX, strnicmp is called strncasecmp
81 #define strnicmp strncasecmp
82 #endif
83 
84 //Sometimes min is not defined.
85 #if !defined(min)
86 #define min(a,b) (((a) < (b)) ? (a) : (b))
87 #endif
88 
89 #define USE_SQLITE_FULL_DISK_CACHING
90 #define USE_SQLITE_READ_UNCOMMITTED
91 #define USE_SQLITE_MEMORY_TEMP_STORE
92 #define USE_SQLITE_BUSY_TIMEOUT
93 // Can not use FTS with shared cache enabled
94 //#define USE_SQLITE_SHARED_CACHE
95 
97 // Prefs used to control memory usage.
98 //
99 // All prefs require a restart to take effect, and pageSize
100 // can only be changed before databases have been created.
101 //
102 // See http://www.sqlite.org/malloc.html for details
103 //
104 // Also note that page cache and page size can be specified on a
105 // per db basis with keys like
106 // songbird.dbengine.main@library.songbirdnest.com.cacheSize
107 
108 #define PREF_BRANCH_BASE "songbird.dbengine."
109 #define PREF_DB_PAGE_SIZE "pageSize"
110 #define PREF_DB_CACHE_SIZE "cacheSize"
111 #define PREF_DB_PREALLOCCACHE_SIZE "preAllocCacheSize"
112 #define PREF_DB_PREALLOCSCRATCH_SIZE "preAllocScratchSize"
113 #define PREF_DB_SOFT_LIMIT "softHeapLimit"
114 
115 // These constants come from sbLocalDatabaseLibraryLoader.cpp
116 // Do not change these constants unless you are changing them in
117 // sbLocalDatabaseLibraryLoader.cpp!
118 #define PREF_SCAN_COMPLETE "songbird.firstrun.scancomplete"
119 #define PREF_BRANCH_LIBRARY_LOADER "songbird.library.loader."
120 #define PREF_MAIN_LIBRARY "songbird.library.main"
121 #define PREF_WEB_LIBRARY "songbird.library.web"
122 #define PREF_DOWNLOAD_LIST "songbird.library.download"
123 #define PREF_PLAYQUEUE_LIBRARY "songbird.library.playqueue"
124 
125 // These are also from sbLocalDatabaseLibraryLoader.cpp.
126 #define PREF_LOADER_DBGUID "databaseGUID"
127 #define PREF_LOADER_DBLOCATION "databaseLocation"
128 
129 // These are also from sbLocalDatabaseLibraryLoader.cpp.
130 #define DBENGINE_GUID_MAIN_LIBRARY "main@library.songbirdnest.com"
131 #define DBENGINE_GUID_WEB_LIBRARY "web@library.songbirdnest.com"
132 #define DBENGINE_GUID_PLAYQUEUE_LIBRARY "playqueue@library.songbirdnest.com"
133 
134 // Unless prefs state otherwise, allow 250 Mbytes
135 // of page cache. WOW!
136 #define DEFAULT_PAGE_SIZE 16384
137 #define DEFAULT_CACHE_SIZE 16000
138 
139 // pre-allocated for caching
140 #define DEFAULT_PREALLOCCACHE_SIZE 0
141 // pre-allocated for scratch memory
142 #define DEFAULT_PREALLOCSCRATCH_SIZE 0
143 
144 #define SQLITE_MAX_RETRIES 666
145 #define MAX_BUSY_RETRY_CLOSE_DB 10
146 
147 // Uncomment or define this to enable perf logging.
148 //#define USE_PERF_LOGGING 1
149 
150 // Idle Service Timeout we use before checking if we should run ANALYZE.
151 #define IDLE_SERVICE_TIMEOUT (5 * 60)
152 // Query Count Threshold for running ANALYZE
153 #define ANALYZE_QUERY_THRESHOLD (800)
154 
155 #if defined(_DEBUG) || defined(DEBUG)
156  #if defined(XP_WIN)
157  #include <windows.h>
158  #endif
159  #define HARD_SANITY_CHECK 1
160 #endif
161 
162 #ifdef USE_PERF_LOGGING
163 
164 class sbDatabaseEnginePerformanceLogger
165 {
166 public:
167  sbDatabaseEnginePerformanceLogger(const nsAString& aQuery,
168  const nsAString& aGuid);
169  ~sbDatabaseEnginePerformanceLogger();
170 
171 private:
172  nsString mQuery;
173  nsString mGuid;
174  PRTime mStart;
175 };
176 
177 #define BEGIN_PERFORMANCE_LOG(_strQuery, _dbName) \
178  sbDatabaseEnginePerformanceLogger _performanceLogger(_strQuery, _dbName)
179 #else
180 #define BEGIN_PERFORMANCE_LOG(_strQuery, _dbName) /* nothing */
181 #endif
182 
183 #define NS_FINAL_UI_STARTUP_CATEGORY "final-ui-startup"
184 
185 /*
186  * Parse a path string in the form of "n1.n2.n3..." where n is an integer.
187  * Returns the next integer in the list while advancing the pos pointer to
188  * the start of the next integer. If the end of the list is reached, pos
189  * is set to null
190  */
191 static int tree_collate_func_next_num(const char* start,
192  char** pos,
193  int length,
194  int eTextRep,
195  int width)
196 {
197  // If we are at the end of the string, set pos to null
198  const char* end = start + length;
199  if (*pos == end || *pos == NULL) {
200  *pos = NULL;
201  return 0;
202  }
203 
204  int num = 0;
205  int sign = 1;
206 
207  while (*pos < end) {
208 
209  // Extract the ASCII value of the digit from the encoded byte(s)
210  char c;
211  switch(eTextRep) {
212  case SQLITE_UTF16BE:
213  c = *(*pos + 1);
214  break;
215  case SQLITE_UTF16LE:
216  case SQLITE_UTF8:
217  c = **pos;
218  break;
219  default:
220  return 0;
221  }
222 
223  // If we see a period, we're done. Also advance the pointer so the next
224  // call starts on the first digit
225  if (c == '.') {
226  *pos += width;
227  break;
228  }
229 
230  // If we encounter a hyphen, treat this as a negative number. Otherwise,
231  // include the digit in the current number
232  if (c == '-') {
233  sign = -1;
234  }
235  else {
236  num = (num * 10) + (c - '0');
237  }
238  *pos += width;
239  }
240 
241  return num * sign;
242 }
243 
244 static int tree_collate_func(void *pCtx,
245  int nA,
246  const void *zA,
247  int nB,
248  const void *zB,
249  int eTextRep)
250 {
251  const char* cA = (const char*) zA;
252  const char* cB = (const char*) zB;
253  char* pA = (char*) cA;
254  char* pB = (char*) cB;
255 
256  int width = eTextRep == SQLITE_UTF8 ? 1 : 2;
257 
258  /*
259  * Compare each number in each string until either the numbers are different
260  * or we run out of numbers to compare
261  */
262  int a = tree_collate_func_next_num(cA, &pA, nA, eTextRep, width);
263  int b = tree_collate_func_next_num(cB, &pB, nB, eTextRep, width);
264 
265  while (pA != NULL && pB != NULL) {
266  if (a != b) {
267  return a < b ? -1 : 1;
268  }
269  a = tree_collate_func_next_num(cA, &pA, nA, eTextRep, width);
270  b = tree_collate_func_next_num(cB, &pB, nB, eTextRep, width);
271  }
272 
273  /*
274  * At this point, if the lengths are the same, the strings are the same
275  */
276  if (nA == nB) {
277  return 0;
278  }
279 
280  /*
281  * Otherwise, the longer string is always smaller
282  */
283  return nA > nB ? -1 : 1;
284 }
285 
286 static int tree_collate_func_utf16be(void *pCtx,
287  int nA,
288  const void *zA,
289  int nB,
290  const void *zB)
291 {
292  return tree_collate_func(pCtx, nA, zA, nB, zB, SQLITE_UTF16BE);
293 }
294 
295 static int tree_collate_func_utf16le(void *pCtx,
296  int nA,
297  const void *zA,
298  int nB,
299  const void *zB)
300 {
301  return tree_collate_func(pCtx, nA, zA, nB, zB, SQLITE_UTF16LE);
302 }
303 
304 static int tree_collate_func_utf8(void *pCtx,
305  int nA,
306  const void *zA,
307  int nB,
308  const void *zB)
309 {
310  return tree_collate_func(pCtx, nA, zA, nB, zB, SQLITE_UTF8);
311 }
312 
313 // these functions are necessary because we cannot rely on the libc versions on
314 // unix or mac due to the discrepency between moz and libc's wchar_t sizes
315 
316 int native_wcslen(const NATIVE_CHAR_TYPE *s) {
317  const NATIVE_CHAR_TYPE *p = s;
318  while (*p) p++;
319  return p-s;
320 }
321 
322 int native_wcscmp(const NATIVE_CHAR_TYPE *s1, const NATIVE_CHAR_TYPE *s2) {
323  while (*s1 == *s2++)
324  if (*s1++ == 0)
325  return (0);
326  return (*s1 - *(s2 - 1));
327 }
328 
329 static PRInt32 gLocaleCollationEnabled = PR_TRUE;
330 
331 /*
332  * Perform collation for current locale. Data always comes in as native wide
333  * chars, ie: utf16 on windows and mac, ucs4 on linux (note that on linux
334  * and mac, sizeof(wchar_t)=2 eventhough the libc's native encoding is ucs4)
335  *
336  * IMPORTANT NOTE: Whenever the algorithm for library_collate is changed and
337  * yields a different sort order than before for *any* set of arbitrary strings,
338  * a new migration step must be added to issue the "reindex 'library_collate'"
339  * sql query. THIS IS OF UTMOST IMPORTANCE because otherwise, the library's
340  * index can become entirely trashed!
341  *
342  */
344  const NATIVE_CHAR_TYPE *zA,
345  const NATIVE_CHAR_TYPE *zB)
346 {
347  // shortcut when both strings are empty
348  if ((zA && !*zA) &&
349  (zB && !*zB))
350  return 0;
351 
352  // if no dbengine service or if collation is disabled, just do a C compare
353 
355 
356  if (!db) {
357  return native_wcscmp(zA, zB);
358  }
359 
360  return db->Collate(cBuffers, zA, zB);
361 }
362 
363 inline void swap_utf16_bytes(const void *aStr,
364  int len) {
365  char *d = (char *)aStr;
366  char t;
367  for (int i=0;i<len;i++) {
368  t = *d;
369  *d = *(d+1);
370  d++;
371  *d = t;
372  d++;
373  }
374 }
375 
376 static int library_collate_func_utf16be(void *pCtx,
377  int nA,
378  const void *zA,
379  int nB,
380  const void *zB)
381 {
382  collationBuffers *cBuffers = reinterpret_cast<collationBuffers *>(pCtx);
383  if (!cBuffers)
384  return 0;
385 
386  // copy to our own string in order to zero terminate and being able to swap
387  // the utf16 bytes if needed. note that we copy a utf16 string here regardless
388  // of the native encoding.
389  cBuffers->encodingConversionBuffer1.copy_utf16((const UTF16_CHARTYPE *)zA, nA);
390  cBuffers->encodingConversionBuffer2.copy_utf16((const UTF16_CHARTYPE *)zB, nB);
391 
392  #ifdef LITTLEENDIAN
393 
394  // utf16 came as big endian, swap bytes
395  swap_utf16_bytes(staticbuffer1a, nA);
396  swap_utf16_bytes(staticbuffer1b, nB);
397 
398  #endif // ifdef LITTLEENDIAN
399 
400  #if defined(XP_UNIX) && !defined(XP_MACOSX)
401 
402  // on linux, native char is not utf16, we need to convert to ucs4
403 
404  glong size;
405  NATIVE_CHAR_TYPE *a =
406  (NATIVE_CHAR_TYPE *)g_utf16_to_ucs4(
407  (gunichar2 *)cBuffers->encodingConversionBuffer1.buffer(),
408  (glong)nA,
409  NULL,
410  &size,
411  NULL);
412  a[size] = 0;
413 
414  NATIVE_CHAR_TYPE *b =
415  (NATIVE_CHAR_TYPE *)g_utf16_to_ucs4(
416  (gunichar2 *)cBuffers->encodingConversionBuffer2.buffer(),
417  (glong)nB,
418  NULL,
419  &size,
420  NULL);
421  b[size] = 0;
422 
423  #else // XP_UNIX && !XP_MACOSX
424 
425  // on mac and windows, utf16 is native, so we just use the buffer as is
426 
427  const NATIVE_CHAR_TYPE *a = cBuffers->encodingConversionBuffer1.buffer();
428  const NATIVE_CHAR_TYPE *b = cBuffers->encodingConversionBuffer1.buffer();
429 
430  #endif
431 
432  int r = library_collate_func(cBuffers, a, b);
433 
434  #if defined(XP_UNIX) && !defined(XP_MACOSX)
435 
436  // free the temporary strings allocated by the utf16 to ucs4 conversion
437 
438  g_free(a);
439  g_free(b);
440 
441  #endif
442 
443  return r;
444 }
445 
446 static int library_collate_func_utf16le(void *pCtx,
447  int nA,
448  const void *zA,
449  int nB,
450  const void *zB)
451 {
452  collationBuffers *cBuffers = reinterpret_cast<collationBuffers *>(pCtx);
453  if (!cBuffers)
454  return 0;
455 
456  // copy to our own string in order to zero terminate and being able to swap
457  // the utf16 bytes if needed. note that we copy a utf16 string here regardless
458  // of the native encoding.
459  cBuffers->encodingConversionBuffer1.copy_utf16((const UTF16_CHARTYPE *)zA, nA);
460  cBuffers->encodingConversionBuffer2.copy_utf16((const UTF16_CHARTYPE *)zB, nB);
461 
462  #ifdef BIGENDIAN
463 
464  // utf16 came as little endian, swap bytes
465  swap_utf16_bytes(staticbuffer1a, nA);
466  swap_utf16_bytes(staticbuffer1b, nB);
467 
468  #endif // ifdef LITTLEENDIAN
469 
470  #if defined(XP_UNIX) && !defined(XP_MACOSX)
471 
472  // on linux, native char is not utf16, we need to convert to ucs4
473 
474  glong size;
475  NATIVE_CHAR_TYPE *a =
476  (NATIVE_CHAR_TYPE *)g_utf16_to_ucs4(
477  (gunichar2 *)cBuffers->encodingConversionBuffer1.buffer(),
478  (glong)nA,
479  NULL,
480  &size,
481  NULL);
482  a[size] = 0;
483 
484  NATIVE_CHAR_TYPE *b =
485  (NATIVE_CHAR_TYPE *)g_utf16_to_ucs4(
486  (gunichar2 *)cBuffers->encodingConversionBuffer2.buffer(),
487  (glong)nB,
488  NULL,
489  &size,
490  NULL);
491  b[size] = 0;
492 
493  #else // XP_UNIX && !XP_MACOSX
494 
495  // on mac and windows, utf16 is native, so we just use the buffer as is
496 
497  const NATIVE_CHAR_TYPE *a = cBuffers->encodingConversionBuffer1.buffer();
498  const NATIVE_CHAR_TYPE *b = cBuffers->encodingConversionBuffer1.buffer();
499 
500  #endif
501 
502  int r = library_collate_func(cBuffers, a, b);
503 
504  #if defined(XP_UNIX) && !defined(XP_MACOSX)
505 
506  // free the temporary strings allocated by the utf16 to ucs4 conversion
507 
508  g_free(a);
509  g_free(b);
510 
511  #endif
512 
513  return r;
514 }
515 
516 static int library_collate_func_utf8(void *pCtx,
517  int nA,
518  const void *zA,
519  int nB,
520  const void *zB)
521 {
522  collationBuffers *cBuffers = reinterpret_cast<collationBuffers *>(pCtx);
523  if (!cBuffers)
524  return 0;
525 
526  // we first convert to the native character encoding so that we can perform
527  // the entire collation algorithm (which requires splitting the strings into
528  // substrings) without doing anymore conversions.
529 
530  // strlen(utf8) * sizeof(NATIVE_CHAR_TYPE) is always large enough to hold
531  // the result of the utf8 to utf16/ucs4 conversion, so no need to call the OS
532  // for the desired size first.
533 
536 
537  NATIVE_CHAR_TYPE *a;
538  NATIVE_CHAR_TYPE *b;
539 
540 #ifdef XP_MACOSX
541 
542  a = cBuffers->encodingConversionBuffer1.buffer();
543  b = cBuffers->encodingConversionBuffer2.buffer();
544 
545  CFStringRef cA = CFStringCreateWithBytes(NULL,
546  (const UInt8*)zA,
547  nA,
548  kCFStringEncodingUTF8,
549  false);
550  CFStringRef cB = CFStringCreateWithBytes(NULL,
551  (const UInt8*)zB,
552  nB,
553  kCFStringEncodingUTF8,
554  false);
555 
556  CFStringGetCharacters(cA, CFRangeMake(0, CFStringGetLength(cA)), a);
557  CFStringGetCharacters(cB, CFRangeMake(0, CFStringGetLength(cB)), b);
558 
559  a[CFStringGetLength(cA)] = 0;
560  b[CFStringGetLength(cB)] = 0;
561 
562  CFRelease(cA);
563  CFRelease(cB);
564 
565 #elif XP_UNIX
566 
567  a = (NATIVE_CHAR_TYPE *)g_utf8_to_ucs4((const gchar *)zA,
568  nA,
569  NULL,
570  NULL,
571  NULL);
572  b = (NATIVE_CHAR_TYPE *)g_utf8_to_ucs4((const gchar *)zB,
573  nB,
574  NULL,
575  NULL,
576  NULL);
577 
578 #elif XP_WIN
579 
580  a = cBuffers->encodingConversionBuffer1.buffer();
581  b = cBuffers->encodingConversionBuffer2.buffer();
582 
583  PRInt32 cnA = MultiByteToWideChar(CP_UTF8,
584  0,
585  (LPCSTR)zA,
586  nA,
587  a,
588  cBuffers->encodingConversionBuffer1
589  .bufferLength());
590 
591  PRInt32 cnB = MultiByteToWideChar(CP_UTF8,
592  0,
593  (LPCSTR)zB,
594  nB,
595  b,
596  cBuffers->encodingConversionBuffer2
597  .bufferLength());
598  a[cnA] = 0;
599  b[cnB] = 0;
600 
601 #endif
602 
603  int r = library_collate_func(cBuffers,
604  (const NATIVE_CHAR_TYPE *)a,
605  (const NATIVE_CHAR_TYPE *)b);
606 
607 #if defined(XP_UNIX) && !defined(XP_MACOSX)
608 
609  g_free(a);
610  g_free(b);
611 
612 #endif
613 
614  return r;
615 }
616 
617 //-----------------------------------------------------------------------------
618 /* Sqlite Dump Helper Class */
619 //-----------------------------------------------------------------------------
620 class CDatabaseDumpProcessor : public nsIRunnable
621 {
622 public:
624  QueryProcessorQueue *aQueryProcessorQueue,
625  nsIFile *aOutputFile);
626  virtual ~CDatabaseDumpProcessor();
628  NS_DECL_NSIRUNNABLE
629 
630 protected:
631  nsresult OutputBuffer(const char *aBuffer);
632  PRInt32 RunSchemaDumpQuery(const nsACString & aQuery);
633  PRInt32 RunTableDumpQuery(const nsACString & aSelect);
634 
635  static int DumpCallback(void *pArg, int inArg,
636  char **azArg, const char **azCol);
637  static char *appendText(char *zIn, char const *zAppend,
638  char quote);
639 
640 protected:
641  nsCOMPtr<nsIFileOutputStream> mOutputStream;
642  nsCOMPtr<nsIFile> mOutputFile;
643  nsRefPtr<CDatabaseEngine> mEngineCallback;
644  nsRefPtr<QueryProcessorQueue> mQueryProcessorQueue;
645  PRBool writeableSchema; // true if PRAGMA writable_schema=on
646 };
647 
648 
650 
652  QueryProcessorQueue *aQueryProcessorQueue,
653  nsIFile *aOutputFile)
654 : mOutputFile(aOutputFile)
655 , mEngineCallback(aCallback)
656 , mQueryProcessorQueue(aQueryProcessorQueue)
657 {
658 }
659 
661 {
662 }
663 
664 NS_IMETHODIMP
665 CDatabaseDumpProcessor::Run()
666 {
667  nsresult rv;
668  mOutputStream =
669  do_CreateInstance("@mozilla.org/network/file-output-stream;1", &rv);
670  NS_ENSURE_SUCCESS(rv, rv);
671 
672  rv = mOutputStream->Init(mOutputFile, -1, 0600, 0);
673  NS_ENSURE_SUCCESS(rv, rv);
674 
675  {
676  nsAutoLock handleLock(mQueryProcessorQueue->m_pHandleLock);
677 
678  // First query, 'schema' dump
679  PRInt32 rc;
680  nsCString schemaDump;
681  schemaDump.AppendLiteral("SELECT name, type, sql FROM sqlite_master "
682  "WHERE sql NOT NULL and type=='table'");
683  rc = RunSchemaDumpQuery(schemaDump);
684  if (rc != SQLITE_OK) {
685  return NS_ERROR_FAILURE;
686  }
687 
688  // Next query, 'table' dump
689  nsCString tableDump;
690  tableDump.AppendLiteral("SELECT sql FROM sqlite_master "
691  "WHERE sql NOT NULL AND type IN ('index', 'trigger', 'view')");
692  rc = RunTableDumpQuery(tableDump);
693  if (rc != SQLITE_OK) {
694  return NS_ERROR_FAILURE;
695  }
696  }
697 
698  return NS_OK;
699 }
700 
701 nsresult
703 {
704  NS_ENSURE_ARG_POINTER(aBuffer);
705 
706  nsresult rv = NS_OK;
707  nsCString buffer(aBuffer);
708  if (buffer.Length() > 0) {
709  PRUint32 writeCount;
710  rv = mOutputStream->Write(buffer.get(), buffer.Length(), &writeCount);
711  NS_ENSURE_SUCCESS(rv, rv);
712  }
713 
714  return rv;
715 }
716 
717 PRInt32
719 {
720  // Run |aQuery|. Use |DumpCallback()| as the callback routine so that
721  // the contents of the query are output as SQL statements.
722  //
723  // If we get a SQLITE_CORRUPT error, rerun the query after appending
724  // "ORDER BY rowid DESC" to the end.
725  nsCString query(aQuery);
726  PRInt32 rc = sqlite3_exec(mQueryProcessorQueue->m_pHandle,
727  query.get(),
728  (sqlite3_callback)DumpCallback,
729  this,
730  0);
731  if (rc == SQLITE_CORRUPT) {
732  char *zQ2 = (char *)malloc(query.Length() + 100);
733  if (zQ2 == 0) {
734  return rc;
735  }
736 
737  sqlite3_snprintf(sizeof(zQ2), zQ2, "%s ORDER BY rowid DESC", query.get());
738  rc = sqlite3_exec(mQueryProcessorQueue->m_pHandle,
739  zQ2,
740  (sqlite3_callback)DumpCallback,
741  this,
742  0);
743  free(zQ2);
744  }
745 
746  return rc;
747 }
748 
749 PRInt32
750 CDatabaseDumpProcessor::RunTableDumpQuery(const nsACString & aSelect)
751 {
752  nsCString select(aSelect);
753  sqlite3_stmt *pSelect;
754  int rc = sqlite3_prepare(mQueryProcessorQueue->m_pHandle,
755  select.get(),
756  -1,
757  &pSelect,
758  0);
759  if (rc != SQLITE_OK || !pSelect) {
760  return rc;
761  }
762 
763  nsresult rv;
764  rc = sqlite3_step(pSelect);
765  while (rc == SQLITE_ROW) {
766  rv = OutputBuffer((const char *)sqlite3_column_text(pSelect, 0));
767  NS_ENSURE_SUCCESS(rv, rv);
768  rv = OutputBuffer(";\n");
769  NS_ENSURE_SUCCESS(rv, rv);
770 
771  rc = sqlite3_step(pSelect);
772  }
773 
774  return sqlite3_finalize(pSelect);
775 }
776 
777 /* static */ int
779  int inArg,
780  char **azArg,
781  const char **azCol)
782 {
783  // This callback routine is used for dumping the database.
784  // Each row received by this callback consists of a table name.
785  // The table type ("index" or "table") and SQL to create the table.
786  // This routine should print text sufficient to recreate the table.
787  int rc;
788  const char *zTable;
789  const char *zType;
790  const char *zSql;
791  CDatabaseDumpProcessor *dumpProcessor = (CDatabaseDumpProcessor *)pArg;
792 
793  if (inArg != 3)
794  return 1;
795 
796  zTable = azArg[0];
797  zType = azArg[1];
798  zSql = azArg[2];
799 
800  if (strcmp(zTable, "sqlite_sequence") == 0) {
801  dumpProcessor->OutputBuffer("DELETE FROM sqlite_sequence;\n");
802  }
803  else if (strcmp(zTable, "sqlite_stat1") == 0) {
804  dumpProcessor->OutputBuffer("ANALYZE sqlite_master;\n");
805  }
806  else if (strncmp(zTable, "sqlite_", 7) == 0) {
807  return 0;
808  }
809  else if (strncmp(zSql, "CREATE VIRTUAL TABLE", 20) == 0) {
810  char *zIns;
811  if (!dumpProcessor->writeableSchema) {
812  dumpProcessor->OutputBuffer("PRAGMA writable_schema=ON;\n");
813  dumpProcessor->writeableSchema = PR_TRUE;
814  }
815  zIns = sqlite3_mprintf(
816  "INSERT INTO sqlite_master(type,name,tbl_name,rootpage,sql)"
817  "VALUES('table','%q','%q',0,'%q');",
818  zTable, zTable, zSql);
819  dumpProcessor->OutputBuffer(zIns);
820  dumpProcessor->OutputBuffer("\n");
821  sqlite3_free(zIns);
822  return 0;
823  }
824  else {
825  dumpProcessor->OutputBuffer(zSql);
826  dumpProcessor->OutputBuffer(";\n");
827  }
828 
829  if (strcmp(zType, "table") == 0) {
830  sqlite3_stmt *pTableInfo = 0;
831  char *zSelect = 0;
832  char *zTableInfo = 0;
833  char *zTmp = 0;
834 
835  zTableInfo = appendText(zTableInfo, "PRAGMA table_info(", 0);
836  zTableInfo = appendText(zTableInfo, zTable, '"');
837  zTableInfo = appendText(zTableInfo, ");", 0);
838 
839  rc = sqlite3_prepare(dumpProcessor->mQueryProcessorQueue->m_pHandle,
840  zTableInfo, -1, &pTableInfo, 0);
841  if (zTableInfo) {
842  free(zTableInfo);
843  }
844  if (rc != SQLITE_OK || !pTableInfo) {
845  return 1;
846  }
847 
848  zSelect = appendText(zSelect, "SELECT 'INSERT INTO ' || ", 0);
849  zTmp = appendText(zTmp, zTable, '"');
850  if (zTmp) {
851  zSelect = appendText(zSelect, zTmp, '\'');
852  }
853  zSelect = appendText(zSelect, " || ' VALUES(' || ", 0);
854  rc = sqlite3_step(pTableInfo);
855  while (rc == SQLITE_ROW) {
856  const char *zText = (const char *)sqlite3_column_text(pTableInfo, 1);
857  zSelect = appendText(zSelect, "quote(", 0);
858  zSelect = appendText(zSelect, zText, '"');
859  rc = sqlite3_step(pTableInfo);
860  if (rc == SQLITE_ROW) {
861  zSelect = appendText(zSelect, ") || ',' || ", 0);
862  }
863  else {
864  zSelect = appendText(zSelect, ") ", 0);
865  }
866  }
867  rc = sqlite3_finalize(pTableInfo);
868  if (rc != SQLITE_OK) {
869  if (zSelect) {
870  free(zSelect);
871  }
872  return 1;
873  }
874  zSelect = appendText(zSelect, "|| ')' FROM ", 0);
875  zSelect = appendText(zSelect, zTable, '"');
876 
877  rc = dumpProcessor->RunTableDumpQuery(nsDependentCString(zSelect));
878  if (rc == SQLITE_CORRUPT) {
879  zSelect = appendText(zSelect, " ORDER BY rowid DESC", 0);
880  rc = dumpProcessor->RunTableDumpQuery(nsDependentCString(zSelect));
881  }
882  if (zSelect) {
883  free(zSelect);
884  }
885  }
886  return 0;
887 }
888 
889 /* static */ char*
891  char const *zAppend,
892  char quote)
893 {
894  // zIn is either a pointer to a NULL-terminated string in memory obtained
895  // from |malloc()|, or a NULL pointer. The string pointed to by zAppend
896  // is added to zIn, and the result returned in memory obtained from |malloc()|.
897  // zIn, if it was not NULL, is freed.
898  int len;
899  int i;
900  int nAppend = strlen(zAppend);
901  int nIn = (zIn ? strlen(zIn) : 0);
902 
903  len = nAppend + nIn + 1;
904  if (quote) {
905  len += 2;
906  for (i = 0; i < nAppend; i++) {
907  if (zAppend[i] == quote) {
908  len++;
909  }
910  }
911  }
912 
913  zIn = (char *)realloc(zIn, len);
914  if (!zIn) {
915  return 0;
916  }
917 
918  if (quote) {
919  char *zCsr = &zIn[nIn];
920  *zCsr++ = quote;
921  for (i = 0; i < nAppend; i++) {
922  *zCsr++ = zAppend[i];
923  if (zAppend[i] == quote) {
924  *zCsr++ = quote;
925  }
926  }
927  *zCsr++ = quote;
928  *zCsr++ = '\0';
929  assert((zCsr - zIn) == len);
930  }
931  else{
932  memcpy(&zIn[nIn], zAppend, nAppend);
933  zIn[len-1] = '\0';
934  }
935 
936  return zIn;
937 }
938 
939 //-----------------------------------------------------------------------------
940 
943 
945 
946 // CLASSES ====================================================================
947 //-----------------------------------------------------------------------------
949 : m_pDBStorePathLock(nsnull)
950 , m_pThreadMonitor(nsnull)
951 , m_CollationBuffersMapMonitor(nsnull)
952 , m_AttemptShutdownOnDestruction(PR_FALSE)
953 , m_IsShutDown(PR_FALSE)
954 , m_MemoryConstraintsSet(PR_FALSE)
955 , m_PromptForDelete(PR_FALSE)
956 , m_DeleteDatabases(PR_FALSE)
957 , m_AddedIdleObserver(PR_FALSE)
958 , m_pPageSpace(nsnull)
959 , m_pScratchSpace(nsnull)
960 #ifdef XP_MACOSX
961 , m_Collator(nsnull)
962 #endif
963 {
965 } //ctor
966 
967 //-----------------------------------------------------------------------------
969 {
970  if (m_AttemptShutdownOnDestruction)
971  Shutdown();
972  if (m_pDBStorePathLock)
973  PR_DestroyLock(m_pDBStorePathLock);
974  if (m_pThreadMonitor)
975  nsAutoMonitor::DestroyMonitor(m_pThreadMonitor);
976  if (m_CollationBuffersMapMonitor)
977  nsAutoMonitor::DestroyMonitor(m_CollationBuffersMapMonitor);
978 
979  if (m_MemoryConstraintsSet) {
980  if (m_pPageSpace) {
981  NS_Free(m_pPageSpace);
982  }
983  if (m_pScratchSpace) {
984  NS_Free(m_pScratchSpace);
985  }
986  }
987 } //dtor
988 
989 //-----------------------------------------------------------------------------
991 {
992  if (gEngine) {
993  NS_ADDREF(gEngine);
994  return gEngine;
995  }
996 
997  NS_NEWXPCOM(gEngine, CDatabaseEngine);
998  if (!gEngine)
999  return nsnull;
1000 
1001  // AddRef once for us (released in nsModule destructor)
1002  NS_ADDREF(gEngine);
1003 
1004  // Set ourselves up properly
1005  if (NS_FAILED(gEngine->Init())) {
1006  NS_ERROR("Failed to Init CDatabaseEngine!");
1007  NS_RELEASE(gEngine);
1008  return nsnull;
1009  }
1010 
1011  // And AddRef once for the caller
1012  NS_ADDREF(gEngine);
1013  return gEngine;
1014 }
1015 
1016 //-----------------------------------------------------------------------------
1017 NS_IMETHODIMP CDatabaseEngine::Init()
1018 {
1019  LOG("CDatabaseEngine[0x%.8x] - Init() - sqlite version %s",
1020  this, sqlite3_libversion());
1021 
1022  PRBool success = m_QueuePool.Init();
1023  NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY);
1024 
1025  m_pThreadMonitor =
1026  nsAutoMonitor::NewMonitor("CDatabaseEngine.m_pThreadMonitor");
1027 
1028  NS_ENSURE_TRUE(m_pThreadMonitor, NS_ERROR_OUT_OF_MEMORY);
1029 
1030  m_CollationBuffersMapMonitor =
1031  nsAutoMonitor::NewMonitor("CDatabaseEngine.m_CollationBuffersMapMonitor");
1032 
1033  NS_ENSURE_TRUE(m_CollationBuffersMapMonitor, NS_ERROR_OUT_OF_MEMORY);
1034 
1035  m_pDBStorePathLock = PR_NewLock();
1036  NS_ENSURE_TRUE(m_pDBStorePathLock, NS_ERROR_OUT_OF_MEMORY);
1037 
1038  nsresult rv = CreateDBStorePath();
1039  NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to create db store folder in profile!");
1040 
1041  nsCOMPtr<nsIObserverService> observerService =
1042  do_GetService("@mozilla.org/observer-service;1", &rv);
1043  if(NS_SUCCEEDED(rv)) {
1044  rv = observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID,
1045  PR_FALSE);
1046 
1047  rv = observerService->AddObserver(this, NS_FINAL_UI_STARTUP_CATEGORY,
1048  PR_FALSE);
1049  }
1050 
1051  // This shouldn't be an 'else' case because we want to set this flag if
1052  // either of the above calls failed
1053  if(NS_FAILED(rv)) {
1054  NS_ERROR("Unable to register xpcom-shutdown observer");
1055  m_AttemptShutdownOnDestruction = PR_TRUE;
1056  }
1057 
1058  // Select a collation locale for the entire lifetime of the app, so that
1059  // it cannot change on us.
1060  rv = GetCurrentCollationLocale(mCollationLocale);
1061 
1062 #ifdef XP_MACOSX
1063 
1064  LocaleRef l;
1065  ::LocaleRefFromLocaleString(mCollationLocale.get(), &l);
1066 
1067  ::UCCreateCollator(l,
1068  kUnicodeCollationClass,
1069  kUCCollateStandardOptions |
1070  kUCCollatePunctuationSignificantMask,
1071  &m_Collator);
1072 #else
1073  setlocale(LC_COLLATE, mCollationLocale.get());
1074 #endif
1075 
1076  m_pThreadPool = do_CreateInstance("@mozilla.org/thread-pool;1", &rv);
1077  NS_ENSURE_SUCCESS(rv, rv);
1078 
1079  rv = m_pThreadPool->SetThreadLimit(4);
1080  NS_ENSURE_SUCCESS(rv, rv);
1081 
1082  rv = m_pThreadPool->SetIdleThreadLimit(1);
1083  NS_ENSURE_SUCCESS(rv, rv);
1084 
1085  rv = m_pThreadPool->SetIdleThreadTimeout(30000);
1086  NS_ENSURE_SUCCESS(rv, rv);
1087 
1088  nsCOMPtr<nsIIdleService> idleService =
1089  do_GetService("@mozilla.org/widget/idleservice;1", &rv);
1090 
1091  if(NS_SUCCEEDED(rv)) {
1092  rv = idleService->AddIdleObserver(this, IDLE_SERVICE_TIMEOUT);
1093  m_AddedIdleObserver = NS_SUCCEEDED(rv) ? PR_TRUE : PR_FALSE;
1094  }
1095 
1096  return NS_OK;
1097 }
1098 
1099 //-----------------------------------------------------------------------------
1100 PR_STATIC_CALLBACK(PLDHashOperator)
1101 EnumQueuesOperate(nsStringHashKey::KeyType aKey, QueryProcessorQueue *aQueue, void *aClosure)
1102 {
1103  NS_ASSERTION(aQueue, "aQueue is null");
1104  NS_ASSERTION(aClosure, "aClosure is null");
1105 
1106  // Stop if thread is null.
1107  NS_ENSURE_TRUE(aQueue, PL_DHASH_STOP);
1108 
1109  // Stop if closure is null because it
1110  // contains the operation we are going to perform.
1111  NS_ENSURE_TRUE(aClosure, PL_DHASH_STOP);
1112 
1113  nsresult rv;
1114  PRUint32 *op = static_cast<PRUint32 *>(aClosure);
1115 
1116  switch(*op) {
1118  rv = aQueue->PrepareForShutdown();
1119  NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to prepare worker thread for shutdown.");
1120  break;
1121 
1123  rv = aQueue->Shutdown();
1124  NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to shut down processor queue.");
1125  break;
1126 
1127  default:
1128  ;
1129  }
1130 
1131  return PL_DHASH_NEXT;
1132 }
1133 
1134 //-----------------------------------------------------------------------------
1136 {
1137  m_IsShutDown = PR_TRUE;
1138 
1139  PRUint32 op = dbEnginePreShutdown;
1140  m_QueuePool.EnumerateRead(EnumQueuesOperate, &op);
1141 
1142  op = dbEngineShutdown;
1143  m_QueuePool.EnumerateRead(EnumQueuesOperate, &op);
1144 
1145  m_QueuePool.Clear();
1146 
1147  nsresult rv = m_pThreadPool->Shutdown();
1148  NS_ENSURE_SUCCESS(rv, rv);
1149 
1150  if(m_AddedIdleObserver) {
1151  nsCOMPtr<nsIIdleService> idleService =
1152  do_GetService("@mozilla.org/widget/idleservice;1", &rv);
1153  if(NS_SUCCEEDED(rv)) {
1154  rv = idleService->RemoveIdleObserver(this, IDLE_SERVICE_TIMEOUT);
1155  NS_WARN_IF_FALSE(NS_SUCCEEDED(rv),
1156  "Failed to remove idle service observer.");
1157  }
1158  }
1159 
1160 #ifdef XP_MACOSX
1161  if (m_Collator)
1162  ::UCDisposeCollator(&m_Collator);
1163 #endif
1164 
1165  if(m_PromptForDeleteTimer) {
1166  rv = m_PromptForDeleteTimer->Cancel();
1167  NS_ENSURE_SUCCESS(rv, rv);
1168 
1169  m_PromptForDeleteTimer = nsnull;
1170  }
1171 
1172  return NS_OK;
1173 }
1174 
1175 //-----------------------------------------------------------------------------
1176 NS_IMETHODIMP CDatabaseEngine::Observe(nsISupports *aSubject,
1177  const char *aTopic,
1178  const PRUnichar *aData)
1179 {
1180  nsresult rv = NS_ERROR_UNEXPECTED;
1181 
1182  if(!strcmp(aTopic, NS_FINAL_UI_STARTUP_CATEGORY)) {
1183  nsCOMPtr<nsIObserverService> observerService =
1184  do_GetService("@mozilla.org/observer-service;1", &rv);
1185  NS_ENSURE_SUCCESS(rv, rv);
1186 
1187  rv = observerService->RemoveObserver(this, NS_FINAL_UI_STARTUP_CATEGORY);
1188  NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Remove Observer Failed!");
1189 
1190  nsAutoMonitor mon(m_pThreadMonitor);
1191  if(m_PromptForDelete) {
1192  mon.Exit();
1193 
1194  rv = PromptToDeleteDatabases();
1195  NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Prompting to Delete Databases Failed!");
1196 
1197  mon.Enter();
1198  }
1199 
1200  m_PromptForDeleteTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
1201  NS_ENSURE_SUCCESS(rv, rv);
1202  }
1203  else if(!strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC)) {
1204  nsAutoMonitor mon(m_pThreadMonitor);
1205  if(m_PromptForDelete) {
1206  mon.Exit();
1207 
1208  rv = PromptToDeleteDatabases();
1209  NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Prompting to Delete Databases Failed!");
1210 
1211  mon.Enter();
1212  }
1213  }
1214  else if(!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
1215  nsCOMPtr<nsIObserverService> observerService =
1216  do_GetService("@mozilla.org/observer-service;1", &rv);
1217  NS_ENSURE_SUCCESS(rv, rv);
1218 
1219  rv = observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
1220  NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Remove Observer Failed!");
1221 
1222  // Shutdown our threads
1223  rv = Shutdown();
1224  NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Shutdown Failed!");
1225 
1226  // Delete any bad databases now
1227  rv = DeleteMarkedDatabases();
1228  NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to delete bad databases!");
1229  }
1230  else if(!strcmp(aTopic, "idle")) {
1231  rv = RunAnalyze();
1232  NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to run ANALYZE on Databases.");
1233  }
1234 
1235  return NS_OK;
1236 }
1237 
1238 
1239 
1240 //-----------------------------------------------------------------------------
1241 nsresult CDatabaseEngine::InitMemoryConstraints()
1242 {
1243  if (m_MemoryConstraintsSet)
1244  return NS_ERROR_ALREADY_INITIALIZED;
1245 
1246  nsresult rv = NS_OK;
1247 
1248  PRInt32 preAllocCache;
1249  PRInt32 preAllocScratch;
1250  PRInt32 softLimit;
1251  PRInt32 pageSize;
1252 
1253  // Load values from the pref system
1254  nsCOMPtr<nsIPrefService> prefService =
1255  do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
1256  NS_ENSURE_SUCCESS(rv, rv);
1257  nsCOMPtr<nsIPrefBranch> prefBranch;
1258  rv = prefService->GetBranch(PREF_BRANCH_BASE, getter_AddRefs(prefBranch));
1259  if (NS_FAILED(rv) ||
1260  NS_FAILED(prefBranch->GetIntPref(PREF_DB_PREALLOCCACHE_SIZE,
1261  &preAllocCache))) {
1262  NS_WARNING("DBEngine failed to get preAllocCache pref. Using default.");
1263  preAllocCache = DEFAULT_PREALLOCCACHE_SIZE;
1264  }
1265  if (NS_FAILED(rv) ||
1266  NS_FAILED(prefBranch->GetIntPref(PREF_DB_PREALLOCSCRATCH_SIZE,
1267  &preAllocScratch))) {
1268  NS_WARNING("DBEngine failed to get preAllocScratch pref. Using default.");
1269  preAllocScratch = DEFAULT_PREALLOCSCRATCH_SIZE;
1270  }
1271  if (NS_FAILED(rv) ||NS_FAILED(prefBranch->GetIntPref(PREF_DB_SOFT_LIMIT,
1272  &softLimit))) {
1273  NS_WARNING("DBEngine failed to get soft heap limit pref. Using default.");
1274  softLimit = 0;
1275  }
1276  if (NS_FAILED(rv) || NS_FAILED(prefBranch->GetIntPref(PREF_DB_PAGE_SIZE,
1277  &pageSize))) {
1278  NS_WARNING("DBEngine failed to get page size pref. Using default.");
1279  pageSize = DEFAULT_PAGE_SIZE;
1280  }
1281 
1282  PRInt32 ret;
1283 
1284  if (preAllocCache > 0) {
1285  m_pPageSpace = NS_Alloc(pageSize * preAllocCache);
1286  if (!m_pPageSpace) {
1287  return NS_ERROR_OUT_OF_MEMORY;
1288  }
1289  ret = sqlite3_config(SQLITE_CONFIG_PAGECACHE, m_pPageSpace,
1290  pageSize, preAllocCache);
1291  NS_ENSURE_TRUE(ret == SQLITE_OK, NS_ERROR_FAILURE);
1292  }
1293 
1294  if (preAllocScratch > 0) {
1295  // http://www.sqlite.org/malloc.html recommends slots 6x page size,
1296  // with as many slots as there are threads.
1297  PRInt32 scratchSlotSize = pageSize * 6;
1298  m_pScratchSpace = NS_Alloc(scratchSlotSize * preAllocScratch);
1299  if (!m_pScratchSpace) {
1300  return NS_ERROR_OUT_OF_MEMORY;
1301  }
1302  ret = sqlite3_config(SQLITE_CONFIG_SCRATCH, m_pScratchSpace,
1303  scratchSlotSize, preAllocScratch);
1304  NS_ENSURE_TRUE(ret == SQLITE_OK, NS_ERROR_FAILURE);
1305  }
1306 
1307  // Try not to use more than X memory...
1308  if (softLimit > 0) {
1309  sqlite3_soft_heap_limit(softLimit);
1310  }
1311 
1312  // Only allow init to happen once
1313  m_MemoryConstraintsSet = PR_TRUE;
1314 
1315  return rv;
1316 }
1317 
1318 //-----------------------------------------------------------------------------
1319 nsresult CDatabaseEngine::GetDBPrefs(const nsAString &dbGUID,
1320  PRInt32 *cacheSize,
1321  PRInt32 *pageSize)
1322 {
1323  nsresult rv = NS_OK;
1324 
1325  nsCOMPtr<nsIPrefService> prefService =
1326  do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
1327  NS_ENSURE_SUCCESS(rv, rv);
1328  nsCOMPtr<nsIPrefBranch> prefBranch;
1329  rv = prefService->GetBranch(PREF_BRANCH_BASE, getter_AddRefs(prefBranch));
1330 
1331  if (NS_FAILED(rv) || NS_FAILED(prefBranch->GetIntPref(PREF_DB_CACHE_SIZE,
1332  cacheSize))) {
1333  NS_WARNING("DBEngine failed to get cache size pref. Using default.");
1334  *cacheSize = DEFAULT_CACHE_SIZE;
1335  }
1336 
1337  if (NS_FAILED(rv) || NS_FAILED(prefBranch->GetIntPref(PREF_DB_PAGE_SIZE,
1338  pageSize))) {
1339  NS_WARNING("DBEngine failed to get page size pref. Using default.");
1340  *pageSize = DEFAULT_PAGE_SIZE;
1341  }
1342 
1343  // Now try for values that are specific to this database guid
1344  // e.g. songbird.dbengine.main@library.songbirdnest.com.cacheSize
1345  nsCString dbBranch(PREF_BRANCH_BASE);
1346  dbBranch.Append(NS_ConvertUTF16toUTF8(dbGUID));
1347  dbBranch.Append(NS_LITERAL_CSTRING("."));
1348  if (NS_SUCCEEDED(prefService->GetBranch(dbBranch.get(),
1349  getter_AddRefs(prefBranch)))) {
1350  prefBranch->GetIntPref(PREF_DB_CACHE_SIZE, cacheSize);
1351  prefBranch->GetIntPref(PREF_DB_PAGE_SIZE, pageSize);
1352  }
1353 
1354  return rv;
1355 }
1356 
1357 //-----------------------------------------------------------------------------
1358 nsresult CDatabaseEngine::OpenDB(const nsAString &dbGUID,
1359  CDatabaseQuery *pQuery,
1360  sqlite3 ** ppHandle)
1361 {
1362  sqlite3 *pHandle = nsnull;
1363 
1364  nsAutoString strFilename;
1365  GetDBStorePath(dbGUID, pQuery, strFilename);
1366 
1367 #if defined(USE_SQLITE_SHARED_CACHE)
1368  sqlite3_enable_shared_cache(1);
1369 #endif
1370 
1371  // Allow the user to control how much memory sqlite uses.
1372  if (!m_MemoryConstraintsSet) {
1373  if (NS_FAILED(InitMemoryConstraints())) {
1374  NS_WARNING("DBEngine failed to set memory usage constraints.");
1375  }
1376  }
1377 
1378  PRInt32 ret = sqlite3_open(NS_ConvertUTF16toUTF8(strFilename).get(), &pHandle);
1379  NS_ASSERTION(ret == SQLITE_OK, "Failed to open database: sqlite_open failed!");
1380  NS_ENSURE_TRUE(ret == SQLITE_OK, NS_ERROR_UNEXPECTED);
1381 
1382  ret = sqlite3_create_collation(pHandle,
1383  "tree",
1384  SQLITE_UTF16BE,
1385  NULL,
1387  NS_ASSERTION(ret == SQLITE_OK, "Failed to set tree collate function: utf16-be!");
1388  NS_ENSURE_TRUE(ret == SQLITE_OK, NS_ERROR_UNEXPECTED);
1389 
1390  ret = sqlite3_create_collation(pHandle,
1391  "tree",
1392  SQLITE_UTF16LE,
1393  NULL,
1395  NS_ASSERTION(ret == SQLITE_OK, "Failed to set tree collate function: utf16-le!");
1396  NS_ENSURE_TRUE(ret == SQLITE_OK, NS_ERROR_UNEXPECTED);
1397 
1398  ret = sqlite3_create_collation(pHandle,
1399  "tree",
1400  SQLITE_UTF8,
1401  NULL,
1403  NS_ASSERTION(ret == SQLITE_OK, "Failed to set tree collate function: utf8!");
1404  NS_ENSURE_TRUE(ret == SQLITE_OK, NS_ERROR_UNEXPECTED);
1405 
1406  collationBuffers *collationBuffersEntry = new collationBuffers();
1407 
1408  {
1409  nsAutoMonitor mon(m_CollationBuffersMapMonitor);
1410  m_CollationBuffersMap[pHandle] = collationBuffersEntry;
1411  }
1412 
1413  ret = sqlite3_create_collation(pHandle,
1414  "library_collate",
1415  SQLITE_UTF8,
1416  collationBuffersEntry,
1418  NS_ASSERTION(ret == SQLITE_OK, "Failed to set library collate function: utf8!");
1419  NS_ENSURE_TRUE(ret == SQLITE_OK, NS_ERROR_UNEXPECTED);
1420 
1421  ret = sqlite3_create_collation(pHandle,
1422  "library_collate",
1423  SQLITE_UTF16LE,
1424  collationBuffersEntry,
1426  NS_ASSERTION(ret == SQLITE_OK, "Failed to set library collate function: utf16le!");
1427  NS_ENSURE_TRUE(ret == SQLITE_OK, NS_ERROR_UNEXPECTED);
1428 
1429  ret = sqlite3_create_collation(pHandle,
1430  "library_collate",
1431  SQLITE_UTF16BE,
1432  collationBuffersEntry,
1434  NS_ASSERTION(ret == SQLITE_OK, "Failed to set library collate function: utf16be!");
1435  NS_ENSURE_TRUE(ret == SQLITE_OK, NS_ERROR_UNEXPECTED);
1436 
1437 
1438  PRInt32 pageSize = DEFAULT_PAGE_SIZE;
1439  PRInt32 cacheSize = DEFAULT_CACHE_SIZE;
1440 
1441  if (NS_FAILED(GetDBPrefs(dbGUID, &cacheSize, &pageSize))) {
1442  NS_WARNING("DBEngine failed to get memory prefs. Using default.");
1443  }
1444 
1445  nsCString query;
1446 
1447  {
1448  char *strErr = nsnull;
1449  query = NS_LITERAL_CSTRING("PRAGMA page_size = ");
1450  query.AppendInt(pageSize);
1451  sqlite3_exec(pHandle, query.get(), nsnull, nsnull, &strErr);
1452  if(strErr) {
1453  NS_WARNING(strErr);
1454  sqlite3_free(strErr);
1455  }
1456  }
1457 
1458  {
1459  char *strErr = nsnull;
1460  query = NS_LITERAL_CSTRING("PRAGMA cache_size = ");
1461  query.AppendInt(cacheSize);
1462  sqlite3_exec(pHandle, query.get(), nsnull, nsnull, &strErr);
1463  if(strErr) {
1464  NS_WARNING(strErr);
1465  sqlite3_free(strErr);
1466  }
1467  }
1468 
1469 #if defined(USE_SQLITE_FULL_DISK_CACHING)
1470  {
1471  char *strErr = nsnull;
1472  sqlite3_exec(pHandle, "PRAGMA synchronous = 0", nsnull, nsnull, &strErr);
1473  if(strErr) {
1474  NS_WARNING(strErr);
1475  sqlite3_free(strErr);
1476  }
1477  }
1478 #endif
1479 
1480 #if defined(USE_SQLITE_READ_UNCOMMITTED)
1481  {
1482  char *strErr = nsnull;
1483  sqlite3_exec(pHandle, "PRAGMA read_uncommitted = 1", nsnull, nsnull, &strErr);
1484  if(strErr) {
1485  NS_WARNING(strErr);
1486  sqlite3_free(strErr);
1487  }
1488  }
1489 #endif
1490 
1491 #if defined(USE_SQLITE_MEMORY_TEMP_STORE)
1492  {
1493  char *strErr = nsnull;
1494  sqlite3_exec(pHandle, "PRAGMA temp_store = 2", nsnull, nsnull, &strErr);
1495  if(strErr) {
1496  NS_WARNING(strErr);
1497  sqlite3_free(strErr);
1498  }
1499  }
1500 #endif
1501 
1502 #if defined(USE_SQLITE_BUSY_TIMEOUT)
1503  sqlite3_busy_timeout(pHandle, 120000);
1504 #endif
1505 
1506  *ppHandle = pHandle;
1507 
1508  return NS_OK;
1509 } //OpenDB
1510 
1511 //-----------------------------------------------------------------------------
1512 nsresult CDatabaseEngine::CloseDB(sqlite3 *pHandle)
1513 {
1514  PRInt32 retries = 0;
1515  PRInt32 ret = SQLITE_BUSY;
1516 
1517  do {
1518  sqlite3_interrupt(pHandle);
1519  if((ret = sqlite3_close(pHandle)) == SQLITE_BUSY) {
1520  PR_Sleep(PR_MillisecondsToInterval(50));
1521  }
1522  }
1523  while(ret == SQLITE_BUSY &&
1524  retries++ < MAX_BUSY_RETRY_CLOSE_DB);
1525 
1526  {
1527  nsAutoMonitor mon(m_CollationBuffersMapMonitor);
1528  collationMap_t::const_iterator found = m_CollationBuffersMap.find(pHandle);
1529  if (found != m_CollationBuffersMap.end()) {
1530  delete found->second;
1531  m_CollationBuffersMap.erase(pHandle);
1532  }
1533  }
1534 
1535  NS_ASSERTION(ret == SQLITE_OK, "");
1536  NS_ENSURE_TRUE(ret == SQLITE_OK, NS_ERROR_UNEXPECTED);
1537 
1538  return NS_OK;
1539 } //CloseDB
1540 
1541 //-----------------------------------------------------------------------------
1542 NS_IMETHODIMP CDatabaseEngine::CloseDatabase(const nsAString &aDatabaseGUID)
1543 {
1544  nsAutoMonitor mon(m_pThreadMonitor);
1545 
1546  nsRefPtr<QueryProcessorQueue> pQueue;
1547  if(m_QueuePool.Get(aDatabaseGUID, getter_AddRefs(pQueue))) {
1548 
1549  nsresult rv = pQueue->PrepareForShutdown();
1550  NS_ENSURE_SUCCESS(rv, rv);
1551 
1552  rv = pQueue->Shutdown();
1553  NS_ENSURE_SUCCESS(rv, rv);
1554 
1555  m_QueuePool.Remove(aDatabaseGUID);
1556  }
1557 
1558  return NS_OK;
1559 }
1560 
1561 //-----------------------------------------------------------------------------
1562 /* [noscript] PRInt32 SubmitQuery (in CDatabaseQueryPtr dbQuery); */
1563 NS_IMETHODIMP CDatabaseEngine::SubmitQuery(CDatabaseQuery * dbQuery, PRInt32 *_retval)
1564 {
1565  if (m_IsShutDown) {
1566  NS_WARNING("Don't submit queries after the DBEngine is shut down!");
1567  return NS_ERROR_FAILURE;
1568  }
1569 
1570  *_retval = SubmitQueryPrivate(dbQuery);
1571  return NS_OK;
1572 }
1573 
1574 //-----------------------------------------------------------------------------
1576 {
1577  //Query is null, bail.
1578  if(!pQuery) {
1579  NS_WARNING("A null queury was submitted to the database engine");
1580  return 1;
1581  }
1582 
1583  //Grip.
1584  NS_ADDREF(pQuery);
1585 
1586  // If the query is already executing, do not add it. This is to prevent
1587  // the same query from getting executed simultaneously
1588  PRBool isExecuting = PR_FALSE;
1589  pQuery->IsExecuting(&isExecuting);
1590  if(isExecuting) {
1591  //Release grip.
1592  NS_RELEASE(pQuery);
1593  return 0;
1594  }
1595 
1596  nsRefPtr<QueryProcessorQueue> pQueue = GetQueueByQuery(pQuery, PR_TRUE);
1597  NS_ENSURE_TRUE(pQueue, 1);
1598 
1599  nsresult rv = pQueue->PushQueryToQueue(pQuery);
1600  NS_ENSURE_SUCCESS(rv, 1);
1601 
1602  {
1603  sbSimpleAutoLock lock(pQuery->m_pLock);
1604  pQuery->m_IsExecuting = PR_TRUE;
1605  }
1606 
1607  rv = pQueue->RunQueue();
1608  NS_ENSURE_SUCCESS(rv, 1);
1609 
1610  PRBool bAsyncQuery = PR_FALSE;
1611  pQuery->IsAyncQuery(&bAsyncQuery);
1612 
1613  PRInt32 result = 0;
1614  if(!bAsyncQuery) {
1615  pQuery->WaitForCompletion(&result);
1616  pQuery->GetLastError(&result);
1617  }
1618 
1619  return result;
1620 } //SubmitQueryPrivate
1621 
1622 //-----------------------------------------------------------------------------
1623 NS_IMETHODIMP
1624 CDatabaseEngine::DumpDatabase(const nsAString & aDatabaseGUID, nsIFile *aOutFile)
1625 {
1626  NS_ENSURE_ARG_POINTER(aOutFile);
1627 
1628  nsRefPtr<CDatabaseQuery> dummyQuery = new CDatabaseQuery();
1629  NS_ENSURE_TRUE(dummyQuery, NS_ERROR_OUT_OF_MEMORY);
1630 
1631  nsresult rv = dummyQuery->SetDatabaseGUID(aDatabaseGUID);
1632  NS_ENSURE_SUCCESS(rv, rv);
1633 
1634  rv = dummyQuery->Init();
1635  NS_ENSURE_SUCCESS(rv, rv);
1636 
1637  nsRefPtr<QueryProcessorQueue> pQueue = GetQueueByQuery(dummyQuery, PR_TRUE);
1638  NS_ENSURE_TRUE(pQueue, NS_ERROR_FAILURE);
1639 
1640  nsRefPtr<CDatabaseDumpProcessor> dumpProcessor =
1641  new CDatabaseDumpProcessor(this, pQueue, aOutFile);
1642 
1643  return dumpProcessor->Run();
1644 }
1645 
1646 //-----------------------------------------------------------------------------
1647 NS_IMETHODIMP CDatabaseEngine::DumpMemoryStatistics()
1648 {
1649  int current = -1;
1650  int highwater = -1;
1651 
1652  printf("DumpMemoryStatistics() format\tCurrent\tHighwater\n");
1653 
1654  sqlite3_status(SQLITE_STATUS_MEMORY_USED, &current, &highwater, 0);
1655  printf("SQLITE_STATUS_MEMORY_USED:\t%d\t%d\n", current, highwater);
1656  sqlite3_status(SQLITE_STATUS_PAGECACHE_USED, &current, &highwater, 0);
1657  printf("SQLITE_STATUS_PAGECACHE_USED:\t%d\t%d\n", current, highwater);
1658  sqlite3_status(SQLITE_STATUS_PAGECACHE_OVERFLOW, &current, &highwater, 0);
1659  printf("SQLITE_STATUS_PAGECACHE_OVERFLOW:\t%d\t%d\n", current, highwater);
1660  sqlite3_status(SQLITE_STATUS_SCRATCH_USED, &current, &highwater, 0);
1661  printf("SQLITE_STATUS_SCRATCH_USED:\t%d\t%d\n", current, highwater);
1662  sqlite3_status(SQLITE_STATUS_SCRATCH_OVERFLOW, &current, &highwater, 0);
1663  printf("SQLITE_STATUS_SCRATCH_OVERFLOW:\t%d\t%d\n", current, highwater);
1664  sqlite3_status(SQLITE_STATUS_MALLOC_SIZE, &current, &highwater, 0);
1665  printf("SQLITE_STATUS_MALLOC_SIZE\t%d\t%d\n", current, highwater);
1666  sqlite3_status(SQLITE_STATUS_PARSER_STACK, &current, &highwater, 0);
1667  printf("SQLITE_STATUS_PARSER_STACK\t%d\t%d\n", current, highwater);
1668  sqlite3_status(SQLITE_STATUS_PAGECACHE_SIZE, &current, &highwater, 0);
1669  printf("SQLITE_STATUS_PAGECACHE_SIZE\t%d\t%d\n", current, highwater);
1670  sqlite3_status(SQLITE_STATUS_SCRATCH_SIZE, &current, &highwater, 0);
1671  printf("SQLITE_STATUS_SCRATCH_SIZE\t%d\t%d\n", current, highwater);
1672 
1673  printf("DumpMemoryStatistics() finished. "
1674  "See dbengine/src/sqlite3.h#6168\n");
1675 
1676  return NS_OK;
1677 }
1678 
1679 //-----------------------------------------------------------------------------
1680 NS_IMETHODIMP CDatabaseEngine::GetCurrentMemoryUsage(PRInt32 flag, PRInt32 *_retval)
1681 {
1682  int disused = -1;
1683  sqlite3_status((int)flag, (int*)_retval, &disused, 0);
1684  return NS_OK;
1685 }
1686 
1687 //-----------------------------------------------------------------------------
1688 NS_IMETHODIMP CDatabaseEngine::GetHighWaterMemoryUsage(PRInt32 flag, PRInt32 *_retval)
1689 {
1690  int disused = -1;
1691  sqlite3_status((int)flag, &disused, (int*)_retval, 0);
1692  return NS_OK;
1693 }
1694 
1695 //-----------------------------------------------------------------------------
1696 NS_IMETHODIMP CDatabaseEngine::ReleaseMemory()
1697 {
1698  // Attempt to free a large amount of memory.
1699  // This will cause SQLite to free as much as it can.
1700  int SB_UNUSED_IN_RELEASE(memReleased) = sqlite3_release_memory(500000000);
1701  LOG("CDatabaseEngine::ReleaseMemory() managed to release %d bytes\n", memReleased);
1702 
1703  return NS_OK;
1704 }
1705 
1706 //-----------------------------------------------------------------------------
1707 already_AddRefed<QueryProcessorQueue> CDatabaseEngine::GetQueueByQuery(CDatabaseQuery *pQuery,
1708  PRBool bCreate /*= PR_FALSE*/)
1709 {
1710  NS_ENSURE_TRUE(pQuery, nsnull);
1711 
1712  nsAutoString strGUID;
1713  nsAutoMonitor mon(m_pThreadMonitor);
1714 
1715  nsRefPtr<QueryProcessorQueue> pQueue;
1716 
1717  nsresult rv = pQuery->GetDatabaseGUID(strGUID);
1718  NS_ENSURE_SUCCESS(rv, nsnull);
1719 
1720  if(!m_QueuePool.Get(strGUID, getter_AddRefs(pQueue))) {
1721  pQueue = CreateQueueFromQuery(pQuery);
1722  }
1723 
1724  NS_ENSURE_TRUE(pQueue, nsnull);
1725 
1726  QueryProcessorQueue *p = pQueue.get();
1727  NS_ADDREF(p);
1728 
1729  return p;
1730 }
1731 
1732 //-----------------------------------------------------------------------------
1733 already_AddRefed<QueryProcessorQueue> CDatabaseEngine::CreateQueueFromQuery(CDatabaseQuery *pQuery)
1734 {
1735  nsAutoString strGUID;
1736  nsAutoMonitor mon(m_pThreadMonitor);
1737 
1738  nsresult rv = pQuery->GetDatabaseGUID(strGUID);
1739  NS_ENSURE_SUCCESS(rv, nsnull);
1740 
1741  nsRefPtr<QueryProcessorQueue> pQueue(new QueryProcessorQueue());
1742  NS_ENSURE_TRUE(pQueue, nsnull);
1743 
1744  sqlite3 *pHandle = nsnull;
1745  rv = OpenDB(strGUID, pQuery, &pHandle);
1746  NS_ENSURE_SUCCESS(rv, nsnull);
1747 
1748  rv = pQueue->Init(this, strGUID, pHandle);
1749  NS_ENSURE_SUCCESS(rv, nsnull);
1750 
1751  PRBool success = m_QueuePool.Put(strGUID, pQueue);
1752  NS_ENSURE_TRUE(success, nsnull);
1753 
1754  QueryProcessorQueue *p = pQueue.get();
1755  NS_ADDREF(p);
1756 
1757  return p;
1758 }
1759 
1760 nsresult
1761 CDatabaseEngine::MarkDatabaseForPotentialDeletion(const nsAString &aDatabaseGUID,
1762  CDatabaseQuery *pQuery)
1763 {
1764  nsAutoMonitor mon(m_pThreadMonitor);
1765 
1766  m_PromptForDelete = PR_TRUE;
1767  m_DatabasesToDelete.insert(std::make_pair(
1768  nsString(aDatabaseGUID), nsRefPtr<CDatabaseQuery>(pQuery)));
1769 
1770  if(m_PromptForDeleteTimer) {
1771  nsresult rv = m_PromptForDeleteTimer->Init(this, nsITimer::TYPE_ONE_SHOT, 100);
1772  NS_ENSURE_SUCCESS(rv, rv);
1773  }
1774 
1775  return NS_OK;
1776 }
1777 
1778 nsresult
1779 CDatabaseEngine::PromptToDeleteDatabases()
1780 {
1781  nsresult rv;
1782 
1783  nsAutoMonitor mon(m_pThreadMonitor);
1784  if(!m_PromptForDelete || m_DatabasesToDelete.empty()) {
1785  return NS_OK;
1786  }
1787  mon.Exit();
1788 
1789  nsCOMPtr<sbIPrompter> promptService =
1790  do_GetService(SONGBIRD_PROMPTER_CONTRACTID, &rv);
1791  NS_ENSURE_SUCCESS(rv, rv);
1792 
1793  PRUint32 buttons = nsIPromptService::BUTTON_POS_0 * nsIPromptService::BUTTON_TITLE_IS_STRING +
1794  nsIPromptService::BUTTON_POS_1 * nsIPromptService::BUTTON_TITLE_IS_STRING +
1795  nsIPromptService::BUTTON_POS_1_DEFAULT;
1796  PRInt32 promptResult = 0;
1797 
1798  // get dialog strings
1800  nsString dialogTitle = bundle.Get("corruptdatabase.dialog.title");
1801  nsString dialogText = bundle.Get("corruptdatabase.dialog.text");
1802  nsString deleteText = bundle.Get("corruptdatabase.dialog.buttons.delete");
1803  nsString continueText = bundle.Get("corruptdatabase.dialog.buttons.cancel");
1804 
1805 
1806  // prompt.
1807  rv = promptService->ConfirmEx(nsnull,
1808  dialogTitle.BeginReading(),
1809  dialogText.BeginReading(),
1810  buttons,
1811  deleteText.BeginReading(), // button 0
1812  continueText.BeginReading(), // button 1
1813  nsnull, // button 2
1814  nsnull, // no checkbox
1815  nsnull, // no check value
1816  &promptResult);
1817  NS_ENSURE_SUCCESS(rv, rv);
1818 
1819  mon.Enter();
1820  m_PromptForDelete = PR_FALSE;
1821  mon.Exit();
1822 
1823  // "Delete" means delete & restart. "Continue" means let the app
1824  // start anyway.
1825  if (promptResult == 0) {
1826 
1827 #ifdef METRICS_ENABLED
1828  // metric: user chose to delete corrupt library
1829  nsCOMPtr<sbIMetrics> metrics =
1830  do_CreateInstance("@songbirdnest.com/Songbird/Metrics;1", &rv);
1831 
1832  if(NS_SUCCEEDED(rv)) {
1833  rv = metrics->MetricsInc(NS_LITERAL_STRING("app"), \
1834  NS_LITERAL_STRING("library.error.reset"),
1835  EmptyString());
1836  NS_ENSURE_SUCCESS(rv, rv);
1837  }
1838 #endif // METRICS_ENABLED
1839 
1840  mon.Enter();
1841  m_DeleteDatabases = PR_TRUE;
1842  mon.Exit();
1843 
1844  // now attempt to quit/restart.
1845  nsCOMPtr<nsIAppStartup> appStartup =
1846  (do_GetService(NS_APPSTARTUP_CONTRACTID, &rv));
1847  NS_ENSURE_SUCCESS(rv, rv);
1848 
1849  rv = appStartup->Quit(nsIAppStartup::eForceQuit | nsIAppStartup::eRestart);
1850  NS_ENSURE_SUCCESS(rv, rv);
1851  }
1852 
1853  return NS_OK;
1854 }
1855 
1856 nsresult
1857 CDatabaseEngine::DeleteMarkedDatabases()
1858 {
1859  nsresult rv;
1860  nsCOMPtr<nsIPrefService> prefService =
1861  do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
1862  NS_ENSURE_SUCCESS(rv, rv);
1863 
1864  nsAutoMonitor mon(m_pThreadMonitor);
1865 
1866  if(!m_DeleteDatabases)
1867  return NS_OK;
1868 
1869  deleteDatabaseMap_t::const_iterator cit = m_DatabasesToDelete.begin();
1870  deleteDatabaseMap_t::const_iterator citEnd = m_DatabasesToDelete.end();
1871 
1872  for(; cit != citEnd; ++cit) {
1873  nsString strFilename;
1874  GetDBStorePath(cit->first, cit->second, strFilename);
1875 
1876  nsCOMPtr<nsILocalFile> databaseFile;
1877  rv = NS_NewLocalFile(strFilename,
1878  PR_FALSE,
1879  getter_AddRefs(databaseFile));
1880  if(NS_FAILED(rv)) {
1881  NS_WARNING("Failed to get local file for database!");
1882  continue;
1883  }
1884 
1885  rv = databaseFile->Remove(PR_FALSE);
1886  NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to delete corrupted database file!");
1887  }
1888 
1889  // Go through prefs branch. If the databaseGUID pref matches
1890  // the database we are deleting, delete the entire branch.
1891 
1892  // If the db guid is the magic main library guid, we will also
1893  // reset the pref that asks the user to import media on startup.
1894 
1895  nsCAutoString prefBranchRoot(PREF_BRANCH_LIBRARY_LOADER);
1896  nsCOMPtr<nsIPrefBranch> loaderPrefBranch;
1897 
1898  rv = prefService->GetBranch(prefBranchRoot.get(), getter_AddRefs(loaderPrefBranch));
1899  NS_ENSURE_SUCCESS(rv, rv);
1900 
1901  PRUint32 libraryKeysCount;
1902  char** libraryKeys;
1903 
1904  rv = loaderPrefBranch->GetChildList("", &libraryKeysCount, &libraryKeys);
1905  NS_ENSURE_SUCCESS(rv, rv);
1906 
1907  sbAutoFreeXPCOMArray<char**> autoFree(libraryKeysCount, libraryKeys);
1908 
1909  for (PRUint32 index = 0; index < libraryKeysCount; index++) {
1910  nsCString pref(libraryKeys[index]);
1911 
1912  PRInt32 firstDotIndex = pref.FindChar('.');
1913  // bad pref string format, skip
1914  if(firstDotIndex == -1) {
1915  continue;
1916  }
1917 
1918  PRUint32 keyLength = firstDotIndex;
1919  if(keyLength == 0) {
1920  continue;
1921  }
1922 
1923  // Should be something like "songbird.library.loader.1.".
1924  nsCString branchString(PREF_BRANCH_LIBRARY_LOADER);
1925  branchString += Substring(pref, 0, keyLength + 1);
1926  if(!StringEndsWith(branchString, NS_LITERAL_CSTRING("."))) {
1927  continue;
1928  }
1929 
1930  nsCOMPtr<nsIPrefBranch> innerBranch;
1931  rv = prefService->GetBranch(branchString.get(), getter_AddRefs(innerBranch));
1932  NS_ENSURE_SUCCESS(rv, rv);
1933 
1934  PRInt32 prefType = nsIPrefBranch::PREF_INVALID;
1935  rv = innerBranch->GetPrefType(PREF_LOADER_DBGUID, &prefType);
1936  NS_ENSURE_SUCCESS(rv, rv);
1937 
1938  if(prefType != nsIPrefBranch::PREF_STRING) {
1939  continue;
1940  }
1941 
1942  nsCString loaderDbGuid;
1943  rv = innerBranch->GetCharPref(PREF_LOADER_DBGUID, getter_Copies(loaderDbGuid));
1944  NS_ENSURE_SUCCESS(rv, rv);
1945 
1946  rv = innerBranch->GetPrefType(PREF_LOADER_DBLOCATION, &prefType);
1947  NS_ENSURE_SUCCESS(rv, rv);
1948 
1949  if(prefType != nsIPrefBranch::PREF_STRING) {
1950  continue;
1951  }
1952 
1953  nsCString loaderDbLocation;
1954  rv = innerBranch->GetCharPref(PREF_LOADER_DBLOCATION, getter_Copies(loaderDbLocation));
1955  NS_ENSURE_SUCCESS(rv, rv);
1956 
1957  deleteDatabaseMap_t::const_iterator citD =
1958  m_DatabasesToDelete.find(NS_ConvertUTF8toUTF16(loaderDbGuid));
1959 
1960  if(citD != m_DatabasesToDelete.end()) {
1961  nsString strFilename;
1962  GetDBStorePath(citD->first, citD->second, strFilename);
1963 
1964  if(strFilename.EqualsLiteral(loaderDbLocation.get())) {
1965  rv = innerBranch->DeleteBranch("");
1966  NS_ENSURE_SUCCESS(rv, rv);
1967 
1968  rv = prefService->SavePrefFile(nsnull);
1969  NS_ENSURE_SUCCESS(rv, rv);
1970  }
1971 
1972  if(loaderDbGuid.EqualsLiteral(DBENGINE_GUID_MAIN_LIBRARY)) {
1973  nsCOMPtr<nsIPrefBranch> doomedBranch;
1974  rv = prefService->GetBranch(PREF_SCAN_COMPLETE, getter_AddRefs(doomedBranch));
1975  NS_ENSURE_SUCCESS(rv, rv);
1976 
1977  rv = doomedBranch->DeleteBranch("");
1978  NS_ENSURE_SUCCESS(rv, rv);
1979 
1980  rv = prefService->GetBranch(PREF_MAIN_LIBRARY, getter_AddRefs(doomedBranch));
1981  NS_ENSURE_SUCCESS(rv, rv);
1982 
1983  rv = doomedBranch->DeleteBranch("");
1984  NS_ENSURE_SUCCESS(rv, rv);
1985 
1986  rv = prefService->GetBranch(PREF_DOWNLOAD_LIST, getter_AddRefs(doomedBranch));
1987  NS_ENSURE_SUCCESS(rv, rv);
1988 
1989  rv = doomedBranch->DeleteBranch("");
1990  NS_ENSURE_SUCCESS(rv, rv);
1991 
1992  rv = prefService->SavePrefFile(nsnull);
1993  NS_ENSURE_SUCCESS(rv, rv);
1994  }
1995  else if(loaderDbGuid.EqualsLiteral(DBENGINE_GUID_WEB_LIBRARY)) {
1996  nsCOMPtr<nsIPrefBranch> doomedBranch;
1997  rv = prefService->GetBranch(PREF_WEB_LIBRARY, getter_AddRefs(doomedBranch));
1998  NS_ENSURE_SUCCESS(rv, rv);
1999 
2000  rv = doomedBranch->DeleteBranch("");
2001  NS_ENSURE_SUCCESS(rv, rv);
2002 
2003  rv = prefService->SavePrefFile(nsnull);
2004  NS_ENSURE_SUCCESS(rv, rv);
2005  }
2006  else if(loaderDbGuid.EqualsLiteral(DBENGINE_GUID_PLAYQUEUE_LIBRARY)) {
2007  nsCOMPtr<nsIPrefBranch> doomedBranch;
2008  rv = prefService->GetBranch(PREF_PLAYQUEUE_LIBRARY,
2009  getter_AddRefs(doomedBranch));
2010  NS_ENSURE_SUCCESS(rv, rv);
2011 
2012  rv = doomedBranch->DeleteBranch("");
2013  NS_ENSURE_SUCCESS(rv, rv);
2014 
2015  rv = prefService->SavePrefFile(nsnull);
2016  NS_ENSURE_SUCCESS(rv, rv);
2017  }
2018  }
2019  }
2020 
2021  m_DatabasesToDelete.clear();
2022  m_DeleteDatabases = PR_FALSE;
2023 
2024  return NS_OK;
2025 }
2026 
2027 template<class T>
2028 PLDHashOperator CDatabaseEngine::EnumerateIntoArrayStringKey(
2029  const nsAString& aKey,
2030  T* aQueue,
2031  void* aArray)
2032 {
2033  nsTArray<nsString> *stringArray =
2034  reinterpret_cast< nsTArray<nsString>* >(aArray);
2035 
2036  // No need to lock the queue here since its access to m_QueuePool is locked
2037  // when we are enumerating it.
2038 
2039  if(aQueue->m_AnalyzeCount > ANALYZE_QUERY_THRESHOLD) {
2040  aQueue->m_AnalyzeCount = 0;
2041  stringArray->AppendElement(aKey);
2042  }
2043 
2044  return PL_DHASH_NEXT;
2045 }
2046 
2047 nsresult
2048 CDatabaseEngine::RunAnalyze()
2049 {
2050  nsAutoMonitor mon(m_pThreadMonitor);
2051 
2052  nsTArray<nsString> dbGUIDs;
2053  m_QueuePool.EnumerateRead(EnumerateIntoArrayStringKey, &dbGUIDs);
2054 
2055  mon.Exit();
2056 
2057  nsresult rv = NS_ERROR_UNEXPECTED;
2058 
2059  PRUint32 current = 0;
2060  PRUint32 length = dbGUIDs.Length();
2061 
2062  for(; current < length; current++) {
2063  nsRefPtr<CDatabaseQuery> query;
2064  NS_NEWXPCOM(query, CDatabaseQuery);
2065  if(!query)
2066  continue;
2067  rv = query->SetDatabaseGUID(dbGUIDs[current]);
2068  if(NS_FAILED(rv))
2069  continue;
2070  rv = query->AddQuery(NS_LITERAL_STRING("ANALYZE"));
2071  if(NS_FAILED(rv))
2072  continue;
2073  rv = query->SetAsyncQuery(PR_TRUE);
2074  if(NS_FAILED(rv))
2075  continue;
2076  rv = SubmitQueryPrivate(query);
2077  NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to submit ANALYZE query.");
2078  }
2079 
2080  return NS_OK;
2081 }
2082 
2083 //-----------------------------------------------------------------------------
2084 /*static*/ void PR_CALLBACK CDatabaseEngine::QueryProcessor(CDatabaseEngine* pEngine,
2085  QueryProcessorQueue *pQueue)
2086 {
2087  if(!pEngine ||
2088  !pQueue ) {
2089  NS_WARNING("Called QueryProcessor without an engine or thread!!!!");
2090  return;
2091  }
2092 
2093  CDatabaseQuery *pQuery = nsnull;
2094 
2095  while(PR_TRUE)
2096  {
2097  pQuery = nsnull;
2098 
2099  { // Enter Monitor
2100  // Wrap any calls that access the pQueue.m_Queue because they cause a
2101  // context switch between threads and can mess up the link between the
2102  // RunQueue() call and the GetQueueSize() here. See bug 6514 for more details.
2103  nsAutoMonitor mon(pQueue->m_pQueueMonitor);
2104  pQueue->m_Running = PR_FALSE;
2105 
2106  PRUint32 queueSize = 0;
2107  nsresult rv = pQueue->GetQueueSize(queueSize);
2108  NS_ASSERTION(NS_SUCCEEDED(rv), "Couldn't get queue size.");
2109 
2110  // Nothing to execute
2111  if(!queueSize || pQueue->m_Shutdown) {
2112  return;
2113  }
2114 
2115  // We must have an item in the queue
2116  rv = pQueue->PopQueryFromQueue(&pQuery);
2117  NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "No query to pop from queue.");
2118 
2119  if(NS_FAILED(rv)) {
2120  return;
2121  }
2122 
2123  pQueue->m_Running = PR_TRUE;
2124  pQueue->m_AnalyzeCount++;
2125  } // Exit Monitor
2126 
2127  //The query is now in a running state.
2128  nsAutoMonitor mon(pQuery->m_pQueryRunningMonitor);
2129 
2130  sqlite3 *pDB = pQueue->m_pHandle;
2131 
2132  LOG("DBE: Process Start, thread 0x%x query 0x%x",
2133  PR_GetCurrentThread(), pQuery);
2134 
2135  PRUint32 nQueryCount = 0;
2136  PRBool bFirstRow = PR_TRUE;
2137 
2138  //Default return error.
2139  pQuery->SetLastError(SQLITE_ERROR);
2140  pQuery->GetQueryCount(&nQueryCount);
2141 
2142  // Create a result set object
2143  nsRefPtr<CDatabaseResult> databaseResult =
2144  new CDatabaseResult(pQuery->m_AsyncQuery);
2145 
2146  // Out of memory, attempt to restart the thread
2147  if(NS_UNLIKELY(!databaseResult)) {
2148  nsAutoMonitor mon(pQueue->m_pQueueMonitor);
2149  pQueue->m_Running = PR_FALSE;
2150  return;
2151  }
2152 
2153  for(PRUint32 currentQuery = 0; currentQuery < nQueryCount && !pQuery->m_IsAborting; ++currentQuery)
2154  {
2155  nsAutoPtr<bindParameterArray_t> pParameters;
2156 
2157  int retDB = 0; // sqlite return code.
2158 
2159  nsCOMPtr<sbIDatabasePreparedStatement> preparedStatement;
2160  nsresult rv = pQuery->PopQuery(getter_AddRefs(preparedStatement));
2161  if (NS_FAILED(rv)) {
2162  LOG("DBE: Failed to get a prepared statement from the Query object.");
2163  continue;
2164  }
2165  nsString strQuery;
2166  preparedStatement->GetQueryString(strQuery);
2167  // cast the prepared statement to its C implementation. this is a really lousy thing to do to an interface pointer.
2168  // since it mostly prevents ever being able to provide an alternative implementation.
2169  CDatabasePreparedStatement *actualPreparedStatement =
2170  static_cast<CDatabasePreparedStatement*>(preparedStatement.get());
2171  sqlite3_stmt *pStmt =
2172  actualPreparedStatement->GetStatement(pQueue->m_pHandle);
2173 
2174  if (!pStmt) {
2175  LOG("DBE: Failed to create a prepared statement from the Query object.");
2176  continue;
2177  }
2178 
2179  PR_Lock(pQuery->m_pLock);
2180  pQuery->m_CurrentQuery = currentQuery;
2181  PR_Unlock(pQuery->m_pLock);
2182 
2183  pParameters = pQuery->PopQueryParameters();
2184 
2185  nsAutoString dbName;
2186  pQuery->GetDatabaseGUID(dbName);
2187 
2188  BEGIN_PERFORMANCE_LOG(strQuery, dbName);
2189 
2190  LOG("DBE: '%s' on '%s'\n",
2191  NS_ConvertUTF16toUTF8(dbName).get(),
2192  NS_ConvertUTF16toUTF8(strQuery).get());
2193 
2194  // If we have parameters for this query, bind them
2195  PRUint32 i = 0; // we need the index as well to know where to bind our values.
2196  bindParameterArray_t::const_iterator const end = pParameters->end();
2197  for (bindParameterArray_t::const_iterator paramIter = pParameters->begin();
2198  paramIter != end;
2199  ++paramIter, ++i) {
2200  const CQueryParameter& p = *paramIter;
2201 
2202  switch(p.type) {
2203  case ISNULL:
2204  sqlite3_bind_null(pStmt, i + 1);
2205  LOG("DBE: Parameter %d is 'NULL'", i);
2206  break;
2207  case UTF8STRING:
2208  sqlite3_bind_text(pStmt, i + 1,
2209  p.utf8StringValue.get(),
2210  p.utf8StringValue.Length(),
2211  SQLITE_TRANSIENT);
2212  LOG("DBE: Parameter %d is '%s'", i, p.utf8StringValue.get());
2213  break;
2214  case STRING:
2215  {
2216  sqlite3_bind_text16(pStmt, i + 1,
2217  p.stringValue.get(),
2218  p.stringValue.Length() * sizeof(PRUnichar),
2219  SQLITE_TRANSIENT);
2220  LOG("DBE: Parameter %d is '%s'", i, NS_ConvertUTF16toUTF8(p.stringValue).get());
2221  break;
2222  }
2223  case DOUBLE:
2224  sqlite3_bind_double(pStmt, i + 1, p.doubleValue);
2225  LOG("DBE: Parameter %d is '%f'", i, p.doubleValue);
2226  break;
2227  case INTEGER32:
2228  sqlite3_bind_int(pStmt, i + 1, p.int32Value);
2229  LOG("DBE: Parameter %d is '%d'", i, p.int32Value);
2230  break;
2231  case INTEGER64:
2232  sqlite3_bind_int64(pStmt, i + 1, p.int64Value);
2233  LOG("DBE: Parameter %d is '%ld'", i, p.int64Value);
2234  break;
2235  }
2236  }
2237 
2238  PRInt32 totalRows = 0;
2239 
2240  PRUint64 rollingSum = 0;
2241  PRUint64 rollingLimit = 0;
2242  PRUint32 rollingLimitColumnIndex = 0;
2243  PRUint32 rollingRowCount = 0;
2244  pQuery->GetRollingLimit(&rollingLimit);
2245  pQuery->GetRollingLimitColumnIndex(&rollingLimitColumnIndex);
2246 
2247  PRBool finishEarly = PR_FALSE;
2248  do
2249  {
2250  retDB = sqlite3_step(pStmt);
2251 
2252  switch(retDB)
2253  {
2254  case SQLITE_ROW:
2255  {
2256  int nCount = sqlite3_column_count(pStmt);
2257  if(bFirstRow)
2258  {
2259  bFirstRow = PR_FALSE;
2260 
2261  std::vector<nsString> vColumnNames;
2262  vColumnNames.reserve(nCount);
2263 
2264  int j = 0;
2265  for(; j < nCount; j++) {
2266  const char *p = (const char *)sqlite3_column_name(pStmt, j);
2267  if (p) {
2268  vColumnNames.push_back(NS_ConvertUTF8toUTF16(p));
2269  }
2270  else {
2271  nsAutoString strColumnName;
2272  strColumnName.SetIsVoid(PR_TRUE);
2273  vColumnNames.push_back(strColumnName);
2274  }
2275  }
2276  databaseResult->SetColumnNames(vColumnNames);
2277  }
2278 
2279  std::vector<nsString> vCellValues;
2280  vCellValues.reserve(nCount);
2281 
2282  TRACE("DBE: Result row %d:", totalRows);
2283 
2284  int k = 0;
2285  // If this is a rolling limit query, increment the rolling
2286  // sum by the value of the specified column index.
2287  if (rollingLimit > 0) {
2288  rollingSum += sqlite3_column_int64(pStmt, rollingLimitColumnIndex);
2289  rollingRowCount++;
2290  }
2291 
2292  // Add the row to the result only if this is not a rolling
2293  // limit query, or if this is a rolling limit query and the
2294  // rolling sum has met or exceeded the limit
2295  if (rollingLimit == 0 || rollingSum >= rollingLimit) {
2296  for(; k < nCount; k++)
2297  {
2298  const char *p = (const char *)sqlite3_column_text(pStmt, k);
2299  nsString strCellValue;
2300  if (p) {
2301  strCellValue = NS_ConvertUTF8toUTF16(p);
2302  }
2303  else {
2304  strCellValue.SetIsVoid(PR_TRUE);
2305  }
2306 
2307  vCellValues.push_back(strCellValue);
2308  TRACE("Column %d: '%s' ", k,
2309  NS_ConvertUTF16toUTF8(strCellValue).get());
2310  }
2311  totalRows++;
2312 
2313  databaseResult->AddRow(vCellValues);
2314 
2315  // If this is a rolling limit query, we're done
2316  if (rollingLimit > 0) {
2317  pQuery->SetRollingLimitResult(rollingRowCount);
2318  pQuery->SetLastError(SQLITE_OK);
2319  TRACE("Rolling limit query complete, %d rows", totalRows);
2320  finishEarly = PR_TRUE;
2321  }
2322  }
2323  }
2324  break;
2325 
2326  case SQLITE_DONE:
2327  {
2328  pQuery->SetLastError(SQLITE_OK);
2329  TRACE("Query complete, %d rows", totalRows);
2330  }
2331  break;
2332 
2333  case SQLITE_BUSY:
2334  {
2335  sqlite3_reset(pStmt);
2336  sqlite3_sleep(50);
2337 
2338  retDB = SQLITE_ROW;
2339  }
2340  break;
2341 
2342  case SQLITE_CORRUPT:
2343  {
2344  pEngine->ReportError(pDB, pStmt);
2345 
2346  // Even if the following fails, this method will exit cleanly
2347  // and report the error to the console
2348  rv = pEngine->MarkDatabaseForPotentialDeletion(dbName, pQuery);
2349  NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to mark database for deletion!");
2350  }
2351  break;
2352 
2353  default:
2354  {
2355  // Log all SQL errors to the error console.
2356  pEngine->ReportError(pDB, pStmt);
2357  pQuery->SetLastError(retDB);
2358  }
2359  }
2360  }
2361  while(retDB == SQLITE_ROW &&
2362  !pQuery->m_IsAborting &&
2363  !finishEarly);
2364 
2365  pQuery->SetResultObject(databaseResult);
2366 
2367  // Quoth the sqlite wiki:
2368  // Sometimes people think they have finished with a SELECT statement because sqlite3_step()
2369  // has returned SQLITE_DONE. But the SELECT is not really complete until sqlite3_reset()
2370  // or sqlite3_finalize() have been called.
2371  sqlite3_reset(pStmt);
2372  }
2373 
2374  //Whatever happened, the query is done running now.
2375  {
2376  sbSimpleAutoLock lock(pQuery->m_pLock);
2377  pQuery->m_QueryHasCompleted = PR_TRUE;
2378  pQuery->m_IsExecuting = PR_FALSE;
2379  pQuery->m_IsAborting = PR_FALSE;
2380  }
2381 
2382  LOG("DBE: Notified query monitor.");
2383 
2384  //Fire off the callback if there is one.
2385  pEngine->DoSimpleCallback(pQuery);
2386  LOG("DBE: Simple query listeners have been processed.");
2387 
2388  LOG("DBE: Process End");
2389 
2390  mon.NotifyAll();
2391  mon.Exit();
2392 
2393  NS_RELEASE(pQuery);
2394  } // while
2395 
2396  return;
2397 } //QueryProcessor
2398 
2399 already_AddRefed<nsIEventTarget>
2401 {
2402  NS_ENSURE_TRUE(m_pThreadPool, nsnull);
2403 
2404  nsresult rv = NS_ERROR_UNEXPECTED;
2405  nsCOMPtr<nsIEventTarget> eventTarget = do_QueryInterface(m_pThreadPool, &rv);
2406  NS_ENSURE_SUCCESS(rv, nsnull);
2407 
2408  return eventTarget.forget();
2409 }
2410 
2411 //-----------------------------------------------------------------------------
2412 void CDatabaseEngine::ReportError(sqlite3* db, sqlite3_stmt* stmt) {
2413  const char *sql = sqlite3_sql(stmt);
2414  const char *errMsg = sqlite3_errmsg(db);
2415 
2416  nsString log;
2417  log.AppendLiteral("SQLite execution error: \n");
2418  log.Append(NS_ConvertUTF8toUTF16(sql));
2419  log.AppendLiteral("\nresulted in the error\n");
2420  log.Append(NS_ConvertUTF8toUTF16(errMsg));
2421  log.AppendLiteral("\n");
2422 
2423  nsresult rv;
2424  nsCOMPtr<nsIConsoleService> consoleService = do_GetService("@mozilla.org/consoleservice;1", &rv);
2425 
2426  nsCOMPtr<nsIScriptError> scriptError = do_CreateInstance(NS_SCRIPTERROR_CONTRACTID);
2427  if (scriptError) {
2428  nsresult rv = scriptError->Init(log.get(),
2429  EmptyString().get(),
2430  EmptyString().get(),
2431  0, // No line number
2432  0, // No column number
2433  0, // An error message.
2434  "DBEngine:StatementExecution");
2435  if (NS_SUCCEEDED(rv)) {
2436  rv = consoleService->LogMessage(scriptError);
2437  }
2438  }
2439 }
2440 //-----------------------------------------------------------------------------
2441 PR_STATIC_CALLBACK(PLDHashOperator)
2443 {
2444  nsCOMArray<sbIDatabaseSimpleQueryCallback> *array = static_cast<nsCOMArray<sbIDatabaseSimpleQueryCallback> *>(closure);
2445  array->AppendObject(data);
2446  return PL_DHASH_NEXT;
2447 }
2448 
2449 //-----------------------------------------------------------------------------
2450 void CDatabaseEngine::DoSimpleCallback(CDatabaseQuery *pQuery)
2451 {
2452  PRUint32 callbackCount = 0;
2453  nsCOMArray<sbIDatabaseSimpleQueryCallback> callbackSnapshot;
2454 
2455  nsCOMPtr<sbIDatabaseResult> pDBResult;
2456  nsAutoString strGUID;
2457 
2458  pQuery->GetResultObject(getter_AddRefs(pDBResult));
2459  pQuery->GetDatabaseGUID(strGUID);
2460 
2461  pQuery->m_CallbackList.EnumerateRead(EnumSimpleCallback, &callbackSnapshot);
2462 
2463  callbackCount = callbackSnapshot.Count();
2464  if(!callbackCount)
2465  return;
2466 
2467  nsString strQuery = NS_LITERAL_STRING("UNIMPLEMENTED");
2468 
2469  for(PRUint32 i = 0; i < callbackCount; i++)
2470  {
2471  nsCOMPtr<sbIDatabaseSimpleQueryCallback> callback = callbackSnapshot.ObjectAt(i);
2472  if(callback)
2473  {
2474  try
2475  {
2476  callback->OnQueryEnd(pDBResult, strGUID, strQuery);
2477  }
2478  catch(...) { }
2479  }
2480  }
2481 
2482  return;
2483 } //DoSimpleCallback
2484 
2485 //-----------------------------------------------------------------------------
2486 nsresult CDatabaseEngine::CreateDBStorePath()
2487 {
2488  nsresult rv = NS_ERROR_FAILURE;
2489  sbSimpleAutoLock lock(m_pDBStorePathLock);
2490 
2491  nsCOMPtr<nsIFile> f;
2492 
2493  rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(f));
2494  if(NS_FAILED(rv)) return rv;
2495 
2496  rv = f->Append(NS_LITERAL_STRING("db"));
2497  if(NS_FAILED(rv)) return rv;
2498 
2499  PRBool dirExists = PR_FALSE;
2500  rv = f->Exists(&dirExists);
2501  if(NS_FAILED(rv)) return rv;
2502 
2503  if(!dirExists)
2504  {
2505  rv = f->Create(nsIFile::DIRECTORY_TYPE, 0700);
2506  if(NS_FAILED(rv)) return rv;
2507  }
2508 
2509  rv = f->GetPath(m_DBStorePath);
2510  if(NS_FAILED(rv)) return rv;
2511 
2512  return NS_OK;
2513 }
2514 
2515 //-----------------------------------------------------------------------------
2516 nsresult CDatabaseEngine::GetDBStorePath(const nsAString &dbGUID, CDatabaseQuery *pQuery, nsAString &strPath)
2517 {
2518  nsresult rv = NS_ERROR_FAILURE;
2519  nsCOMPtr<nsILocalFile> f;
2520  nsCString spec;
2521  nsAutoString strDBFile(dbGUID);
2522 
2523  rv = pQuery->GetDatabaseLocation(spec);
2524  NS_ENSURE_SUCCESS(rv, rv);
2525 
2526  if(!spec.IsEmpty())
2527  {
2528  nsCOMPtr<nsIFile> file;
2529  rv = NS_GetFileFromURLSpec(spec, getter_AddRefs(file));
2530  NS_ENSURE_SUCCESS(rv, rv);
2531 
2532  nsAutoString path;
2533  rv = file->GetPath(path);
2534  NS_ENSURE_SUCCESS(rv, rv);
2535 
2536  rv = NS_NewLocalFile(path, PR_FALSE, getter_AddRefs(f));
2537  NS_ENSURE_SUCCESS(rv, rv);
2538  }
2539  else
2540  {
2541  PR_Lock(m_pDBStorePathLock);
2542  rv = NS_NewLocalFile(m_DBStorePath, PR_FALSE, getter_AddRefs(f));
2543  PR_Unlock(m_pDBStorePathLock);
2544  NS_ENSURE_SUCCESS(rv, rv);
2545  }
2546 
2547  strDBFile.AppendLiteral(".db");
2548  rv = f->Append(strDBFile);
2549  if(NS_FAILED(rv)) return rv;
2550  rv = f->GetPath(strPath);
2551  if(NS_FAILED(rv)) return rv;
2552 
2553  return NS_OK;
2554 } //GetDBStorePath
2555 
2556 NS_IMETHODIMP CDatabaseEngine::GetLocaleCollationEnabled(PRBool *aEnabled)
2557 {
2558  NS_ENSURE_ARG_POINTER(aEnabled);
2559  *aEnabled = (PRBool)gLocaleCollationEnabled;
2560  return NS_OK;
2561 }
2562 
2563 NS_IMETHODIMP CDatabaseEngine::SetLocaleCollationEnabled(PRBool aEnabled)
2564 {
2565  PR_AtomicSet(&gLocaleCollationEnabled, (PRInt32)aEnabled);
2566  return NS_OK;
2567 }
2568 
2569 PRInt32 CDatabaseEngine::CollateForCurrentLocale(collationBuffers *aCollationBuffers,
2570  const NATIVE_CHAR_TYPE *aStr1,
2571  const NATIVE_CHAR_TYPE *aStr2) {
2572  // shortcut when both strings are empty
2573  if (!aStr1 && !aStr2)
2574  return 0;
2575 
2576  PRInt32 retval;
2577 
2578  // apply the proper collation algorithm, depending on the user's locale.
2579 
2580  // note that it is impossible to use the proper sort for *all* languages at
2581  // the same time, because what is proper depends on the original language from
2582  // which the string came. for instance, Hungarian artists should have their
2583  // accented vowels sorted the same as non-accented ones, but a French artist
2584  // should have the last accent determine that order. because we cannot
2585  // possibly guess the origin locale for the string, the only thing we can do
2586  // is use the user's current locale on all strings.
2587 
2588  // that being said, many language-specific letters (such as the German Eszett)
2589  // have one one way of being properly sorted (in this instance, it must sort
2590  // with the same weight as 'ss', and are collated that way no matter what
2591  // locale is being used.
2592 
2593 #ifdef XP_MACOSX
2594 
2595  // on osx, use carbon collate functions because the CRT functions do not
2596  // have access to carbon's collation setting.
2597 
2598  PRInt32 nA = native_wcslen(aStr1);
2599  PRInt32 nB = native_wcslen(aStr2);
2600 
2601  // we should always have a collator, but just in case we don't,
2602  // use the default collation algorithm.
2603  if (!m_Collator) {
2604  ::UCCompareTextDefault(kUCCollateStandardOptions,
2605  aStr1,
2606  nA,
2607  aStr2,
2608  nB,
2609  NULL,
2610  (SInt32 *)&retval);
2611  } else {
2612  ::UCCompareText(m_Collator,
2613  aStr1,
2614  nA,
2615  aStr2,
2616  nB,
2617  NULL,
2618  (SInt32 *)&retval);
2619  }
2620 
2621 #else
2622 
2623  retval = wcscoll((const wchar_t *)aStr1, (const wchar_t *)aStr2);
2624 
2625 #endif // ifdef XP_MACOSX
2626 
2627  return retval;
2628 }
2629 
2630 // This defines where to sort strings with leading numbers relative to strings
2631 // without leading numbers. (-1 = top, 1 = bottom)
2632 #define LEADING_NUMBERS_SORTPOSITION -1
2633 
2634 PRInt32 CDatabaseEngine::CollateWithLeadingNumbers(collationBuffers *aCollationBuffers,
2635  const NATIVE_CHAR_TYPE *aStr1,
2636  PRInt32 *number1Length,
2637  const NATIVE_CHAR_TYPE *aStr2,
2638  PRInt32 *number2Length) {
2639  PRBool hasLeadingNumberA = PR_FALSE;
2640  PRBool hasLeadingNumberB = PR_FALSE;
2641 
2642  PRFloat64 leadingNumberA;
2643  PRFloat64 leadingNumberB;
2644 
2645  SB_ExtractLeadingNumber(aStr1,
2646  &hasLeadingNumberA,
2647  &leadingNumberA,
2648  number1Length);
2649  SB_ExtractLeadingNumber(aStr2,
2650  &hasLeadingNumberB,
2651  &leadingNumberB,
2652  number2Length);
2653 
2654  // we want strings with leading numbers to always sort the same way relative
2655  // to those without leading numbers
2656  if (hasLeadingNumberA && !hasLeadingNumberB) {
2658  } else if (!hasLeadingNumberA && hasLeadingNumberB) {
2660  } else if (hasLeadingNumberA && hasLeadingNumberB) {
2661  if (leadingNumberA > leadingNumberB)
2662  return 1;
2663  else if (leadingNumberA < leadingNumberB)
2664  return -1;
2665  }
2666 
2667  // either both numbers are equal, or neither string had a leading number,
2668  // use the (possibly) stripped down strings to collate.
2669 
2670  aStr1 += *number1Length;
2671  aStr2 += *number2Length;
2672 
2673  return CollateForCurrentLocale(aCollationBuffers, aStr1, aStr2);
2674 }
2675 
2676 PRInt32 CDatabaseEngine::Collate(collationBuffers *aCollationBuffers,
2677  const NATIVE_CHAR_TYPE *aStr1,
2678  const NATIVE_CHAR_TYPE *aStr2) {
2679 
2680  const NATIVE_CHAR_TYPE *remainderA = aStr1;
2681  const NATIVE_CHAR_TYPE *remainderB = aStr2;
2682 
2683  while (1) {
2684 
2685  // if either string is empty, break the loop
2686  if (!*remainderA ||
2687  !*remainderB)
2688  break;
2689 
2690  // find the next number in each string, if any
2691  PRInt32 nextNumberPosA = SB_FindNextNumber(remainderA);
2692  PRInt32 nextNumberPosB = SB_FindNextNumber(remainderB);
2693 
2694  if (nextNumberPosA == -1 ||
2695  nextNumberPosB == -1) {
2696  // if either string does not have anymore number, break the loop
2697  // so we do a final collate on the remainder of the strings
2698  break;
2699  } else {
2700  // both strings still have more number(s)
2701 
2702  // if one of the strings begins with a number and the other does not,
2703  // enforce leading number sort position
2704  if (nextNumberPosA == 0 && nextNumberPosB != 0) {
2706  } else if (nextNumberPosA != 0 && nextNumberPosB == 0) {
2708  }
2709 
2710  // extract the substrings that precede the numbers, then collate these.
2711  // if they are not equivalent, then return the result of that collation.
2712 
2713  aCollationBuffers->substringExtractionBuffer1
2714  .copy_native(remainderA, nextNumberPosA);
2715  aCollationBuffers->substringExtractionBuffer2
2716  .copy_native(remainderB, nextNumberPosB);
2717 
2718  PRInt32 substringCollate =
2719  CollateForCurrentLocale(aCollationBuffers,
2720  aCollationBuffers->substringExtractionBuffer1.buffer(),
2721  aCollationBuffers->substringExtractionBuffer2.buffer());
2722 
2723  if (substringCollate != 0) {
2724  return substringCollate;
2725  }
2726 
2727  // the leading substrings are equivalent, so parse the numbers and
2728  // see if they are equivalent too
2729 
2730  // remove the leading substrings from the remainder strings, so that
2731  // they now begin with the next numbers
2732  remainderA += nextNumberPosA;
2733  remainderB += nextNumberPosB;
2734 
2735  PRInt32 numberALength;
2736  PRInt32 numberBLength;
2737 
2738  PRInt32 leadingNumbersCollate =
2739  CollateWithLeadingNumbers(aCollationBuffers,
2740  remainderA,
2741  &numberALength,
2742  remainderB,
2743  &numberBLength);
2744 
2745  // if the numbers were not equivalent, return the result of the number
2746  // collation
2747  if (leadingNumbersCollate != 0) {
2748  return leadingNumbersCollate;
2749  }
2750 
2751  // discard the numbers
2752  remainderA += numberALength;
2753  remainderB += numberBLength;;
2754 
2755  // if we failed to actually parse a number on both strings (ie, there was
2756  // a number char that was detected but it did not parse to a valid number)
2757  // then we need to advance both strings by one character, otherwise we can
2758  // run into an infinite loop trying to parse those two numbers if the two
2759  // remainders are also equal. If the remainders are not equal, then
2760  // leadingNumbersCollate was != 0 and we've exited already. If only one of
2761  // the two strings failed to parse to a number, but the remainders are
2762  // still equal, we've advanced on one of the strings, and we're not going
2763  // to infinitely loop.
2764  if (numberALength == 0 &&
2765  numberBLength == 0) {
2766  remainderA++;
2767  remainderB++;
2768  }
2769 
2770  // and loop ...
2771  }
2772 
2773  }
2774 
2775  // if both strings are now empty, the original strings were equivalent
2776  if (!*remainderA &&
2777  !*remainderB) {
2778  return 0;
2779  }
2780 
2781  // collate what we have left. although at most one string may have a leading
2782  // number, we want to go through CollateWithLeadingNumbers_UTF8 anyway in
2783  // order to enforce the position of strings with leading numbers relative to
2784  // strings without leading numbers
2785 
2786  PRInt32 numberALength;
2787  PRInt32 numberBLength;
2788 
2789  return CollateWithLeadingNumbers(aCollationBuffers,
2790  remainderA,
2791  &numberALength,
2792  remainderB,
2793  &numberBLength);
2794 }
2795 
2796 nsresult
2797 CDatabaseEngine::GetCurrentCollationLocale(nsCString &aCollationLocale) {
2798 
2799 #ifdef XP_MACOSX
2800 
2801  CFStringRef collationIdentifier = NULL;
2802 
2803  // try to read the collation language from the prefs (note, CFLocaleGetValue
2804  // with kCFLocaleCollationIdentifier would be ideal, but although that call
2805  // works for other constants, it does not for the collation identifier, at
2806  // least on 10.4, which we want to support).
2807 
2808  CFPropertyListRef pl =
2809  CFPreferencesCopyAppValue(CFSTR("AppleCollationOrder"),
2810  kCFPreferencesCurrentApplication);
2811 
2812  // use the value we've read if it existed and is what we're expecting
2813  if (pl != NULL &&
2814  CFGetTypeID(pl) == CFStringGetTypeID()) {
2815  collationIdentifier = (CFStringRef)pl;
2816  } else {
2817  // otherwise, retrieve the list of prefered languages, the first item
2818  // is the user's language, and defines the default collation.
2819  CFPropertyListRef al =
2820  CFPreferencesCopyAppValue(CFSTR("AppleLanguages"),
2821  kCFPreferencesCurrentApplication);
2822  // if the array exists and is what we expect, read the first item
2823  if (al != NULL &&
2824  CFGetTypeID(al) == CFArrayGetTypeID()) {
2825  CFArrayRef ar = (CFArrayRef)al;
2826  if (CFArrayGetCount(ar) > 0) {
2827  CFTypeRef lang = CFArrayGetValueAtIndex(ar, 0);
2828  if (lang != NULL &&
2829  CFGetTypeID(lang) == CFStringGetTypeID()) {
2830  collationIdentifier = (CFStringRef)lang;
2831  }
2832  }
2833  }
2834  }
2835 
2836  // if everything goes wrong and we're completely unable to read either values,
2837  // that's kind of a bummer, print a debug message and assume that we are using
2838  // the standard "C" collation (strcmp). a reason this could happen might be
2839  // that we are running on an unsupported version of osx that does not have
2840  // these strings (either real old, or a future version which stores them
2841  // elsewhere or with different types). another reason this might happen is
2842  // if the computer is about to explode.
2843 
2844  if (collationIdentifier) {
2845  char buf[64]="";
2846  CFStringGetCString(collationIdentifier,
2847  buf,
2848  sizeof(buf),
2849  kCFStringEncodingASCII);
2850  buf[sizeof(buf)-1] = 0;
2851 
2852  aCollationLocale = buf;
2853 
2854  } else {
2855  LOG("Could not retrieve the collation locale identifier");
2856  }
2857 #else
2858 
2859  // read the current collation id, this is usually the standard C collate
2860  nsCString curCollate(setlocale(LC_COLLATE, NULL));
2861 
2862  // read the user defined collation id, this usually corresponds to the
2863  // user's OS language selection
2864  aCollationLocale = setlocale(LC_COLLATE, "");
2865 
2866  // restore the default collation
2867  setlocale(LC_COLLATE, curCollate.get());
2868 
2869 #endif
2870 
2871  return NS_OK;
2872 }
2873 
2874 NS_IMETHODIMP CDatabaseEngine::GetLocaleCollationID(nsAString &aID) {
2875  aID = NS_ConvertASCIItoUTF16(mCollationLocale);
2876  return NS_OK;
2877 }
2878 
2879 #ifdef USE_PERF_LOGGING
2880 
2881 sbDatabaseEnginePerformanceLogger::sbDatabaseEnginePerformanceLogger(const nsAString& aQuery,
2882  const nsAString& aGuid) :
2883  mQuery(aQuery),
2884  mGuid(aGuid)
2885 {
2886  mStart = PR_Now();
2887 }
2888 
2889 sbDatabaseEnginePerformanceLogger::~sbDatabaseEnginePerformanceLogger()
2890 {
2891  PRUint32 total = PR_Now() - mStart;
2892 
2893  nsString consoleMessageStr;
2894  consoleMessageStr.AppendLiteral("sbDatabaseEnginePerformanceLogger\n\t");
2895  consoleMessageStr.AppendLiteral("DB GUID: ");
2896  consoleMessageStr.Append(mGuid);
2897  consoleMessageStr.AppendLiteral(" -- Execution Time: ");
2898  consoleMessageStr.AppendInt(total / PR_USEC_PER_MSEC);
2899  consoleMessageStr.AppendLiteral("ms\nQuery:\n\t");
2900  consoleMessageStr.Append(mQuery);
2901 
2902  nsCOMPtr<nsIConsoleService> consoleService =
2903  do_GetService("@mozilla.org/consoleservice;1");
2904 
2905  if(consoleService) {
2906  consoleService->LogStringMessage(consoleMessageStr.get());
2907  }
2908 }
2909 
2910 #endif
function start(ch)
nsRefPtr< QueryProcessorQueue > mQueryProcessorQueue
var total
NATIVE_CHAR_TYPE * buffer()
static int library_collate_func_utf8(void *pCtx, int nA, const void *zA, int nB, const void *zB)
#define PREF_WEB_LIBRARY
const NS_APP_USER_PROFILE_50_DIR
static int tree_collate_func_utf16be(void *pCtx, int nA, const void *zA, int nB, const void *zB)
static int tree_collate_func(void *pCtx, int nA, const void *zA, int nB, const void *zB, int eTextRep)
#define SB_PRLOG_SETUP(x)
Definition: sbDebugUtils.h:115
return NS_OK
#define PREF_DB_PAGE_SIZE
#define DBENGINE_GUID_MAIN_LIBRARY
EnumSimpleCallback(nsISupports *key, sbIDatabaseSimpleQueryCallback *data, void *closure)
static nsCOMPtr< nsIObserverService > observerService
Definition: UnityProxy.cpp:6
ParameterType type
#define MAX_BUSY_RETRY_CLOSE_DB
#define DBENGINE_GUID_WEB_LIBRARY
#define LOG(args)
PRInt32 SB_FindNextNumber(const CHARTYPE *aStr)
_dialogDatepicker pos
PR_STATIC_CALLBACK(PRBool) FindElementCallback(void *aElement
inArray array
#define DEFAULT_PREALLOCSCRATCH_SIZE
void swap_utf16_bytes(const void *aStr, int len)
var pref
Definition: openLocation.js:44
_selectMonthYear select
const NS_PREFSERVICE_CONTRACTID
#define PREF_LOADER_DBLOCATION
CDatabaseDumpProcessor(CDatabaseEngine *aCallback, QueryProcessorQueue *aQueryProcessorQueue, nsIFile *aOutputFile)
already_AddRefed< QueryProcessorQueue > GetQueueByQuery(CDatabaseQuery *pQuery, PRBool bCreate=PR_FALSE)
function log(s)
#define PREF_DOWNLOAD_LIST
nsString Get(const nsAString &aKey, const nsAString &aDefault=SBVoidString())
fastString encodingConversionBuffer2
#define DEFAULT_PAGE_SIZE
fastString substringExtractionBuffer1
static int library_collate_func_utf16le(void *pCtx, int nA, const void *zA, int nB, const void *zB)
#define PREF_PLAYQUEUE_LIBRARY
static char * appendText(char *zIn, char const *zAppend, char quote)
#define IDLE_SERVICE_TIMEOUT
nsresult OutputBuffer(const char *aBuffer)
nsCOMPtr< nsIFileOutputStream > mOutputStream
PRInt32 RunSchemaDumpQuery(const nsACString &aQuery)
#define SONGBIRD_PROMPTER_CONTRACTID
#define PREF_DB_PREALLOCCACHE_SIZE
static int tree_collate_func_utf16le(void *pCtx, int nA, const void *zA, int nB, const void *zB)
PRBool m_QueryHasCompleted
function width(ele) rect(ele).width
PRInt32 SubmitQueryPrivate(CDatabaseQuery *pQuery)
#define DBENGINE_GUID_PLAYQUEUE_LIBRARY
#define PREF_MAIN_LIBRARY
EnumQueuesOperate(nsStringHashKey::KeyType aKey, QueryProcessorQueue *aQueue, void *aClosure)
[USER CODE SHOULD NOT REFERENCE THIS CLASS]
#define BEGIN_PERFORMANCE_LOG(_strQuery, _dbName)
static CDatabaseEngine * GetSingleton()
PRUint32 m_CurrentQuery
function d(s)
fastString substringExtractionBuffer2
#define DEFAULT_PREALLOCCACHE_SIZE
nsresult GetDatabaseLocation(nsACString &aURISpec)
var t
PRMonitor * m_pQueueMonitor
var bundle
virtual ~CDatabaseEngine()
NS_IMPL_THREADSAFE_ISUPPORTS1(sbDeviceCapsCompatibility, sbIDeviceCapsCompatibility) sbDeviceCapsCompatibility
void SB_ExtractLeadingNumber(const CHARTYPE *str, PRBool *hasLeadingNumber, PRFloat64 *leadingNumber, PRInt32 *numberLength)
#define LEADING_NUMBERS_SORTPOSITION
PRMonitor * m_pQueryRunningMonitor
void SetResultObject(CDatabaseResult *aResultObject)
function retries(aHandlers)
nsCString utf8StringValue
static PRInt32 gLocaleCollationEnabled
nsCOMPtr< nsIFile > mOutputFile
nsresult PopQuery(sbIDatabasePreparedStatement **_retval)
function TRACE(s)
function num(elem, prop)
static int DumpCallback(void *pArg, int inArg, char **azArg, const char **azCol)
nsInterfaceHashtableMT< nsISupportsHashKey, sbIDatabaseSimpleQueryCallback > m_CallbackList
already_AddRefed< nsIEventTarget > GetEventTarget()
#define PREF_SCAN_COMPLETE
#define NS_FINAL_UI_STARTUP_CATEGORY
#define DEFAULT_CACHE_SIZE
PRBool m_IsExecuting
void * aClosure
Definition: sbArray.cpp:52
static int library_collate_func(collationBuffers *cBuffers, const NATIVE_CHAR_TYPE *zA, const NATIVE_CHAR_TYPE *zB)
grep callback
A callback object used to inform client code that a query has completed.
#define PREF_LOADER_DBGUID
PRLock * m_pLock
NS_IMPL_THREADSAFE_ISUPPORTS2(sbDeviceCapabilities, sbIDeviceCapabilities, nsIClassInfo) NS_IMPL_CI_INTERFACE_GETTER2(sbDeviceCapabilities
#define ANALYZE_QUERY_THRESHOLD
nsString stringValue
return ret
Songbird String Bundle Definitions.
int native_wcscmp(const NATIVE_CHAR_TYPE *s1, const NATIVE_CHAR_TYPE *s2)
#define PREF_DB_SOFT_LIMIT
#define PREF_BRANCH_LIBRARY_LOADER
already_AddRefed< QueryProcessorQueue > CreateQueueFromQuery(CDatabaseQuery *pQuery)
void grow_native(PRInt32 aLength)
int native_wcslen(const NATIVE_CHAR_TYPE *s)
PRInt32 SubmitQuery(in CDatabaseQueryPtr aDBQuery)
Submit a query to the database engine for processing.
void copy_native(const NATIVE_CHAR_TYPE *aFrom, PRInt32 aSize)
static int tree_collate_func_utf8(void *pCtx, int nA, const void *zA, int nB, const void *zB)
NS_IMETHOD Shutdown()
PRInt32 RunTableDumpQuery(const nsACString &aSelect)
let promptService
Songbird Database Prepared Query.
#define PREF_DB_PREALLOCSCRATCH_SIZE
nsresult OpenDB(const nsAString &dbGUID, CDatabaseQuery *pQuery, sqlite3 **ppHandle)
sqlite3_stmt * GetStatement(sqlite3 *db)
void copy_utf16(const UTF16_CHARTYPE *aFrom, PRInt32 aSize)
friend class QueryProcessorQueue
PRPackedBool m_Shutdown
PRInt32 bufferLength()
static void PR_CALLBACK QueryProcessor(CDatabaseEngine *pEngine, QueryProcessorQueue *pQueue)
static int library_collate_func_utf16be(void *pCtx, int nA, const void *zA, int nB, const void *zB)
fastString encodingConversionBuffer1
PRPackedBool m_Running
#define PREF_BRANCH_BASE
nsresult PopQueryFromQueue(CDatabaseQuery **ppQuery)
observe data
Definition: FeedWriter.js:1329
static int tree_collate_func_next_num(const char *start, char **pos, int length, int eTextRep, int width)
_getSelectedPageStyle s i
nsRefPtr< CDatabaseEngine > mEngineCallback
nsresult GetQueueSize(PRUint32 &aSize)
CDatabaseEngine * gEngine
PRInt32 Collate(collationBuffers *aCollationBuffers, const NATIVE_CHAR_TYPE *aStr1, const NATIVE_CHAR_TYPE *aStr2)
#define SB_UNUSED_IN_RELEASE(decl)
Definition: sbDebugUtils.h:55
static nsModuleComponentInfo sbDatabaseEngine[]
CDatabaseResult * GetResultObject()
#define PREF_DB_CACHE_SIZE
_updateTextAndScrollDataForFrame aData
var file
nsresult CloseDB(sqlite3 *pHandle)
bindParameterArray_t * PopQueryParameters()