Bug 452317 - FeedConverter.js: QueryInterface should throw NS_ERROR_NO_INTERFACE...
[wine-gecko.git] / toolkit / components / satchel / src / nsFormHistory.cpp
blob9d038a89cb74d4ab42a2eb8b2e4d5bedd22f58cd
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is Mozilla Communicator client code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
23 * Joe Hewitt <hewitt@netscape.com> (Original Author)
25 * Alternatively, the contents of this file may be used under the terms of
26 * either the GNU General Public License Version 2 or later (the "GPL"), or
27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
39 #include "nsFormHistory.h"
41 #include "nsIServiceManager.h"
42 #include "nsIObserverService.h"
43 #include "nsICategoryManager.h"
44 #include "nsIDirectoryService.h"
45 #include "nsAppDirectoryServiceDefs.h"
46 #include "nsMorkCID.h"
47 #include "nsIMdbFactoryFactory.h"
48 #include "nsQuickSort.h"
49 #include "nsCRT.h"
50 #include "nsString.h"
51 #include "nsUnicharUtils.h"
52 #include "nsReadableUtils.h"
53 #include "nsIDOMNode.h"
54 #include "nsIDOMHTMLFormElement.h"
55 #include "nsIDOMHTMLInputElement.h"
56 #include "nsIDOMHTMLCollection.h"
57 #include "nsIPrefService.h"
58 #include "nsIPrefBranch.h"
59 #include "nsIPrefBranch2.h"
60 #include "nsVoidArray.h"
61 #include "nsCOMArray.h"
63 static void SwapBytes(PRUnichar* aDest, const PRUnichar* aSrc, PRUint32 aLen)
65 for(PRUint32 i = 0; i < aLen; i++)
67 PRUnichar aChar = *aSrc++;
68 *aDest++ = (0xff & (aChar >> 8)) | (aChar << 8);
72 #define PREF_FORMFILL_BRANCH "browser.formfill."
73 #define PREF_FORMFILL_ENABLE "enable"
75 // upper bounds on saved form data, more isn't useful.
76 #define FORMFILL_NAME_MAX_LEN 1000
77 #define FORMFILL_VALUE_MAX_LEN 4000
79 static const char *kFormHistoryFileName = "formhistory.dat";
81 NS_INTERFACE_MAP_BEGIN(nsFormHistory)
82 NS_INTERFACE_MAP_ENTRY(nsIFormHistory2)
83 NS_INTERFACE_MAP_ENTRY(nsIObserver)
84 NS_INTERFACE_MAP_ENTRY(nsIFormSubmitObserver)
85 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
86 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
87 NS_INTERFACE_MAP_END_THREADSAFE
89 NS_IMPL_THREADSAFE_ADDREF(nsFormHistory)
90 NS_IMPL_THREADSAFE_RELEASE(nsFormHistory)
92 mdb_column nsFormHistory::kToken_ValueColumn = 0;
93 mdb_column nsFormHistory::kToken_NameColumn = 0;
95 PRBool nsFormHistory::gFormHistoryEnabled = PR_FALSE;
96 PRBool nsFormHistory::gPrefsInitialized = PR_FALSE;
98 nsFormHistory::nsFormHistory() :
99 mEnv(nsnull),
100 mStore(nsnull),
101 mTable(nsnull),
102 mReverseByteOrder(PR_FALSE)
104 NS_ASSERTION(!gFormHistory, "nsFormHistory must be used as a service");
105 gFormHistory = this;
108 nsFormHistory::~nsFormHistory()
110 NS_ASSERTION(gFormHistory == this,
111 "nsFormHistory must be used as a service");
112 CloseDatabase();
113 gFormHistory = nsnull;
116 nsresult
117 nsFormHistory::Init()
119 nsCOMPtr<nsIObserverService> service = do_GetService("@mozilla.org/observer-service;1");
120 if (service)
121 service->AddObserver(this, NS_EARLYFORMSUBMIT_SUBJECT, PR_TRUE);
123 return NS_OK;
126 nsFormHistory *nsFormHistory::gFormHistory = nsnull;
128 /* static */ PRBool
129 nsFormHistory::FormHistoryEnabled()
131 if (!gPrefsInitialized) {
132 nsCOMPtr<nsIPrefService> prefService = do_GetService(NS_PREFSERVICE_CONTRACTID);
134 prefService->GetBranch(PREF_FORMFILL_BRANCH,
135 getter_AddRefs(gFormHistory->mPrefBranch));
136 gFormHistory->mPrefBranch->GetBoolPref(PREF_FORMFILL_ENABLE,
137 &gFormHistoryEnabled);
139 nsCOMPtr<nsIPrefBranch2> branchInternal =
140 do_QueryInterface(gFormHistory->mPrefBranch);
141 branchInternal->AddObserver(PREF_FORMFILL_ENABLE, gFormHistory, PR_TRUE);
143 gPrefsInitialized = PR_TRUE;
146 return gFormHistoryEnabled;
150 ////////////////////////////////////////////////////////////////////////
151 //// nsIFormHistory2
153 NS_IMETHODIMP
154 nsFormHistory::GetHasEntries(PRBool *aHasEntries)
156 nsresult rv = OpenDatabase(); // lazily ensure that the database is open
157 NS_ENSURE_SUCCESS(rv, rv);
159 PRUint32 rowCount;
160 mdb_err err = mTable->GetCount(mEnv, &rowCount);
161 NS_ENSURE_TRUE(!err, NS_ERROR_FAILURE);
163 *aHasEntries = rowCount != 0;
164 return NS_OK;
167 NS_IMETHODIMP
168 nsFormHistory::AddEntry(const nsAString &aName, const nsAString &aValue)
170 if (!FormHistoryEnabled())
171 return NS_OK;
173 nsresult rv = OpenDatabase(); // lazily ensure that the database is open
174 NS_ENSURE_SUCCESS(rv, rv);
176 nsCOMPtr<nsIMdbRow> row;
177 AppendRow(aName, aValue, getter_AddRefs(row));
178 return NS_OK;
181 NS_IMETHODIMP
182 nsFormHistory::EntryExists(const nsAString &aName, const nsAString &aValue, PRBool *_retval)
184 return EntriesExistInternal(&aName, &aValue, _retval);
187 NS_IMETHODIMP
188 nsFormHistory::NameExists(const nsAString &aName, PRBool *_retval)
190 return EntriesExistInternal(&aName, nsnull, _retval);
193 NS_IMETHODIMP
194 nsFormHistory::RemoveEntry(const nsAString &aName, const nsAString &aValue)
196 return NS_ERROR_NOT_IMPLEMENTED;
199 NS_IMETHODIMP
200 nsFormHistory::RemoveEntriesForName(const nsAString &aName)
202 return RemoveEntriesInternal(&aName);
205 NS_IMETHODIMP
206 nsFormHistory::RemoveAllEntries()
208 nsresult rv = RemoveEntriesInternal(nsnull);
210 if (NS_SUCCEEDED(rv))
211 rv = InitByteOrder(PR_TRUE);
213 rv |= Flush();
215 return rv;
218 ////////////////////////////////////////////////////////////////////////
219 //// nsIObserver
221 NS_IMETHODIMP
222 nsFormHistory::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *aData)
224 if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
225 mPrefBranch->GetBoolPref(PREF_FORMFILL_ENABLE, &gFormHistoryEnabled);
228 return NS_OK;
231 ////////////////////////////////////////////////////////////////////////
232 //// nsIFormSubmitObserver
234 NS_IMETHODIMP
235 nsFormHistory::Notify(nsIDOMHTMLFormElement* formElt, nsIDOMWindowInternal* aWindow, nsIURI* aActionURL, PRBool* aCancelSubmit)
237 if (!FormHistoryEnabled())
238 return NS_OK;
240 nsresult rv = OpenDatabase(); // lazily ensure that the database is open
241 NS_ENSURE_SUCCESS(rv, rv);
243 nsCOMPtr<nsIDOMHTMLCollection> elts;
244 formElt->GetElements(getter_AddRefs(elts));
246 const char *textString = "text";
248 PRUint32 length;
249 elts->GetLength(&length);
250 for (PRUint32 i = 0; i < length; ++i) {
251 nsCOMPtr<nsIDOMNode> node;
252 elts->Item(i, getter_AddRefs(node));
253 nsCOMPtr<nsIDOMHTMLInputElement> inputElt = do_QueryInterface(node);
254 if (inputElt) {
255 // Filter only inputs that are of type "text"
256 nsAutoString type;
257 inputElt->GetType(type);
258 if (type.EqualsIgnoreCase(textString)) {
259 // If this input has a name/id and value, add it to the database
260 nsAutoString value;
261 inputElt->GetValue(value);
262 if (!value.IsEmpty()) {
263 nsAutoString name;
264 inputElt->GetName(name);
265 if (name.IsEmpty())
266 inputElt->GetId(name);
268 if (!name.IsEmpty())
269 AppendRow(name, value, nsnull);
275 return NS_OK;
278 ////////////////////////////////////////////////////////////////////////
279 //// Database I/O
281 class SatchelErrorHook : public nsIMdbErrorHook
283 public:
284 NS_DECL_ISUPPORTS
286 // nsIMdbErrorHook
287 NS_IMETHOD OnErrorString(nsIMdbEnv* ev, const char* inAscii);
288 NS_IMETHOD OnErrorYarn(nsIMdbEnv* ev, const mdbYarn* inYarn);
289 NS_IMETHOD OnWarningString(nsIMdbEnv* ev, const char* inAscii);
290 NS_IMETHOD OnWarningYarn(nsIMdbEnv* ev, const mdbYarn* inYarn);
291 NS_IMETHOD OnAbortHintString(nsIMdbEnv* ev, const char* inAscii);
292 NS_IMETHOD OnAbortHintYarn(nsIMdbEnv* ev, const mdbYarn* inYarn);
295 // nsIMdbErrorHook has no IID!
296 NS_IMPL_ISUPPORTS0(SatchelErrorHook)
298 NS_IMETHODIMP
299 SatchelErrorHook::OnErrorString(nsIMdbEnv *ev, const char *inAscii)
301 printf("mork error: %s\n", inAscii);
302 return NS_OK;
305 NS_IMETHODIMP
306 SatchelErrorHook::OnErrorYarn(nsIMdbEnv *ev, const mdbYarn* inYarn)
308 printf("mork error yarn: %p\n", (void*)inYarn);
309 return NS_OK;
312 NS_IMETHODIMP
313 SatchelErrorHook::OnWarningString(nsIMdbEnv *ev, const char *inAscii)
315 printf("mork warning: %s\n", inAscii);
316 return NS_OK;
319 NS_IMETHODIMP
320 SatchelErrorHook::OnWarningYarn(nsIMdbEnv *ev, const mdbYarn *inYarn)
322 printf("mork warning yarn: %p\n", (void*)inYarn);
323 return NS_OK;
326 NS_IMETHODIMP
327 SatchelErrorHook::OnAbortHintString(nsIMdbEnv *ev, const char *inAscii)
329 printf("mork abort: %s\n", inAscii);
330 return NS_OK;
333 NS_IMETHODIMP
334 SatchelErrorHook::OnAbortHintYarn(nsIMdbEnv *ev, const mdbYarn *inYarn)
336 printf("mork abort yarn: %p\n", (void*)inYarn);
337 return NS_OK;
340 nsresult
341 nsFormHistory::OpenDatabase()
343 if (mStore)
344 return NS_OK;
346 // Get a handle to the database file
347 nsCOMPtr <nsIFile> historyFile;
348 nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(historyFile));
349 NS_ENSURE_SUCCESS(rv, rv);
350 historyFile->Append(NS_ConvertUTF8toUTF16(kFormHistoryFileName));
352 // Get an Mdb Factory
353 nsCOMPtr <nsIMdbFactoryService> mdbFactory = do_GetService(NS_MORK_CONTRACTID, &rv);
354 NS_ENSURE_SUCCESS(rv, rv);
355 rv = mdbFactory->GetMdbFactory(getter_AddRefs(mMdbFactory));
356 NS_ENSURE_SUCCESS(rv, rv);
358 // Create the Mdb environment
359 mdb_err err = mMdbFactory->MakeEnv(nsnull, &mEnv);
360 NS_ASSERTION(err == 0, "ERROR: Unable to create Form History mdb");
361 mEnv->SetAutoClear(PR_TRUE);
362 NS_ENSURE_TRUE(!err, NS_ERROR_FAILURE);
363 mEnv->SetErrorHook(new SatchelErrorHook());
365 nsCAutoString filePath;
366 historyFile->GetNativePath(filePath);
367 PRBool exists = PR_TRUE;
368 historyFile->Exists(&exists);
370 PRBool createdNew = PR_FALSE;
372 if (!exists || NS_FAILED(rv = OpenExistingFile(filePath.get()))) {
373 // If the file doesn't exist, or we fail trying to open it,
374 // then make sure it is deleted and then create an empty database file
375 historyFile->Remove(PR_FALSE);
376 rv = CreateNewFile(filePath.get());
377 createdNew = PR_TRUE;
379 NS_ENSURE_SUCCESS(rv, rv);
381 // Get the initial size of the file, needed later for Commit
382 historyFile->GetFileSize(&mFileSizeOnDisk);
384 rv = InitByteOrder(createdNew);
386 /* // TESTING: Add a row to the database
387 nsAutoString foopy;
388 foopy.AssignWithConversion("foopy");
389 nsAutoString oogly;
390 oogly.AssignWithConversion("oogly");
391 AppendRow(foopy, oogly, nsnull);
392 Flush(); */
394 /* // TESTING: Dump the contents of the database
395 PRUint32 count = 0;
396 mdb_err err = mTable->GetCount(mEnv, &count);
397 printf("%d rows in form history\n", count);
399 for (mdb_pos pos = count - 1; pos >= 0; --pos) {
400 nsCOMPtr<nsIMdbRow> row;
401 err = mTable->PosToRow(mEnv, pos, getter_AddRefs(row));
403 nsAutoString name;
404 GetRowValue(row, kToken_NameColumn, name);
405 nsAutoString value;
406 GetRowValue(row, kToken_ValueColumn, value);
407 printf("ROW: %s - %s\n", ToNewCString(name), ToNewCString(value));
408 } */
410 return rv;
413 nsresult
414 nsFormHistory::OpenExistingFile(const char *aPath)
416 nsCOMPtr<nsIMdbFile> oldFile;
417 nsIMdbHeap* dbHeap = 0;
418 mdb_err err = mMdbFactory->OpenOldFile(mEnv, dbHeap, aPath, mdbBool_kFalse, getter_AddRefs(oldFile));
419 NS_ENSURE_TRUE(!err && oldFile, NS_ERROR_FAILURE);
421 mdb_bool canOpen = 0;
422 mdbYarn outFormat = {nsnull, 0, 0, 0, 0, nsnull};
423 err = mMdbFactory->CanOpenFilePort(mEnv, oldFile, &canOpen, &outFormat);
424 NS_ENSURE_TRUE(!err && canOpen, NS_ERROR_FAILURE);
426 nsCOMPtr<nsIMdbThumb> thumb;
427 mdbOpenPolicy policy = {{0, 0}, 0, 0};
428 err = mMdbFactory->OpenFileStore(mEnv, dbHeap, oldFile, &policy, getter_AddRefs(thumb));
429 NS_ENSURE_TRUE(!err && thumb, NS_ERROR_FAILURE);
431 PRBool done;
432 mdb_err thumbErr = UseThumb(thumb, &done);
434 if (err == 0 && done)
435 err = mMdbFactory->ThumbToOpenStore(mEnv, thumb, &mStore);
436 NS_ENSURE_TRUE(!err, NS_ERROR_FAILURE);
438 nsresult rv = CreateTokens();
439 NS_ENSURE_SUCCESS(rv, rv);
441 mdbOid oid = {kToken_RowScope, 1};
442 err = mStore->GetTable(mEnv, &oid, &mTable);
443 NS_ENSURE_TRUE(!err, NS_ERROR_FAILURE);
444 if (!mTable) {
445 NS_WARNING("ERROR: Form history file is corrupt, now deleting it.");
446 return NS_ERROR_FAILURE;
449 err = mTable->GetMetaRow(mEnv, &oid, nsnull, getter_AddRefs(mMetaRow));
450 if (err)
451 NS_WARNING("Could not get meta row");
453 if (NS_FAILED(thumbErr))
454 err = thumbErr;
456 return err ? NS_ERROR_FAILURE : NS_OK;
459 nsresult
460 nsFormHistory::CreateNewFile(const char *aPath)
462 nsIMdbHeap* dbHeap = 0;
463 nsCOMPtr<nsIMdbFile> newFile;
464 mdb_err err = mMdbFactory->CreateNewFile(mEnv, dbHeap, aPath, getter_AddRefs(newFile));
465 NS_ENSURE_TRUE(!err && newFile, NS_ERROR_FAILURE);
467 nsCOMPtr <nsIMdbTable> oldTable = mTable;;
468 nsCOMPtr <nsIMdbStore> oldStore = mStore;
469 mdbOpenPolicy policy = {{0, 0}, 0, 0};
470 err = mMdbFactory->CreateNewFileStore(mEnv, dbHeap, newFile, &policy, &mStore);
471 NS_ENSURE_TRUE(!err, NS_ERROR_FAILURE);
473 nsresult rv = CreateTokens();
474 NS_ENSURE_SUCCESS(rv, rv);
476 // Create the one and only table in the database
477 err = mStore->NewTable(mEnv, kToken_RowScope, kToken_Kind, PR_TRUE, nsnull, &mTable);
478 NS_ENSURE_TRUE(!err && mTable, NS_ERROR_FAILURE);
480 mdbOid oid = {kToken_RowScope, 1};
481 err = mTable->GetMetaRow(mEnv, &oid, nsnull, getter_AddRefs(mMetaRow));
482 if (err) {
483 NS_WARNING("Could not get meta row");
484 return NS_ERROR_FAILURE;
487 // oldTable will only be set if we detected a corrupt db, and are
488 // trying to restore data from it.
489 if (oldTable)
490 CopyRowsFromTable(oldTable);
492 // Force a commit now to get it written out.
493 nsCOMPtr<nsIMdbThumb> thumb;
494 err = mStore->CompressCommit(mEnv, getter_AddRefs(thumb));
495 NS_ENSURE_TRUE(!err, NS_ERROR_FAILURE);
497 PRBool done;
498 err = UseThumb(thumb, &done);
500 return err || !done ? NS_ERROR_FAILURE : NS_OK;
503 nsresult
504 nsFormHistory::CloseDatabase()
506 Flush();
508 mMetaRow = nsnull;
510 if (mTable)
511 mTable->Release();
513 if (mStore)
514 mStore->Release();
516 if (mEnv)
517 mEnv->Release();
519 mTable = nsnull;
520 mEnv = nsnull;
521 mStore = nsnull;
523 return NS_OK;
526 nsresult
527 nsFormHistory::CreateTokens()
529 mdb_err err;
531 if (!mStore)
532 return NS_ERROR_NOT_INITIALIZED;
534 err = mStore->StringToToken(mEnv, "ns:formhistory:db:row:scope:formhistory:all", &kToken_RowScope);
535 if (err != 0) return NS_ERROR_FAILURE;
537 err = mStore->StringToToken(mEnv, "ns:formhistory:db:table:kind:formhistory", &kToken_Kind);
538 if (err != 0) return NS_ERROR_FAILURE;
540 err = mStore->StringToToken(mEnv, "Value", &kToken_ValueColumn);
541 if (err != 0) return NS_ERROR_FAILURE;
543 err = mStore->StringToToken(mEnv, "Name", &kToken_NameColumn);
544 if (err != 0) return NS_ERROR_FAILURE;
546 err = mStore->StringToToken(mEnv, "ByteOrder", &kToken_ByteOrder);
547 if (err != 0) return NS_ERROR_FAILURE;
549 return NS_OK;
552 nsresult
553 nsFormHistory::Flush()
555 if (!mStore || !mTable)
556 return NS_OK;
558 mdb_err err;
560 nsCOMPtr<nsIMdbThumb> thumb;
561 err = mStore->CompressCommit(mEnv, getter_AddRefs(thumb));
563 if (err == 0)
564 err = UseThumb(thumb, nsnull);
566 return err ? NS_ERROR_FAILURE : NS_OK;
569 mdb_err
570 nsFormHistory::UseThumb(nsIMdbThumb *aThumb, PRBool *aDone)
572 mdb_count total;
573 mdb_count current;
574 mdb_bool done;
575 mdb_bool broken;
576 mdb_err err;
578 do {
579 err = aThumb->DoMore(mEnv, &total, &current, &done, &broken);
580 } while ((err == 0) && !broken && !done);
582 if (aDone)
583 *aDone = done;
585 return err ? NS_ERROR_FAILURE : NS_OK;
588 nsresult
589 nsFormHistory::CopyRowsFromTable(nsIMdbTable *sourceTable)
591 nsCOMPtr<nsIMdbTableRowCursor> rowCursor;
592 mdb_err err = sourceTable->GetTableRowCursor(mEnv, -1, getter_AddRefs(rowCursor));
593 NS_ENSURE_TRUE(!err, NS_ERROR_FAILURE);
595 nsCOMPtr<nsIMdbRow> row;
596 mdb_pos pos;
597 do {
598 rowCursor->NextRow(mEnv, getter_AddRefs(row), &pos);
599 if (!row)
600 break;
602 mdbOid rowId;
603 rowId.mOid_Scope = kToken_RowScope;
604 rowId.mOid_Id = mdb_id(-1);
606 nsCOMPtr<nsIMdbRow> newRow;
607 mTable->NewRow(mEnv, &rowId, getter_AddRefs(newRow));
608 newRow->SetRow(mEnv, row);
609 mTable->AddRow(mEnv, newRow);
610 } while (row);
611 return NS_OK;
614 nsresult
615 nsFormHistory::AppendRow(const nsAString &aName, const nsAString &aValue, nsIMdbRow **aResult)
617 if (!mTable)
618 return NS_ERROR_NOT_INITIALIZED;
620 if (aName.Length() > FORMFILL_NAME_MAX_LEN ||
621 aValue.Length() > FORMFILL_VALUE_MAX_LEN)
622 return NS_ERROR_INVALID_ARG;
624 PRBool exists = PR_TRUE;
625 EntryExists(aName, aValue, &exists);
626 if (exists)
627 return NS_OK;
629 mdbOid rowId;
630 rowId.mOid_Scope = kToken_RowScope;
631 rowId.mOid_Id = mdb_id(-1);
633 nsCOMPtr<nsIMdbRow> row;
634 mdb_err err = mTable->NewRow(mEnv, &rowId, getter_AddRefs(row));
635 if (err != 0)
636 return NS_ERROR_FAILURE;
638 SetRowValue(row, kToken_NameColumn, aName);
639 SetRowValue(row, kToken_ValueColumn, aValue);
641 if (aResult) {
642 *aResult = row;
643 NS_ADDREF(*aResult);
646 return NS_OK;
649 nsresult
650 nsFormHistory::SetRowValue(nsIMdbRow *aRow, mdb_column aCol, const nsAString &aValue)
652 PRInt32 len = aValue.Length() * sizeof(PRUnichar);
653 PRUnichar *swapval = nsnull;
654 mdbYarn yarn = {nsnull, len, len, 0, 0, nsnull};
655 const nsPromiseFlatString& buffer = PromiseFlatString(aValue);
657 if (mReverseByteOrder) {
658 swapval = new PRUnichar[aValue.Length()];
659 if (!swapval)
660 return NS_ERROR_OUT_OF_MEMORY;
661 SwapBytes(swapval, buffer.get(), aValue.Length());
662 yarn.mYarn_Buf = swapval;
664 else
665 yarn.mYarn_Buf = (void*)buffer.get();
667 mdb_err err = aRow->AddColumn(mEnv, aCol, &yarn);
669 delete swapval;
671 return err ? NS_ERROR_FAILURE : NS_OK;
674 nsresult
675 nsFormHistory::GetRowValue(nsIMdbRow *aRow, mdb_column aCol, nsAString &aValue)
677 mdbYarn yarn;
678 mdb_err err = aRow->AliasCellYarn(mEnv, aCol, &yarn);
679 if (err != 0)
680 return NS_ERROR_FAILURE;
682 aValue.Truncate(0);
683 if (!yarn.mYarn_Fill)
684 return NS_OK;
686 switch (yarn.mYarn_Form) {
687 case 0: { // unicode
688 PRUint32 len = yarn.mYarn_Fill / sizeof(PRUnichar);
689 if (mReverseByteOrder) {
690 PRUnichar *swapval = new PRUnichar[len];
691 if (!swapval)
692 return NS_ERROR_OUT_OF_MEMORY;
693 SwapBytes(swapval, (const PRUnichar*)yarn.mYarn_Buf, len);
694 aValue.Assign(swapval, len);
695 delete swapval;
697 else
698 aValue.Assign((const PRUnichar *)yarn.mYarn_Buf, len);
699 break;
701 default:
702 return NS_ERROR_UNEXPECTED;
705 return NS_OK;
708 nsresult
709 nsFormHistory::AutoCompleteSearch(const nsAString &aInputName,
710 const nsAString &aInputValue,
711 nsIAutoCompleteMdbResult2 *aPrevResult,
712 nsIAutoCompleteResult **aResult)
714 if (!FormHistoryEnabled())
715 return NS_OK;
717 nsresult rv = OpenDatabase(); // lazily ensure that the database is open
718 NS_ENSURE_SUCCESS(rv, rv);
720 nsCOMPtr<nsIAutoCompleteMdbResult2> result;
722 if (aPrevResult) {
723 result = aPrevResult;
725 PRUint32 rowCount;
726 result->GetMatchCount(&rowCount);
728 for (PRInt32 i = rowCount-1; i >= 0; --i) {
729 nsIMdbRow *row;
730 result->GetRowAt(i, &row);
731 if (!RowMatch(row, aInputName, aInputValue, nsnull))
732 result->RemoveValueAt(i, PR_FALSE);
734 } else {
735 result = do_CreateInstance("@mozilla.org/autocomplete/mdb-result;1");
737 result->SetSearchString(aInputValue);
738 result->Init(mEnv, mTable);
739 result->SetTokens(kToken_ValueColumn, nsIAutoCompleteMdbResult2::kUnicharType, nsnull, nsIAutoCompleteMdbResult2::kUnicharType);
740 result->SetReverseByteOrder(mReverseByteOrder);
742 // Get a cursor to iterate through all rows in the database
743 nsCOMPtr<nsIMdbTableRowCursor> rowCursor;
744 mdb_err err = mTable->GetTableRowCursor(mEnv, -1, getter_AddRefs(rowCursor));
745 NS_ENSURE_TRUE(!err, NS_ERROR_FAILURE);
747 // Store only the matching values
748 nsAutoVoidArray matchingValues;
749 nsCOMArray<nsIMdbRow> matchingRows;
751 nsCOMPtr<nsIMdbRow> row;
752 mdb_pos pos;
753 do {
754 rowCursor->NextRow(mEnv, getter_AddRefs(row), &pos);
755 if (!row)
756 break;
758 PRUnichar *value = 0; // We will own the allocated string value
759 if (RowMatch(row, aInputName, aInputValue, &value)) {
760 matchingRows.AppendObject(row);
761 matchingValues.AppendElement(value);
763 } while (row);
765 // Turn auto array into flat array for quick sort, now that we
766 // know how many items there are
767 PRUint32 count = matchingRows.Count();
769 if (count > 0) {
770 PRUint32* items = new PRUint32[count];
771 PRUint32 i;
772 for (i = 0; i < count; ++i)
773 items[i] = i;
775 NS_QuickSort(items, count, sizeof(PRUint32),
776 SortComparison, &matchingValues);
778 for (i = 0; i < count; ++i) {
779 // Place the sorted result into the autocomplete result
780 result->AddRow(matchingRows[items[i]]);
782 // Free up these strings we owned.
783 NS_Free(matchingValues[i]);
786 delete[] items;
789 PRUint32 matchCount;
790 result->GetMatchCount(&matchCount);
791 if (matchCount > 0) {
792 result->SetSearchResult(nsIAutoCompleteResult::RESULT_SUCCESS);
793 result->SetDefaultIndex(0);
794 } else {
795 result->SetSearchResult(nsIAutoCompleteResult::RESULT_NOMATCH);
796 result->SetDefaultIndex(-1);
800 *aResult = result;
801 NS_IF_ADDREF(*aResult);
803 return NS_OK;
806 int PR_CALLBACK
807 nsFormHistory::SortComparison(const void *v1, const void *v2, void *closureVoid)
809 PRUint32 *index1 = (PRUint32 *)v1;
810 PRUint32 *index2 = (PRUint32 *)v2;
811 nsAutoVoidArray *array = (nsAutoVoidArray *)closureVoid;
813 PRUnichar *s1 = (PRUnichar *)array->ElementAt(*index1);
814 PRUnichar *s2 = (PRUnichar *)array->ElementAt(*index2);
816 return nsCRT::strcmp(s1, s2);
819 PRBool
820 nsFormHistory::RowMatch(nsIMdbRow *aRow, const nsAString &aInputName, const nsAString &aInputValue, PRUnichar **aValue)
822 nsAutoString name;
823 GetRowValue(aRow, kToken_NameColumn, name);
825 if (name.Equals(aInputName)) {
826 nsAutoString value;
827 GetRowValue(aRow, kToken_ValueColumn, value);
828 if (Compare(Substring(value, 0, aInputValue.Length()), aInputValue, nsCaseInsensitiveStringComparator()) == 0) {
829 if (aValue)
830 *aValue = ToNewUnicode(value);
831 return PR_TRUE;
835 return PR_FALSE;
838 nsresult
839 nsFormHistory::EntriesExistInternal(const nsAString *aName, const nsAString *aValue, PRBool *_retval)
841 // Unfortunately we have to do a brute force search through the database
842 // because mork didn't bother to implement any indexing functionality
844 *_retval = PR_FALSE;
846 nsresult rv = OpenDatabase(); // lazily ensure that the database is open
847 NS_ENSURE_SUCCESS(rv, rv);
849 // Get a cursor to iterate through all rows in the database
850 nsCOMPtr<nsIMdbTableRowCursor> rowCursor;
851 mdb_err err = mTable->GetTableRowCursor(mEnv, -1, getter_AddRefs(rowCursor));
852 NS_ENSURE_TRUE(!err, NS_ERROR_FAILURE);
854 nsCOMPtr<nsIMdbRow> row;
855 mdb_pos pos;
856 do {
857 rowCursor->NextRow(mEnv, getter_AddRefs(row), &pos);
858 if (!row)
859 break;
861 // Check if the name and value combination match this row
862 nsAutoString name;
863 GetRowValue(row, kToken_NameColumn, name);
865 if (Compare(name, *aName, nsCaseInsensitiveStringComparator()) == 0) {
866 nsAutoString value;
867 GetRowValue(row, kToken_ValueColumn, value);
868 if (!aValue || Compare(value, *aValue, nsCaseInsensitiveStringComparator()) == 0) {
869 *_retval = PR_TRUE;
870 break;
873 } while (1);
875 return NS_OK;
878 nsresult
879 nsFormHistory::RemoveEntriesInternal(const nsAString *aName)
881 nsresult rv = OpenDatabase(); // lazily ensure that the database is open
882 NS_ENSURE_SUCCESS(rv, rv);
884 if (!mTable) return NS_OK;
886 mdb_err err;
887 mdb_count count;
888 nsAutoString name;
889 err = mTable->GetCount(mEnv, &count);
890 if (err != 0) return NS_ERROR_FAILURE;
892 // Begin the batch.
893 int marker;
894 err = mTable->StartBatchChangeHint(mEnv, &marker);
895 NS_ASSERTION(err == 0, "unable to start batch");
896 if (err != 0) return NS_ERROR_FAILURE;
898 for (mdb_pos pos = count - 1; pos >= 0; --pos) {
899 nsCOMPtr<nsIMdbRow> row;
900 err = mTable->PosToRow(mEnv, pos, getter_AddRefs(row));
901 NS_ASSERTION(err == 0, "unable to get row");
902 if (err != 0)
903 break;
905 NS_ASSERTION(row != nsnull, "no row");
906 if (! row)
907 continue;
909 // Check if the name matches this row
910 GetRowValue(row, kToken_NameColumn, name);
912 if (!aName || Compare(name, *aName, nsCaseInsensitiveStringComparator()) == 0) {
914 // Officially cut the row *now*, before notifying any observers:
915 // that way, any re-entrant calls won't find the row.
916 err = mTable->CutRow(mEnv, row);
917 NS_ASSERTION(err == 0, "couldn't cut row");
918 if (err != 0)
919 continue;
921 // possibly avoid leakage
922 err = row->CutAllColumns(mEnv);
923 NS_ASSERTION(err == 0, "couldn't cut all columns");
924 // we'll notify regardless of whether we could successfully
925 // CutAllColumns or not.
930 // Finish the batch.
931 err = mTable->EndBatchChangeHint(mEnv, &marker);
932 NS_ASSERTION(err == 0, "error ending batch");
934 return (err == 0) ? NS_OK : NS_ERROR_FAILURE;
938 nsresult
939 nsFormHistory::InitByteOrder(PRBool aForce)
941 // bigEndian and littleEndian are endianness markers that are stored in
942 // the formhistory db as UTF-16. Define them to be strings easily
943 // recognized in either endianness.
944 nsAutoString bigEndianByteOrder((PRUnichar*)"BBBB", 2);
945 nsAutoString littleEndianByteOrder((PRUnichar*)"llll", 2);
946 #ifdef IS_BIG_ENDIAN
947 nsAutoString nativeByteOrder(bigEndianByteOrder);
948 #else
949 nsAutoString nativeByteOrder(littleEndianByteOrder);
950 #endif
952 nsAutoString fileByteOrder;
953 nsresult rv = NS_OK;
955 if (!aForce)
956 rv = GetByteOrder(fileByteOrder);
958 if (aForce || NS_FAILED(rv) ||
959 !(fileByteOrder.Equals(bigEndianByteOrder) ||
960 fileByteOrder.Equals(littleEndianByteOrder))) {
961 #if defined(XP_MACOSX) && defined(IS_LITTLE_ENDIAN)
962 // The formhistory db did not carry endiannes information until the
963 // initial x86 Mac release. There are a lot of users out there who
964 // will be switching from ppc versions to x86, and their unmarked
965 // formhistory files are big-endian. On x86 Macs, unless aForce is set
966 // (indicating formhistory reset or a brand-new db), use big-endian byte
967 // ordering and turn swapping on.
968 if (aForce) {
969 mReverseByteOrder = PR_FALSE;
970 rv = SaveByteOrder(nativeByteOrder);
972 else {
973 mReverseByteOrder = PR_TRUE;
974 rv = SaveByteOrder(bigEndianByteOrder);
976 #else
977 mReverseByteOrder = PR_FALSE;
978 rv = SaveByteOrder(nativeByteOrder);
979 #endif
981 else
982 mReverseByteOrder = !fileByteOrder.Equals(nativeByteOrder);
984 return rv;
987 nsresult
988 nsFormHistory::GetByteOrder(nsAString& aByteOrder)
990 NS_ENSURE_SUCCESS(OpenDatabase(), NS_ERROR_FAILURE);
992 mdb_err err = GetRowValue(mMetaRow, kToken_ByteOrder, aByteOrder);
993 NS_ENSURE_FALSE(err, NS_ERROR_FAILURE);
995 return NS_OK;
998 nsresult
999 nsFormHistory::SaveByteOrder(const nsAString& aByteOrder)
1001 NS_ENSURE_SUCCESS(OpenDatabase(), NS_ERROR_FAILURE);
1003 mdb_err err = SetRowValue(mMetaRow, kToken_ByteOrder, aByteOrder);
1004 NS_ENSURE_FALSE(err, NS_ERROR_FAILURE);
1006 return NS_OK;