Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* This Source Code Form is subject to the terms of the Mozilla Public
3
* License, v. 2.0. If a copy of the MPL was not distributed with this
4
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6
#include "nsNavBookmarks.h"
7
8
#include "nsNavHistory.h"
9
#include "nsAnnotationService.h"
10
#include "nsPlacesMacros.h"
11
#include "Helpers.h"
12
13
#include "nsAppDirectoryServiceDefs.h"
14
#include "nsNetUtil.h"
15
#include "nsUnicharUtils.h"
16
#include "nsPrintfCString.h"
17
#include "nsQueryObject.h"
18
#include "mozilla/Preferences.h"
19
#include "mozilla/storage.h"
20
#include "mozilla/dom/PlacesBookmarkAddition.h"
21
#include "mozilla/dom/PlacesObservers.h"
22
#include "mozilla/dom/PlacesVisit.h"
23
24
#include "GeckoProfiler.h"
25
26
using namespace mozilla;
27
28
// These columns sit to the right of the kGetInfoIndex_* columns.
29
const int32_t nsNavBookmarks::kGetChildrenIndex_Guid = 18;
30
const int32_t nsNavBookmarks::kGetChildrenIndex_Position = 19;
31
const int32_t nsNavBookmarks::kGetChildrenIndex_Type = 20;
32
const int32_t nsNavBookmarks::kGetChildrenIndex_PlaceID = 21;
33
const int32_t nsNavBookmarks::kGetChildrenIndex_SyncStatus = 22;
34
35
using namespace mozilla::places;
36
37
extern "C" {
38
39
// Returns the total number of Sync changes recorded since Places startup for
40
// all bookmarks. This function uses C linkage because it's called from the
41
// Rust synced bookmarks mirror, on the storage thread. Using `get_service` to
42
// access the bookmarks service from Rust trips a thread-safety assertion, so
43
// we can't use `nsNavBookmarks::GetTotalSyncChanges`.
44
int64_t NS_NavBookmarksTotalSyncChanges() {
45
return nsNavBookmarks::sTotalSyncChanges;
46
}
47
48
} // extern "C"
49
50
PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsNavBookmarks, gBookmarksService)
51
52
namespace {
53
54
#define SKIP_TAGS(condition) ((condition) ? SkipTags : DontSkip)
55
56
bool DontSkip(nsCOMPtr<nsINavBookmarkObserver> obs) { return false; }
57
bool SkipTags(nsCOMPtr<nsINavBookmarkObserver> obs) {
58
bool skipTags = false;
59
(void)obs->GetSkipTags(&skipTags);
60
return skipTags;
61
}
62
bool SkipDescendants(nsCOMPtr<nsINavBookmarkObserver> obs) {
63
bool skipDescendantsOnItemRemoval = false;
64
(void)obs->GetSkipDescendantsOnItemRemoval(&skipDescendantsOnItemRemoval);
65
return skipDescendantsOnItemRemoval;
66
}
67
68
template <typename Method, typename DataType>
69
class AsyncGetBookmarksForURI : public AsyncStatementCallback {
70
public:
71
AsyncGetBookmarksForURI(nsNavBookmarks* aBookmarksSvc, Method aCallback,
72
const DataType& aData)
73
: mBookmarksSvc(aBookmarksSvc), mCallback(aCallback), mData(aData) {}
74
75
void Init() {
76
RefPtr<Database> DB = Database::GetDatabase();
77
if (DB) {
78
nsCOMPtr<mozIStorageAsyncStatement> stmt = DB->GetAsyncStatement(
79
"/* do not warn (bug 1175249) */ "
80
"SELECT b.id, b.guid, b.parent, b.lastModified, t.guid, t.parent "
81
"FROM moz_bookmarks b "
82
"JOIN moz_bookmarks t on t.id = b.parent "
83
"WHERE b.fk = (SELECT id FROM moz_places WHERE url_hash = "
84
"hash(:page_url) AND url = :page_url) "
85
"ORDER BY b.lastModified DESC, b.id DESC ");
86
if (stmt) {
87
(void)URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"),
88
mData.bookmark.url);
89
nsCOMPtr<mozIStoragePendingStatement> pendingStmt;
90
(void)stmt->ExecuteAsync(this, getter_AddRefs(pendingStmt));
91
}
92
}
93
}
94
95
NS_IMETHOD HandleResult(mozIStorageResultSet* aResultSet) override {
96
nsCOMPtr<mozIStorageRow> row;
97
while (NS_SUCCEEDED(aResultSet->GetNextRow(getter_AddRefs(row))) && row) {
98
// Skip tags, for the use-cases of this async getter they are useless.
99
int64_t grandParentId = -1, tagsFolderId = -1;
100
nsresult rv = row->GetInt64(5, &grandParentId);
101
NS_ENSURE_SUCCESS(rv, rv);
102
rv = mBookmarksSvc->GetTagsFolder(&tagsFolderId);
103
NS_ENSURE_SUCCESS(rv, rv);
104
if (grandParentId == tagsFolderId) {
105
continue;
106
}
107
108
mData.bookmark.grandParentId = grandParentId;
109
rv = row->GetInt64(0, &mData.bookmark.id);
110
NS_ENSURE_SUCCESS(rv, rv);
111
rv = row->GetUTF8String(1, mData.bookmark.guid);
112
NS_ENSURE_SUCCESS(rv, rv);
113
rv = row->GetInt64(2, &mData.bookmark.parentId);
114
NS_ENSURE_SUCCESS(rv, rv);
115
// lastModified (3) should not be set for the use-cases of this getter.
116
rv = row->GetUTF8String(4, mData.bookmark.parentGuid);
117
NS_ENSURE_SUCCESS(rv, rv);
118
119
if (mCallback) {
120
((*mBookmarksSvc).*mCallback)(mData);
121
}
122
}
123
return NS_OK;
124
}
125
126
private:
127
RefPtr<nsNavBookmarks> mBookmarksSvc;
128
Method mCallback;
129
DataType mData;
130
};
131
132
// Returns the sync change counter increment for a change source constant.
133
inline int64_t DetermineSyncChangeDelta(uint16_t aSource) {
134
return aSource == nsINavBookmarksService::SOURCE_SYNC ? 0 : 1;
135
}
136
137
// Returns the sync status for a new item inserted by a change source.
138
inline int32_t DetermineInitialSyncStatus(uint16_t aSource) {
139
if (aSource == nsINavBookmarksService::SOURCE_SYNC) {
140
return nsINavBookmarksService::SYNC_STATUS_NORMAL;
141
}
142
if (aSource == nsINavBookmarksService::SOURCE_RESTORE_ON_STARTUP) {
143
return nsINavBookmarksService::SYNC_STATUS_UNKNOWN;
144
}
145
return nsINavBookmarksService::SYNC_STATUS_NEW;
146
}
147
148
// Indicates whether an item has been uploaded to the server and
149
// needs a tombstone on deletion.
150
inline bool NeedsTombstone(const BookmarkData& aBookmark) {
151
return aBookmark.syncStatus == nsINavBookmarksService::SYNC_STATUS_NORMAL;
152
}
153
154
} // namespace
155
156
nsNavBookmarks::nsNavBookmarks() : mCanNotify(false) {
157
NS_ASSERTION(!gBookmarksService,
158
"Attempting to create two instances of the service!");
159
gBookmarksService = this;
160
}
161
162
nsNavBookmarks::~nsNavBookmarks() {
163
NS_ASSERTION(gBookmarksService == this,
164
"Deleting a non-singleton instance of the service");
165
if (gBookmarksService == this) gBookmarksService = nullptr;
166
}
167
168
NS_IMPL_ISUPPORTS(nsNavBookmarks, nsINavBookmarksService, nsINavHistoryObserver,
169
nsIObserver, nsISupportsWeakReference)
170
171
Atomic<int64_t> nsNavBookmarks::sLastInsertedItemId(0);
172
173
void // static
174
nsNavBookmarks::StoreLastInsertedId(const nsACString& aTable,
175
const int64_t aLastInsertedId) {
176
MOZ_ASSERT(aTable.EqualsLiteral("moz_bookmarks"));
177
sLastInsertedItemId = aLastInsertedId;
178
}
179
180
Atomic<int64_t> nsNavBookmarks::sTotalSyncChanges(0);
181
182
void // static
183
nsNavBookmarks::NoteSyncChange() {
184
sTotalSyncChanges++;
185
}
186
187
nsresult nsNavBookmarks::Init() {
188
mDB = Database::GetDatabase();
189
NS_ENSURE_STATE(mDB);
190
191
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
192
if (os) {
193
(void)os->AddObserver(this, TOPIC_PLACES_CONNECTION_CLOSED, true);
194
}
195
196
mCanNotify = true;
197
198
// Allows us to notify on title changes. MUST BE LAST so it is impossible
199
// to fail after this call, or the history service will have a reference to
200
// us and we won't go away.
201
nsNavHistory* history = nsNavHistory::GetHistoryService();
202
NS_ENSURE_STATE(history);
203
history->AddObserver(this, true);
204
AutoTArray<PlacesEventType, 1> events;
205
events.AppendElement(PlacesEventType::Page_visited, fallible);
206
PlacesObservers::AddListener(events, this);
207
208
// DO NOT PUT STUFF HERE that can fail. See observer comment above.
209
210
return NS_OK;
211
}
212
213
nsresult nsNavBookmarks::AdjustIndices(int64_t aFolderId, int32_t aStartIndex,
214
int32_t aEndIndex, int32_t aDelta) {
215
NS_ASSERTION(
216
aStartIndex >= 0 && aEndIndex <= INT32_MAX && aStartIndex <= aEndIndex,
217
"Bad indices");
218
219
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
220
"UPDATE moz_bookmarks SET position = position + :delta "
221
"WHERE parent = :parent "
222
"AND position BETWEEN :from_index AND :to_index");
223
NS_ENSURE_STATE(stmt);
224
mozStorageStatementScoper scoper(stmt);
225
226
nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("delta"), aDelta);
227
NS_ENSURE_SUCCESS(rv, rv);
228
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
229
NS_ENSURE_SUCCESS(rv, rv);
230
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("from_index"), aStartIndex);
231
NS_ENSURE_SUCCESS(rv, rv);
232
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("to_index"), aEndIndex);
233
NS_ENSURE_SUCCESS(rv, rv);
234
235
rv = stmt->Execute();
236
NS_ENSURE_SUCCESS(rv, rv);
237
238
return NS_OK;
239
}
240
241
nsresult nsNavBookmarks::AdjustSeparatorsSyncCounter(int64_t aFolderId,
242
int32_t aStartIndex,
243
int64_t aSyncChangeDelta) {
244
MOZ_ASSERT(aStartIndex >= 0, "Bad start position");
245
if (!aSyncChangeDelta) {
246
return NS_OK;
247
}
248
249
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
250
"UPDATE moz_bookmarks SET syncChangeCounter = syncChangeCounter + :delta "
251
"WHERE parent = :parent AND position >= :start_index "
252
"AND type = :item_type ");
253
NS_ENSURE_STATE(stmt);
254
mozStorageStatementScoper scoper(stmt);
255
256
nsresult rv =
257
stmt->BindInt64ByName(NS_LITERAL_CSTRING("delta"), aSyncChangeDelta);
258
NS_ENSURE_SUCCESS(rv, rv);
259
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
260
NS_ENSURE_SUCCESS(rv, rv);
261
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("start_index"), aStartIndex);
262
NS_ENSURE_SUCCESS(rv, rv);
263
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_type"), TYPE_SEPARATOR);
264
NS_ENSURE_SUCCESS(rv, rv);
265
266
rv = stmt->Execute();
267
NS_ENSURE_SUCCESS(rv, rv);
268
269
return NS_OK;
270
}
271
272
NS_IMETHODIMP
273
nsNavBookmarks::GetPlacesRoot(int64_t* aRoot) {
274
int64_t id = mDB->GetRootFolderId();
275
NS_ENSURE_TRUE(id > 0, NS_ERROR_UNEXPECTED);
276
*aRoot = id;
277
return NS_OK;
278
}
279
280
NS_IMETHODIMP
281
nsNavBookmarks::GetBookmarksMenuFolder(int64_t* aRoot) {
282
int64_t id = mDB->GetMenuFolderId();
283
NS_ENSURE_TRUE(id > 0, NS_ERROR_UNEXPECTED);
284
*aRoot = id;
285
return NS_OK;
286
}
287
288
NS_IMETHODIMP
289
nsNavBookmarks::GetToolbarFolder(int64_t* aRoot) {
290
int64_t id = mDB->GetToolbarFolderId();
291
NS_ENSURE_TRUE(id > 0, NS_ERROR_UNEXPECTED);
292
*aRoot = id;
293
return NS_OK;
294
}
295
296
NS_IMETHODIMP
297
nsNavBookmarks::GetTagsFolder(int64_t* aRoot) {
298
int64_t id = mDB->GetTagsFolderId();
299
NS_ENSURE_TRUE(id > 0, NS_ERROR_UNEXPECTED);
300
*aRoot = id;
301
return NS_OK;
302
}
303
304
NS_IMETHODIMP
305
nsNavBookmarks::GetTotalSyncChanges(int64_t* aTotalSyncChanges) {
306
*aTotalSyncChanges = sTotalSyncChanges;
307
return NS_OK;
308
}
309
310
nsresult nsNavBookmarks::InsertBookmarkInDB(
311
int64_t aPlaceId, enum ItemType aItemType, int64_t aParentId,
312
int32_t aIndex, const nsACString& aTitle, PRTime aDateAdded,
313
PRTime aLastModified, const nsACString& aParentGuid, int64_t aGrandParentId,
314
nsIURI* aURI, uint16_t aSource, int64_t* _itemId, nsACString& _guid) {
315
// Check for a valid itemId.
316
MOZ_ASSERT(_itemId && (*_itemId == -1 || *_itemId > 0));
317
// Check for a valid placeId.
318
MOZ_ASSERT(aPlaceId && (aPlaceId == -1 || aPlaceId > 0));
319
320
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
321
"INSERT INTO moz_bookmarks "
322
"(id, fk, type, parent, position, title, "
323
"dateAdded, lastModified, guid, syncStatus, syncChangeCounter) "
324
"VALUES (:item_id, :page_id, :item_type, :parent, :item_index, "
325
":item_title, :date_added, :last_modified, "
326
":item_guid, :sync_status, :change_counter)");
327
NS_ENSURE_STATE(stmt);
328
mozStorageStatementScoper scoper(stmt);
329
330
nsresult rv;
331
if (*_itemId != -1)
332
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), *_itemId);
333
else
334
rv = stmt->BindNullByName(NS_LITERAL_CSTRING("item_id"));
335
NS_ENSURE_SUCCESS(rv, rv);
336
337
if (aPlaceId != -1)
338
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), aPlaceId);
339
else
340
rv = stmt->BindNullByName(NS_LITERAL_CSTRING("page_id"));
341
NS_ENSURE_SUCCESS(rv, rv);
342
343
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_type"), aItemType);
344
NS_ENSURE_SUCCESS(rv, rv);
345
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aParentId);
346
NS_ENSURE_SUCCESS(rv, rv);
347
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_index"), aIndex);
348
NS_ENSURE_SUCCESS(rv, rv);
349
350
if (aTitle.IsEmpty())
351
rv = stmt->BindNullByName(NS_LITERAL_CSTRING("item_title"));
352
else
353
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("item_title"), aTitle);
354
NS_ENSURE_SUCCESS(rv, rv);
355
356
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("date_added"), aDateAdded);
357
NS_ENSURE_SUCCESS(rv, rv);
358
359
if (aLastModified) {
360
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("last_modified"),
361
aLastModified);
362
} else {
363
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("last_modified"), aDateAdded);
364
}
365
NS_ENSURE_SUCCESS(rv, rv);
366
367
// Could use IsEmpty because our callers check for GUID validity,
368
// but it doesn't hurt.
369
bool hasExistingGuid = _guid.Length() == 12;
370
if (hasExistingGuid) {
371
MOZ_ASSERT(IsValidGUID(_guid));
372
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("item_guid"), _guid);
373
NS_ENSURE_SUCCESS(rv, rv);
374
} else {
375
nsAutoCString guid;
376
rv = GenerateGUID(guid);
377
NS_ENSURE_SUCCESS(rv, rv);
378
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("item_guid"), guid);
379
NS_ENSURE_SUCCESS(rv, rv);
380
_guid.Assign(guid);
381
}
382
383
int64_t syncChangeDelta = DetermineSyncChangeDelta(aSource);
384
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("change_counter"),
385
syncChangeDelta);
386
NS_ENSURE_SUCCESS(rv, rv);
387
388
uint16_t syncStatus = DetermineInitialSyncStatus(aSource);
389
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("sync_status"), syncStatus);
390
NS_ENSURE_SUCCESS(rv, rv);
391
392
rv = stmt->Execute();
393
NS_ENSURE_SUCCESS(rv, rv);
394
395
// Remove stale tombstones if we're reinserting an item.
396
if (hasExistingGuid) {
397
rv = RemoveTombstone(_guid);
398
NS_ENSURE_SUCCESS(rv, rv);
399
}
400
401
if (*_itemId == -1) {
402
*_itemId = sLastInsertedItemId;
403
}
404
405
if (aParentId > 0) {
406
// Update last modified date of the ancestors.
407
// TODO (bug 408991): Doing this for all ancestors would be slow without a
408
// nested tree, so for now update only the parent.
409
rv = SetItemDateInternal(LAST_MODIFIED, syncChangeDelta, aParentId,
410
aDateAdded);
411
NS_ENSURE_SUCCESS(rv, rv);
412
}
413
414
int64_t tagsRootId = TagsRootId();
415
bool isTagging = aGrandParentId == tagsRootId;
416
if (isTagging) {
417
// If we're tagging a bookmark, increment the change counter for all
418
// bookmarks with the URI.
419
rv = AddSyncChangesForBookmarksWithURI(aURI, syncChangeDelta);
420
NS_ENSURE_SUCCESS(rv, rv);
421
}
422
423
// Mark all affected separators as changed
424
rv = AdjustSeparatorsSyncCounter(aParentId, aIndex + 1, syncChangeDelta);
425
NS_ENSURE_SUCCESS(rv, rv);
426
427
// Add a cache entry since we know everything about this bookmark.
428
BookmarkData bookmark;
429
bookmark.id = *_itemId;
430
bookmark.guid.Assign(_guid);
431
if (!aTitle.IsEmpty()) {
432
bookmark.title.Assign(aTitle);
433
}
434
bookmark.position = aIndex;
435
bookmark.placeId = aPlaceId;
436
bookmark.parentId = aParentId;
437
bookmark.type = aItemType;
438
bookmark.dateAdded = aDateAdded;
439
if (aLastModified)
440
bookmark.lastModified = aLastModified;
441
else
442
bookmark.lastModified = aDateAdded;
443
if (aURI) {
444
rv = aURI->GetSpec(bookmark.url);
445
NS_ENSURE_SUCCESS(rv, rv);
446
}
447
bookmark.parentGuid = aParentGuid;
448
bookmark.grandParentId = aGrandParentId;
449
bookmark.syncStatus = syncStatus;
450
451
return NS_OK;
452
}
453
454
NS_IMETHODIMP
455
nsNavBookmarks::InsertBookmark(int64_t aFolder, nsIURI* aURI, int32_t aIndex,
456
const nsACString& aTitle,
457
const nsACString& aGUID, uint16_t aSource,
458
int64_t* aNewBookmarkId) {
459
NS_ENSURE_ARG(aURI);
460
NS_ENSURE_ARG_POINTER(aNewBookmarkId);
461
NS_ENSURE_ARG_MIN(aIndex, nsINavBookmarksService::DEFAULT_INDEX);
462
463
if (!aGUID.IsEmpty() && !IsValidGUID(aGUID)) return NS_ERROR_INVALID_ARG;
464
465
mozStorageTransaction transaction(mDB->MainConn(), false);
466
467
nsNavHistory* history = nsNavHistory::GetHistoryService();
468
NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
469
int64_t placeId;
470
nsAutoCString placeGuid;
471
nsresult rv = history->GetOrCreateIdForPage(aURI, &placeId, placeGuid);
472
NS_ENSURE_SUCCESS(rv, rv);
473
474
// Get the correct index for insertion. This also ensures the parent exists.
475
int32_t index, folderCount;
476
int64_t grandParentId;
477
nsAutoCString folderGuid;
478
rv = FetchFolderInfo(aFolder, &folderCount, folderGuid, &grandParentId);
479
NS_ENSURE_SUCCESS(rv, rv);
480
if (aIndex == nsINavBookmarksService::DEFAULT_INDEX ||
481
aIndex >= folderCount) {
482
index = folderCount;
483
} else {
484
index = aIndex;
485
// Create space for the insertion.
486
rv = AdjustIndices(aFolder, index, INT32_MAX, 1);
487
NS_ENSURE_SUCCESS(rv, rv);
488
}
489
490
*aNewBookmarkId = -1;
491
PRTime dateAdded = RoundedPRNow();
492
nsAutoCString guid(aGUID);
493
nsCString title;
494
TruncateTitle(aTitle, title);
495
496
rv = InsertBookmarkInDB(placeId, BOOKMARK, aFolder, index, title, dateAdded,
497
0, folderGuid, grandParentId, aURI, aSource,
498
aNewBookmarkId, guid);
499
NS_ENSURE_SUCCESS(rv, rv);
500
501
// If not a tag, recalculate frecency for this entry, since it changed.
502
int64_t tagsRootId = TagsRootId();
503
if (grandParentId != tagsRootId) {
504
rv = history->UpdateFrecency(placeId);
505
NS_ENSURE_SUCCESS(rv, rv);
506
}
507
508
rv = transaction.Commit();
509
NS_ENSURE_SUCCESS(rv, rv);
510
511
if (mCanNotify) {
512
Sequence<OwningNonNull<PlacesEvent>> events;
513
nsAutoCString utf8spec;
514
aURI->GetSpec(utf8spec);
515
516
RefPtr<PlacesBookmarkAddition> bookmark = new PlacesBookmarkAddition();
517
bookmark->mItemType = TYPE_BOOKMARK;
518
bookmark->mId = *aNewBookmarkId;
519
bookmark->mParentId = aFolder;
520
bookmark->mIndex = index;
521
bookmark->mUrl.Assign(NS_ConvertUTF8toUTF16(utf8spec));
522
bookmark->mTitle.Assign(NS_ConvertUTF8toUTF16(title));
523
bookmark->mDateAdded = dateAdded / 1000;
524
bookmark->mGuid.Assign(guid);
525
bookmark->mParentGuid.Assign(folderGuid);
526
bookmark->mSource = aSource;
527
bookmark->mIsTagging = grandParentId == mDB->GetTagsFolderId();
528
bool success = !!events.AppendElement(bookmark.forget(), fallible);
529
MOZ_RELEASE_ASSERT(success);
530
531
PlacesObservers::NotifyListeners(events);
532
}
533
534
// If the bookmark has been added to a tag container, notify all
535
// bookmark-folder result nodes which contain a bookmark for the new
536
// bookmark's url.
537
if (grandParentId == tagsRootId) {
538
// Notify a tags change to all bookmarks for this URI.
539
nsTArray<BookmarkData> bookmarks;
540
rv = GetBookmarksForURI(aURI, bookmarks);
541
NS_ENSURE_SUCCESS(rv, rv);
542
543
for (uint32_t i = 0; i < bookmarks.Length(); ++i) {
544
// Check that bookmarks doesn't include the current tag itemId.
545
MOZ_ASSERT(bookmarks[i].id != *aNewBookmarkId);
546
547
NOTIFY_BOOKMARKS_OBSERVERS(
548
mCanNotify, mObservers, DontSkip,
549
OnItemChanged(bookmarks[i].id, NS_LITERAL_CSTRING("tags"), false,
550
EmptyCString(), bookmarks[i].lastModified,
551
TYPE_BOOKMARK, bookmarks[i].parentId, bookmarks[i].guid,
552
bookmarks[i].parentGuid, EmptyCString(), aSource));
553
}
554
}
555
556
return NS_OK;
557
}
558
559
NS_IMETHODIMP
560
nsNavBookmarks::RemoveItem(int64_t aItemId, uint16_t aSource) {
561
AUTO_PROFILER_LABEL("nsNavBookmarks::RemoveItem", OTHER);
562
563
NS_ENSURE_ARG(!IsRoot(aItemId));
564
565
BookmarkData bookmark;
566
nsresult rv = FetchItemInfo(aItemId, bookmark);
567
NS_ENSURE_SUCCESS(rv, rv);
568
569
mozStorageTransaction transaction(mDB->MainConn(), false);
570
571
// First, if not a tag, remove item annotations.
572
int64_t tagsRootId = TagsRootId();
573
bool isUntagging = bookmark.grandParentId == tagsRootId;
574
if (bookmark.parentId != tagsRootId && !isUntagging) {
575
nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
576
NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY);
577
rv = annosvc->RemoveItemAnnotations(bookmark.id);
578
NS_ENSURE_SUCCESS(rv, rv);
579
}
580
581
if (bookmark.type == TYPE_FOLDER) {
582
// Remove all of the folder's children.
583
rv = RemoveFolderChildren(bookmark.id, aSource);
584
NS_ENSURE_SUCCESS(rv, rv);
585
}
586
587
nsCOMPtr<mozIStorageStatement> stmt =
588
mDB->GetStatement("DELETE FROM moz_bookmarks WHERE id = :item_id");
589
NS_ENSURE_STATE(stmt);
590
mozStorageStatementScoper scoper(stmt);
591
592
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), bookmark.id);
593
NS_ENSURE_SUCCESS(rv, rv);
594
rv = stmt->Execute();
595
NS_ENSURE_SUCCESS(rv, rv);
596
597
// Fix indices in the parent.
598
if (bookmark.position != DEFAULT_INDEX) {
599
rv = AdjustIndices(bookmark.parentId, bookmark.position + 1, INT32_MAX, -1);
600
NS_ENSURE_SUCCESS(rv, rv);
601
}
602
603
int64_t syncChangeDelta = DetermineSyncChangeDelta(aSource);
604
605
// Add a tombstone for synced items.
606
if (syncChangeDelta) {
607
rv = InsertTombstone(bookmark);
608
NS_ENSURE_SUCCESS(rv, rv);
609
}
610
611
bookmark.lastModified = RoundedPRNow();
612
rv = SetItemDateInternal(LAST_MODIFIED, syncChangeDelta, bookmark.parentId,
613
bookmark.lastModified);
614
NS_ENSURE_SUCCESS(rv, rv);
615
616
// Mark all affected separators as changed
617
rv = AdjustSeparatorsSyncCounter(bookmark.parentId, bookmark.position,
618
syncChangeDelta);
619
NS_ENSURE_SUCCESS(rv, rv);
620
621
if (isUntagging) {
622
// If we're removing a tag, increment the change counter for all bookmarks
623
// with the URI.
624
rv = AddSyncChangesForBookmarksWithURL(bookmark.url, syncChangeDelta);
625
NS_ENSURE_SUCCESS(rv, rv);
626
}
627
628
rv = transaction.Commit();
629
NS_ENSURE_SUCCESS(rv, rv);
630
631
nsCOMPtr<nsIURI> uri;
632
if (bookmark.type == TYPE_BOOKMARK) {
633
// If not a tag, recalculate frecency for this entry, since it changed.
634
if (bookmark.grandParentId != tagsRootId) {
635
nsNavHistory* history = nsNavHistory::GetHistoryService();
636
NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
637
rv = history->UpdateFrecency(bookmark.placeId);
638
NS_ENSURE_SUCCESS(rv, rv);
639
}
640
// A broken url should not interrupt the removal process.
641
(void)NS_NewURI(getter_AddRefs(uri), bookmark.url);
642
// We cannot assert since some automated tests are checking this path.
643
NS_WARNING_ASSERTION(uri, "Invalid URI in RemoveItem");
644
}
645
646
NOTIFY_BOOKMARKS_OBSERVERS(
647
mCanNotify, mObservers,
648
SKIP_TAGS(bookmark.parentId == tagsRootId ||
649
bookmark.grandParentId == tagsRootId),
650
OnItemRemoved(bookmark.id, bookmark.parentId, bookmark.position,
651
bookmark.type, uri, bookmark.guid, bookmark.parentGuid,
652
aSource));
653
654
if (bookmark.type == TYPE_BOOKMARK && bookmark.grandParentId == tagsRootId &&
655
uri) {
656
// If the removed bookmark was child of a tag container, notify a tags
657
// change to all bookmarks for this URI.
658
nsTArray<BookmarkData> bookmarks;
659
rv = GetBookmarksForURI(uri, bookmarks);
660
NS_ENSURE_SUCCESS(rv, rv);
661
662
for (uint32_t i = 0; i < bookmarks.Length(); ++i) {
663
NOTIFY_BOOKMARKS_OBSERVERS(
664
mCanNotify, mObservers, DontSkip,
665
OnItemChanged(bookmarks[i].id, NS_LITERAL_CSTRING("tags"), false,
666
EmptyCString(), bookmarks[i].lastModified,
667
TYPE_BOOKMARK, bookmarks[i].parentId, bookmarks[i].guid,
668
bookmarks[i].parentGuid, EmptyCString(), aSource));
669
}
670
}
671
672
return NS_OK;
673
}
674
675
NS_IMETHODIMP
676
nsNavBookmarks::CreateFolder(int64_t aParent, const nsACString& aTitle,
677
int32_t aIndex, const nsACString& aGUID,
678
uint16_t aSource, int64_t* aNewFolderId) {
679
// NOTE: aParent can be null for root creation, so not checked
680
NS_ENSURE_ARG_POINTER(aNewFolderId);
681
NS_ENSURE_ARG_MIN(aIndex, nsINavBookmarksService::DEFAULT_INDEX);
682
if (!aGUID.IsEmpty() && !IsValidGUID(aGUID)) return NS_ERROR_INVALID_ARG;
683
684
// Get the correct index for insertion. This also ensures the parent exists.
685
int32_t index = aIndex, folderCount;
686
int64_t grandParentId;
687
nsAutoCString folderGuid;
688
nsresult rv =
689
FetchFolderInfo(aParent, &folderCount, folderGuid, &grandParentId);
690
NS_ENSURE_SUCCESS(rv, rv);
691
692
mozStorageTransaction transaction(mDB->MainConn(), false);
693
694
if (aIndex == nsINavBookmarksService::DEFAULT_INDEX ||
695
aIndex >= folderCount) {
696
index = folderCount;
697
} else {
698
// Create space for the insertion.
699
rv = AdjustIndices(aParent, index, INT32_MAX, 1);
700
NS_ENSURE_SUCCESS(rv, rv);
701
}
702
703
*aNewFolderId = -1;
704
PRTime dateAdded = RoundedPRNow();
705
nsAutoCString guid(aGUID);
706
nsCString title;
707
TruncateTitle(aTitle, title);
708
709
rv = InsertBookmarkInDB(-1, FOLDER, aParent, index, title, dateAdded, 0,
710
folderGuid, grandParentId, nullptr, aSource,
711
aNewFolderId, guid);
712
NS_ENSURE_SUCCESS(rv, rv);
713
714
rv = transaction.Commit();
715
NS_ENSURE_SUCCESS(rv, rv);
716
717
int64_t tagsRootId = TagsRootId();
718
719
if (mCanNotify) {
720
Sequence<OwningNonNull<PlacesEvent>> events;
721
RefPtr<PlacesBookmarkAddition> folder = new PlacesBookmarkAddition();
722
folder->mItemType = TYPE_FOLDER;
723
folder->mId = *aNewFolderId;
724
folder->mParentId = aParent;
725
folder->mIndex = index;
726
folder->mTitle.Assign(NS_ConvertUTF8toUTF16(title));
727
folder->mDateAdded = dateAdded / 1000;
728
folder->mGuid.Assign(guid);
729
folder->mParentGuid.Assign(folderGuid);
730
folder->mSource = aSource;
731
folder->mIsTagging = aParent == tagsRootId;
732
bool success = !!events.AppendElement(folder.forget(), fallible);
733
MOZ_RELEASE_ASSERT(success);
734
735
PlacesObservers::NotifyListeners(events);
736
}
737
738
return NS_OK;
739
}
740
741
nsresult nsNavBookmarks::GetDescendantChildren(
742
int64_t aFolderId, const nsACString& aFolderGuid, int64_t aGrandParentId,
743
nsTArray<BookmarkData>& aFolderChildrenArray) {
744
// New children will be added from this index on.
745
uint32_t startIndex = aFolderChildrenArray.Length();
746
nsresult rv;
747
{
748
// Collect children informations.
749
// Select all children of a given folder, sorted by position.
750
// This is a LEFT JOIN because not all bookmarks types have a place.
751
// We construct a result where the first columns exactly match
752
// kGetInfoIndex_* order, and additionally contains columns for position,
753
// item_child, and folder_child from moz_bookmarks.
754
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
755
"SELECT h.id, h.url, b.title, h.rev_host, h.visit_count, "
756
"h.last_visit_date, null, b.id, b.dateAdded, b.lastModified, "
757
"b.parent, null, h.frecency, h.hidden, h.guid, null, null, null, "
758
"b.guid, b.position, b.type, b.fk, b.syncStatus "
759
"FROM moz_bookmarks b "
760
"LEFT JOIN moz_places h ON b.fk = h.id "
761
"WHERE b.parent = :parent "
762
"ORDER BY b.position ASC");
763
NS_ENSURE_STATE(stmt);
764
mozStorageStatementScoper scoper(stmt);
765
766
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
767
NS_ENSURE_SUCCESS(rv, rv);
768
769
bool hasMore;
770
while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
771
BookmarkData child;
772
rv = stmt->GetInt64(nsNavHistory::kGetInfoIndex_ItemId, &child.id);
773
NS_ENSURE_SUCCESS(rv, rv);
774
child.parentId = aFolderId;
775
child.grandParentId = aGrandParentId;
776
child.parentGuid = aFolderGuid;
777
rv = stmt->GetInt32(kGetChildrenIndex_Type, &child.type);
778
NS_ENSURE_SUCCESS(rv, rv);
779
rv = stmt->GetInt64(kGetChildrenIndex_PlaceID, &child.placeId);
780
NS_ENSURE_SUCCESS(rv, rv);
781
rv = stmt->GetInt32(kGetChildrenIndex_Position, &child.position);
782
NS_ENSURE_SUCCESS(rv, rv);
783
rv = stmt->GetUTF8String(kGetChildrenIndex_Guid, child.guid);
784
NS_ENSURE_SUCCESS(rv, rv);
785
rv = stmt->GetInt32(kGetChildrenIndex_SyncStatus, &child.syncStatus);
786
NS_ENSURE_SUCCESS(rv, rv);
787
788
if (child.type == TYPE_BOOKMARK) {
789
rv = stmt->GetUTF8String(nsNavHistory::kGetInfoIndex_URL, child.url);
790
NS_ENSURE_SUCCESS(rv, rv);
791
}
792
793
// Append item to children's array.
794
aFolderChildrenArray.AppendElement(child);
795
}
796
}
797
798
// Recursively call GetDescendantChildren for added folders.
799
// We start at startIndex since previous folders are checked
800
// by previous calls to this method.
801
uint32_t childCount = aFolderChildrenArray.Length();
802
for (uint32_t i = startIndex; i < childCount; ++i) {
803
if (aFolderChildrenArray[i].type == TYPE_FOLDER) {
804
// nsTarray assumes that all children can be memmove()d, thus we can't
805
// just pass aFolderChildrenArray[i].guid to a method that will change
806
// the array itself. Otherwise, since it's passed by reference, after a
807
// memmove() it could point to garbage and cause intermittent crashes.
808
nsCString guid = aFolderChildrenArray[i].guid;
809
GetDescendantChildren(aFolderChildrenArray[i].id, guid, aFolderId,
810
aFolderChildrenArray);
811
}
812
}
813
814
return NS_OK;
815
}
816
817
nsresult nsNavBookmarks::RemoveFolderChildren(int64_t aFolderId,
818
uint16_t aSource) {
819
AUTO_PROFILER_LABEL("nsNavBookmarks::RemoveFolderChilder", OTHER);
820
821
NS_ENSURE_ARG_MIN(aFolderId, 1);
822
int64_t rootId = -1;
823
nsresult rv = GetPlacesRoot(&rootId);
824
NS_ENSURE_SUCCESS(rv, rv);
825
NS_ENSURE_ARG(aFolderId != rootId);
826
827
BookmarkData folder;
828
rv = FetchItemInfo(aFolderId, folder);
829
NS_ENSURE_SUCCESS(rv, rv);
830
NS_ENSURE_ARG(folder.type == TYPE_FOLDER);
831
832
// Fill folder children array recursively.
833
nsTArray<BookmarkData> folderChildrenArray;
834
rv = GetDescendantChildren(folder.id, folder.guid, folder.parentId,
835
folderChildrenArray);
836
NS_ENSURE_SUCCESS(rv, rv);
837
838
// Build a string of folders whose children will be removed.
839
nsCString foldersToRemove;
840
for (uint32_t i = 0; i < folderChildrenArray.Length(); ++i) {
841
BookmarkData& child = folderChildrenArray[i];
842
843
if (child.type == TYPE_FOLDER) {
844
foldersToRemove.Append(',');
845
foldersToRemove.AppendInt(child.id);
846
}
847
}
848
849
int64_t syncChangeDelta = DetermineSyncChangeDelta(aSource);
850
851
// Delete items from the database now.
852
mozStorageTransaction transaction(mDB->MainConn(), false);
853
854
nsCOMPtr<mozIStorageStatement> deleteStatement =
855
mDB->GetStatement(NS_LITERAL_CSTRING("DELETE FROM moz_bookmarks "
856
"WHERE parent IN (:parent") +
857
foldersToRemove + NS_LITERAL_CSTRING(")"));
858
NS_ENSURE_STATE(deleteStatement);
859
mozStorageStatementScoper deleteStatementScoper(deleteStatement);
860
861
rv =
862
deleteStatement->BindInt64ByName(NS_LITERAL_CSTRING("parent"), folder.id);
863
NS_ENSURE_SUCCESS(rv, rv);
864
rv = deleteStatement->Execute();
865
NS_ENSURE_SUCCESS(rv, rv);
866
867
// Clean up orphan items annotations.
868
nsCOMPtr<mozIStorageConnection> conn = mDB->MainConn();
869
if (!conn) {
870
return NS_ERROR_UNEXPECTED;
871
}
872
rv = conn->ExecuteSimpleSQL(
873
NS_LITERAL_CSTRING("DELETE FROM moz_items_annos "
874
"WHERE id IN ("
875
"SELECT a.id from moz_items_annos a "
876
"LEFT JOIN moz_bookmarks b ON a.item_id = b.id "
877
"WHERE b.id ISNULL)"));
878
NS_ENSURE_SUCCESS(rv, rv);
879
880
// Set the lastModified date.
881
rv = SetItemDateInternal(LAST_MODIFIED, syncChangeDelta, folder.id,
882
RoundedPRNow());
883
NS_ENSURE_SUCCESS(rv, rv);
884
885
int64_t tagsRootId = TagsRootId();
886
887
if (syncChangeDelta) {
888
nsTArray<TombstoneData> tombstones(folderChildrenArray.Length());
889
PRTime dateRemoved = RoundedPRNow();
890
891
for (uint32_t i = 0; i < folderChildrenArray.Length(); ++i) {
892
BookmarkData& child = folderChildrenArray[i];
893
if (NeedsTombstone(child)) {
894
// Write tombstones for synced children.
895
TombstoneData childTombstone = {child.guid, dateRemoved};
896
tombstones.AppendElement(childTombstone);
897
}
898
bool isUntagging = child.grandParentId == tagsRootId;
899
if (isUntagging) {
900
// Bump the change counter for all tagged bookmarks when removing a tag
901
// folder.
902
rv = AddSyncChangesForBookmarksWithURL(child.url, syncChangeDelta);
903
NS_ENSURE_SUCCESS(rv, rv);
904
}
905
}
906
907
rv = InsertTombstones(tombstones);
908
NS_ENSURE_SUCCESS(rv, rv);
909
}
910
911
rv = transaction.Commit();
912
NS_ENSURE_SUCCESS(rv, rv);
913
914
// Call observers in reverse order to serve children before their parent.
915
for (int32_t i = folderChildrenArray.Length() - 1; i >= 0; --i) {
916
BookmarkData& child = folderChildrenArray[i];
917
918
nsCOMPtr<nsIURI> uri;
919
if (child.type == TYPE_BOOKMARK) {
920
// If not a tag, recalculate frecency for this entry, since it changed.
921
if (child.grandParentId != tagsRootId) {
922
nsNavHistory* history = nsNavHistory::GetHistoryService();
923
NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
924
rv = history->UpdateFrecency(child.placeId);
925
NS_ENSURE_SUCCESS(rv, rv);
926
}
927
// A broken url should not interrupt the removal process.
928
(void)NS_NewURI(getter_AddRefs(uri), child.url);
929
// We cannot assert since some automated tests are checking this path.
930
NS_WARNING_ASSERTION(uri, "Invalid URI in RemoveFolderChildren");
931
}
932
933
NOTIFY_BOOKMARKS_OBSERVERS(
934
mCanNotify, mObservers,
935
((child.grandParentId == tagsRootId) ? SkipTags : SkipDescendants),
936
OnItemRemoved(child.id, child.parentId, child.position, child.type, uri,
937
child.guid, child.parentGuid, aSource));
938
939
if (child.type == TYPE_BOOKMARK && child.grandParentId == tagsRootId &&
940
uri) {
941
// If the removed bookmark was a child of a tag container, notify all
942
// bookmark-folder result nodes which contain a bookmark for the removed
943
// bookmark's url.
944
nsTArray<BookmarkData> bookmarks;
945
rv = GetBookmarksForURI(uri, bookmarks);
946
NS_ENSURE_SUCCESS(rv, rv);
947
948
for (uint32_t i = 0; i < bookmarks.Length(); ++i) {
949
NOTIFY_BOOKMARKS_OBSERVERS(
950
mCanNotify, mObservers, DontSkip,
951
OnItemChanged(bookmarks[i].id, NS_LITERAL_CSTRING("tags"), false,
952
EmptyCString(), bookmarks[i].lastModified,
953
TYPE_BOOKMARK, bookmarks[i].parentId,
954
bookmarks[i].guid, bookmarks[i].parentGuid,
955
EmptyCString(), aSource));
956
}
957
}
958
}
959
960
return NS_OK;
961
}
962
963
nsresult nsNavBookmarks::FetchItemInfo(int64_t aItemId,
964
BookmarkData& _bookmark) {
965
// LEFT JOIN since not all bookmarks have an associated place.
966
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
967
"SELECT b.id, h.url, b.title, b.position, b.fk, b.parent, b.type, "
968
"b.dateAdded, b.lastModified, b.guid, t.guid, t.parent, "
969
"b.syncStatus "
970
"FROM moz_bookmarks b "
971
"LEFT JOIN moz_bookmarks t ON t.id = b.parent "
972
"LEFT JOIN moz_places h ON h.id = b.fk "
973
"WHERE b.id = :item_id");
974
NS_ENSURE_STATE(stmt);
975
mozStorageStatementScoper scoper(stmt);
976
977
nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
978
NS_ENSURE_SUCCESS(rv, rv);
979
980
bool hasResult;
981
rv = stmt->ExecuteStep(&hasResult);
982
NS_ENSURE_SUCCESS(rv, rv);
983
if (!hasResult) {
984
return NS_ERROR_INVALID_ARG;
985
}
986
987
_bookmark.id = aItemId;
988
rv = stmt->GetUTF8String(1, _bookmark.url);
989
NS_ENSURE_SUCCESS(rv, rv);
990
991
bool isNull;
992
rv = stmt->GetIsNull(2, &isNull);
993
NS_ENSURE_SUCCESS(rv, rv);
994
if (!isNull) {
995
rv = stmt->GetUTF8String(2, _bookmark.title);
996
NS_ENSURE_SUCCESS(rv, rv);
997
}
998
rv = stmt->GetInt32(3, &_bookmark.position);
999
NS_ENSURE_SUCCESS(rv, rv);
1000
rv = stmt->GetInt64(4, &_bookmark.placeId);
1001
NS_ENSURE_SUCCESS(rv, rv);
1002
rv = stmt->GetInt64(5, &_bookmark.parentId);
1003
NS_ENSURE_SUCCESS(rv, rv);
1004
rv = stmt->GetInt32(6, &_bookmark.type);
1005
NS_ENSURE_SUCCESS(rv, rv);
1006
rv = stmt->GetInt64(7, reinterpret_cast<int64_t*>(&_bookmark.dateAdded));
1007
NS_ENSURE_SUCCESS(rv, rv);
1008
rv = stmt->GetInt64(8, reinterpret_cast<int64_t*>(&_bookmark.lastModified));
1009
NS_ENSURE_SUCCESS(rv, rv);
1010
rv = stmt->GetUTF8String(9, _bookmark.guid);
1011
NS_ENSURE_SUCCESS(rv, rv);
1012
// Getting properties of the root would show no parent.
1013
rv = stmt->GetIsNull(10, &isNull);
1014
NS_ENSURE_SUCCESS(rv, rv);
1015
if (!isNull) {
1016
rv = stmt->GetUTF8String(10, _bookmark.parentGuid);
1017
NS_ENSURE_SUCCESS(rv, rv);
1018
rv = stmt->GetInt64(11, &_bookmark.grandParentId);
1019
NS_ENSURE_SUCCESS(rv, rv);
1020
} else {
1021
_bookmark.grandParentId = -1;
1022
}
1023
rv = stmt->GetInt32(12, &_bookmark.syncStatus);
1024
NS_ENSURE_SUCCESS(rv, rv);
1025
1026
return NS_OK;
1027
}
1028
1029
nsresult nsNavBookmarks::FetchItemInfo(const nsCString& aGUID,
1030
BookmarkData& _bookmark) {
1031
// LEFT JOIN since not all bookmarks have an associated place.
1032
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
1033
"SELECT b.id, h.url, b.title, b.position, b.fk, b.parent, b.type, "
1034
"b.dateAdded, b.lastModified, t.guid, t.parent, "
1035
"b.syncStatus "
1036
"FROM moz_bookmarks b "
1037
"LEFT JOIN moz_bookmarks t ON t.id = b.parent "
1038
"LEFT JOIN moz_places h ON h.id = b.fk "
1039
"WHERE b.guid = :item_guid");
1040
NS_ENSURE_STATE(stmt);
1041
mozStorageStatementScoper scoper(stmt);
1042
1043
nsresult rv =
1044
stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("item_guid"), aGUID);
1045
NS_ENSURE_SUCCESS(rv, rv);
1046
1047
_bookmark.guid = aGUID;
1048
1049
bool hasResult;
1050
rv = stmt->ExecuteStep(&hasResult);
1051
NS_ENSURE_SUCCESS(rv, rv);
1052
if (!hasResult) {
1053
return NS_ERROR_INVALID_ARG;
1054
}
1055
1056
rv = stmt->GetInt64(0, &_bookmark.id);
1057
NS_ENSURE_SUCCESS(rv, rv);
1058
1059
rv = stmt->GetUTF8String(1, _bookmark.url);
1060
NS_ENSURE_SUCCESS(rv, rv);
1061
1062
bool isNull;
1063
rv = stmt->GetIsNull(2, &isNull);
1064
NS_ENSURE_SUCCESS(rv, rv);
1065
if (!isNull) {
1066
rv = stmt->GetUTF8String(2, _bookmark.title);
1067
NS_ENSURE_SUCCESS(rv, rv);
1068
}
1069
rv = stmt->GetInt32(3, &_bookmark.position);
1070
NS_ENSURE_SUCCESS(rv, rv);
1071
rv = stmt->GetInt64(4, &_bookmark.placeId);
1072
NS_ENSURE_SUCCESS(rv, rv);
1073
rv = stmt->GetInt64(5, &_bookmark.parentId);
1074
NS_ENSURE_SUCCESS(rv, rv);
1075
rv = stmt->GetInt32(6, &_bookmark.type);
1076
NS_ENSURE_SUCCESS(rv, rv);
1077
rv = stmt->GetInt64(7, reinterpret_cast<int64_t*>(&_bookmark.dateAdded));
1078
NS_ENSURE_SUCCESS(rv, rv);
1079
rv = stmt->GetInt64(8, reinterpret_cast<int64_t*>(&_bookmark.lastModified));
1080
NS_ENSURE_SUCCESS(rv, rv);
1081
// Getting properties of the root would show no parent.
1082
rv = stmt->GetIsNull(9, &isNull);
1083
NS_ENSURE_SUCCESS(rv, rv);
1084
if (!isNull) {
1085
rv = stmt->GetUTF8String(9, _bookmark.parentGuid);
1086
NS_ENSURE_SUCCESS(rv, rv);
1087
rv = stmt->GetInt64(10, &_bookmark.grandParentId);
1088
NS_ENSURE_SUCCESS(rv, rv);
1089
} else {
1090
_bookmark.grandParentId = -1;
1091
}
1092
rv = stmt->GetInt32(11, &_bookmark.syncStatus);
1093
NS_ENSURE_SUCCESS(rv, rv);
1094
1095
return NS_OK;
1096
}
1097
1098
nsresult nsNavBookmarks::SetItemDateInternal(enum BookmarkDate aDateType,
1099
int64_t aSyncChangeDelta,
1100
int64_t aItemId, PRTime aValue) {
1101
aValue = RoundToMilliseconds(aValue);
1102
1103
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
1104
"UPDATE moz_bookmarks SET lastModified = :date, "
1105
"syncChangeCounter = syncChangeCounter + :delta "
1106
"WHERE id = :item_id");
1107
NS_ENSURE_STATE(stmt);
1108
mozStorageStatementScoper scoper(stmt);
1109
1110
nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("date"), aValue);
1111
NS_ENSURE_SUCCESS(rv, rv);
1112
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
1113
NS_ENSURE_SUCCESS(rv, rv);
1114
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("delta"), aSyncChangeDelta);
1115
NS_ENSURE_SUCCESS(rv, rv);
1116
1117
rv = stmt->Execute();
1118
NS_ENSURE_SUCCESS(rv, rv);
1119
1120
// note, we are not notifying the observers
1121
// that the item has changed.
1122
1123
return NS_OK;
1124
}
1125
1126
NS_IMETHODIMP
1127
nsNavBookmarks::SetItemLastModified(int64_t aItemId, PRTime aLastModified,
1128
uint16_t aSource) {
1129
NS_ENSURE_ARG_MIN(aItemId, 1);
1130
1131
BookmarkData bookmark;
1132
nsresult rv = FetchItemInfo(aItemId, bookmark);
1133
NS_ENSURE_SUCCESS(rv, rv);
1134
1135
int64_t tagsRootId = TagsRootId();
1136
bool isTagging = bookmark.grandParentId == tagsRootId;
1137
int64_t syncChangeDelta = DetermineSyncChangeDelta(aSource);
1138
1139
// Round here so that we notify with the right value.
1140
bookmark.lastModified = RoundToMilliseconds(aLastModified);
1141
1142
if (isTagging) {
1143
// If we're changing a tag, bump the change counter for all tagged
1144
// bookmarks. We use a separate code path to avoid a transaction for
1145
// non-tags.
1146
mozStorageTransaction transaction(mDB->MainConn(), false);
1147
1148
rv = SetItemDateInternal(LAST_MODIFIED, syncChangeDelta, bookmark.id,
1149
bookmark.lastModified);
1150
NS_ENSURE_SUCCESS(rv, rv);
1151
1152
rv = AddSyncChangesForBookmarksWithURL(bookmark.url, syncChangeDelta);
1153
NS_ENSURE_SUCCESS(rv, rv);
1154
1155
rv = transaction.Commit();
1156
NS_ENSURE_SUCCESS(rv, rv);
1157
} else {
1158
rv = SetItemDateInternal(LAST_MODIFIED, syncChangeDelta, bookmark.id,
1159
bookmark.lastModified);
1160
NS_ENSURE_SUCCESS(rv, rv);
1161
}
1162
1163
// Note: mDBSetItemDateAdded also sets lastModified to aDateAdded.
1164
NOTIFY_BOOKMARKS_OBSERVERS(
1165
mCanNotify, mObservers,
1166
SKIP_TAGS(isTagging || bookmark.parentId == tagsRootId),
1167
OnItemChanged(bookmark.id, NS_LITERAL_CSTRING("lastModified"), false,
1168
nsPrintfCString("%" PRId64, bookmark.lastModified),
1169
bookmark.lastModified, bookmark.type, bookmark.parentId,
1170
bookmark.guid, bookmark.parentGuid, EmptyCString(),
1171
aSource));
1172
return NS_OK;
1173
}
1174
1175
nsresult nsNavBookmarks::AddSyncChangesForBookmarksWithURL(
1176
const nsACString& aURL, int64_t aSyncChangeDelta) {
1177
if (!aSyncChangeDelta) {
1178
return NS_OK;
1179
}
1180
nsCOMPtr<nsIURI> uri;
1181
nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL);
1182
if (NS_WARN_IF(NS_FAILED(rv))) {
1183
// Ignore sync changes for invalid URLs.
1184
return NS_OK;
1185
}
1186
return AddSyncChangesForBookmarksWithURI(uri, aSyncChangeDelta);
1187
}
1188
1189
nsresult nsNavBookmarks::AddSyncChangesForBookmarksWithURI(
1190
nsIURI* aURI, int64_t aSyncChangeDelta) {
1191
if (NS_WARN_IF(!aURI) || !aSyncChangeDelta) {
1192
// Ignore sync changes for invalid URIs.
1193
return NS_OK;
1194
}
1195
1196
nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement(
1197
"UPDATE moz_bookmarks SET "
1198
"syncChangeCounter = syncChangeCounter + :delta "
1199
"WHERE type = :type AND "
1200
"fk = (SELECT id FROM moz_places WHERE url_hash = hash(:url) AND "
1201
"url = :url)");
1202
NS_ENSURE_STATE(statement);
1203
mozStorageStatementScoper scoper(statement);
1204
1205
nsresult rv =
1206
statement->BindInt64ByName(NS_LITERAL_CSTRING("delta"), aSyncChangeDelta);
1207
NS_ENSURE_SUCCESS(rv, rv);
1208
rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("type"),
1209
nsINavBookmarksService::TYPE_BOOKMARK);
1210
NS_ENSURE_SUCCESS(rv, rv);
1211
rv = URIBinder::Bind(statement, NS_LITERAL_CSTRING("url"), aURI);
1212
NS_ENSURE_SUCCESS(rv, rv);
1213
1214
return statement->Execute();
1215
}
1216
1217
nsresult nsNavBookmarks::AddSyncChangesForBookmarksInFolder(
1218
int64_t aFolderId, int64_t aSyncChangeDelta) {
1219
if (!aSyncChangeDelta) {
1220
return NS_OK;
1221
}
1222
1223
nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement(
1224
"UPDATE moz_bookmarks SET "
1225
"syncChangeCounter = syncChangeCounter + :delta "
1226
"WHERE type = :type AND "
1227
"fk = (SELECT fk FROM moz_bookmarks WHERE parent = :parent)");
1228
NS_ENSURE_STATE(statement);
1229
mozStorageStatementScoper scoper(statement);
1230
1231
nsresult rv =
1232
statement->BindInt64ByName(NS_LITERAL_CSTRING("delta"), aSyncChangeDelta);
1233
NS_ENSURE_SUCCESS(rv, rv);
1234
rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("type"),
1235
nsINavBookmarksService::TYPE_BOOKMARK);
1236
NS_ENSURE_SUCCESS(rv, rv);
1237
rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
1238
NS_ENSURE_SUCCESS(rv, rv);
1239
1240
rv = statement->Execute();
1241
NS_ENSURE_SUCCESS(rv, rv);
1242
1243
return NS_OK;
1244
}
1245
1246
nsresult nsNavBookmarks::InsertTombstone(const BookmarkData& aBookmark) {
1247
if (!NeedsTombstone(aBookmark)) {
1248
return NS_OK;
1249
}
1250
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
1251
"INSERT INTO moz_bookmarks_deleted (guid, dateRemoved) "
1252
"VALUES (:guid, :date_removed)");
1253
NS_ENSURE_STATE(stmt);
1254
mozStorageStatementScoper scoper(stmt);
1255
1256
nsresult rv =
1257
stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aBookmark.guid);
1258
NS_ENSURE_SUCCESS(rv, rv);
1259
rv =
1260
stmt->BindInt64ByName(NS_LITERAL_CSTRING("date_removed"), RoundedPRNow());
1261
NS_ENSURE_SUCCESS(rv, rv);
1262
1263
rv = stmt->Execute();
1264
NS_ENSURE_SUCCESS(rv, rv);
1265
1266
return NS_OK;
1267
}
1268
1269
nsresult nsNavBookmarks::InsertTombstones(
1270
const nsTArray<TombstoneData>& aTombstones) {
1271
if (aTombstones.IsEmpty()) {
1272
return NS_OK;
1273
}
1274
1275
nsCOMPtr<mozIStorageConnection> conn = mDB->MainConn();
1276
NS_ENSURE_STATE(conn);
1277
1278
int32_t variableLimit = 0;
1279
nsresult rv = conn->GetVariableLimit(&variableLimit);
1280
NS_ENSURE_SUCCESS(rv, rv);
1281
1282
size_t maxRowsPerChunk = variableLimit / 2;
1283
for (uint32_t startIndex = 0; startIndex < aTombstones.Length();
1284
startIndex += maxRowsPerChunk) {
1285
size_t rowsPerChunk =
1286
std::min(maxRowsPerChunk, aTombstones.Length() - startIndex);
1287
1288
// Build a query to insert all tombstones in a single statement, chunking to
1289
// avoid the SQLite bound parameter limit.
1290
nsAutoCString tombstonesToInsert;
1291
tombstonesToInsert.AppendLiteral("VALUES (?, ?)");
1292
for (uint32_t i = 1; i < rowsPerChunk; ++i) {
1293
tombstonesToInsert.AppendLiteral(", (?, ?)");
1294
}
1295
#ifdef DEBUG
1296
MOZ_ASSERT(tombstonesToInsert.CountChar('?') == rowsPerChunk * 2,
1297
"Expected one binding param per column for each tombstone");
1298
#endif
1299
1300
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
1301
NS_LITERAL_CSTRING("INSERT INTO moz_bookmarks_deleted "
1302
"(guid, dateRemoved) ") +
1303
tombstonesToInsert);
1304
NS_ENSURE_STATE(stmt);
1305
mozStorageStatementScoper scoper(stmt);
1306
1307
uint32_t paramIndex = 0;
1308
for (uint32_t i = 0; i < rowsPerChunk; ++i) {
1309
const TombstoneData& tombstone = aTombstones[startIndex + i];
1310
rv = stmt->BindUTF8StringByIndex(paramIndex++, tombstone.guid);
1311
NS_ENSURE_SUCCESS(rv, rv);
1312
rv = stmt->BindInt64ByIndex(paramIndex++, tombstone.dateRemoved);
1313
NS_ENSURE_SUCCESS(rv, rv);
1314
}
1315
1316
rv = stmt->Execute();
1317
NS_ENSURE_SUCCESS(rv, rv);
1318
}
1319
1320
return NS_OK;
1321
}
1322
1323
nsresult nsNavBookmarks::RemoveTombstone(const nsACString& aGUID) {
1324
nsCOMPtr<mozIStorageStatement> stmt =
1325
mDB->GetStatement("DELETE FROM moz_bookmarks_deleted WHERE guid = :guid");
1326
NS_ENSURE_STATE(stmt);
1327
mozStorageStatementScoper scoper(stmt);
1328
1329
nsresult rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aGUID);
1330
NS_ENSURE_SUCCESS(rv, rv);
1331
1332
return stmt->Execute();
1333
}
1334
1335
NS_IMETHODIMP
1336
nsNavBookmarks::SetItemTitle(int64_t aItemId, const nsACString& aTitle,
1337
uint16_t aSource) {
1338
NS_ENSURE_ARG_MIN(aItemId, 1);
1339
1340
BookmarkData bookmark;
1341
nsresult rv = FetchItemInfo(aItemId, bookmark);
1342
NS_ENSURE_SUCCESS(rv, rv);
1343
1344
int64_t tagsRootId = TagsRootId();
1345
bool isChangingTagFolder = bookmark.parentId == tagsRootId;
1346
int64_t syncChangeDelta = DetermineSyncChangeDelta(aSource);
1347
1348
nsAutoCString title;
1349
TruncateTitle(aTitle, title);
1350
1351
if (isChangingTagFolder) {
1352
// If we're changing the title of a tag folder, bump the change counter
1353
// for all tagged bookmarks. We use a separate code path to avoid a
1354
// transaction for non-tags.
1355
mozStorageTransaction transaction(mDB->MainConn(), false);
1356
1357
rv = SetItemTitleInternal(bookmark, title, syncChangeDelta);
1358
NS_ENSURE_SUCCESS(rv, rv);
1359
1360
rv = AddSyncChangesForBookmarksInFolder(bookmark.id, syncChangeDelta);
1361
NS_ENSURE_SUCCESS(rv, rv);
1362
1363
rv = transaction.Commit();
1364
NS_ENSURE_SUCCESS(rv, rv);
1365
} else {
1366
rv = SetItemTitleInternal(bookmark, title, syncChangeDelta);
1367
NS_ENSURE_SUCCESS(rv, rv);
1368
}
1369
1370
NOTIFY_BOOKMARKS_OBSERVERS(
1371
mCanNotify, mObservers, SKIP_TAGS(isChangingTagFolder),
1372
OnItemChanged(bookmark.id, NS_LITERAL_CSTRING("title"), false, title,
1373
bookmark.lastModified, bookmark.type, bookmark.parentId,
1374
bookmark.guid, bookmark.parentGuid, EmptyCString(),
1375
aSource));
1376
return NS_OK;
1377
}
1378
1379
nsresult nsNavBookmarks::SetItemTitleInternal(BookmarkData& aBookmark,
1380
const nsACString& aTitle,
1381
int64_t aSyncChangeDelta) {
1382
nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement(
1383
"UPDATE moz_bookmarks SET "
1384
"title = :item_title, lastModified = :date, "
1385
"syncChangeCounter = syncChangeCounter + :delta "
1386
"WHERE id = :item_id");
1387
NS_ENSURE_STATE(statement);
1388
mozStorageStatementScoper scoper(statement);
1389
1390
nsresult rv;
1391
if (aTitle.IsEmpty()) {
1392
rv = statement->BindNullByName(NS_LITERAL_CSTRING("item_title"));
1393
} else {
1394
rv = statement->BindUTF8StringByName(NS_LITERAL_CSTRING("item_title"),
1395
aTitle);
1396
}
1397
NS_ENSURE_SUCCESS(rv, rv);
1398
aBookmark.lastModified = RoundToMilliseconds(RoundedPRNow());
1399
rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("date"),
1400
aBookmark.lastModified);
1401
NS_ENSURE_SUCCESS(rv, rv);
1402
rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aBookmark.id);
1403
NS_ENSURE_SUCCESS(rv, rv);
1404
rv =
1405
statement->BindInt64ByName(NS_LITERAL_CSTRING("delta"), aSyncChangeDelta);
1406
NS_ENSURE_SUCCESS(rv, rv);
1407
1408
rv = statement->Execute();
1409
NS_ENSURE_SUCCESS(rv, rv);
1410
1411
return NS_OK;
1412
}
1413
1414
NS_IMETHODIMP
1415
nsNavBookmarks::GetItemTitle(int64_t aItemId, nsACString& _title) {
1416
NS_ENSURE_ARG_MIN(aItemId, 1);
1417
1418
BookmarkData bookmark;
1419
nsresult rv = FetchItemInfo(aItemId, bookmark);
1420
NS_ENSURE_SUCCESS(rv, rv);
1421
1422
_title = bookmark.title;
1423
return NS_OK;
1424
}
1425
1426
nsresult nsNavBookmarks::GetBookmarkURI(int64_t aItemId, nsIURI** _URI) {
1427
NS_ENSURE_ARG_MIN(aItemId, 1);
1428
NS_ENSURE_ARG_POINTER(_URI);
1429
1430
BookmarkData bookmark;
1431
nsresult rv = FetchItemInfo(aItemId, bookmark);
1432
NS_ENSURE_SUCCESS(rv, rv);
1433
1434
rv = NS_NewURI(_URI, bookmark.url);
1435
NS_ENSURE_SUCCESS(rv, rv);
1436
1437
return NS_OK;
1438
}
1439
1440
nsresult nsNavBookmarks::ResultNodeForContainer(
1441
const nsCString& aGUID, nsNavHistoryQueryOptions* aOptions,
1442
nsNavHistoryResultNode** aNode) {
1443
BookmarkData bookmark;
1444
nsresult rv = FetchItemInfo(aGUID, bookmark);
1445
NS_ENSURE_SUCCESS(rv, rv);
1446
1447
if (bookmark.type == TYPE_FOLDER) { // TYPE_FOLDER
1448
*aNode =
1449
new nsNavHistoryFolderResultNode(bookmark.title, aOptions, bookmark.id);
1450
} else {
1451
return NS_ERROR_INVALID_ARG;
1452
}
1453
1454
(*aNode)->mDateAdded = bookmark.dateAdded;
1455
(*aNode)->mLastModified = bookmark.lastModified;
1456
(*aNode)->mBookmarkGuid = bookmark.guid;
1457
(*aNode)->GetAsFolder()->mTargetFolderGuid = bookmark.guid;
1458
1459
NS_ADDREF(*aNode);
1460
return NS_OK;
1461
}
1462
1463
nsresult nsNavBookmarks::QueryFolderChildren(
1464
int64_t aFolderId, nsNavHistoryQueryOptions* aOptions,
1465
nsCOMArray<nsNavHistoryResultNode>* aChildren) {
1466
NS_ENSURE_ARG_POINTER(aOptions);
1467
NS_ENSURE_ARG_POINTER(aChildren);
1468
1469
// Select all children of a given folder, sorted by position.
1470
// This is a LEFT JOIN because not all bookmarks types have a place.
1471
// We construct a result where the first columns exactly match those returned
1472
// by mDBGetURLPageInfo, and additionally contains columns for position,
1473
// item_child, and folder_child from moz_bookmarks.
1474
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
1475
"SELECT h.id, h.url, b.title, h.rev_host, h.visit_count, "
1476
"h.last_visit_date, null, b.id, b.dateAdded, b.lastModified, "
1477
"b.parent, null, h.frecency, h.hidden, h.guid, null, null, null, "
1478
"b.guid, b.position, b.type, b.fk "
1479
"FROM moz_bookmarks b "
1480
"LEFT JOIN moz_places h ON b.fk = h.id "
1481
"WHERE b.parent = :parent "
1482
"AND (NOT :excludeItems OR "
1483
"b.type = :folder OR "
1484
"h.url_hash BETWEEN hash('place', 'prefix_lo') AND hash('place', "
1485
"'prefix_hi')) "
1486
"ORDER BY b.position ASC");
1487
NS_ENSURE_STATE(stmt);
1488
mozStorageStatementScoper scoper(stmt);
1489
1490
nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
1491
NS_ENSURE_SUCCESS(rv, rv);
1492
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("folder"), TYPE_FOLDER);
1493
NS_ENSURE_SUCCESS(rv, rv);
1494
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("excludeItems"),
1495
aOptions->ExcludeItems());
1496
NS_ENSURE_SUCCESS(rv, rv);
1497
1498
nsCOMPtr<mozIStorageValueArray> row = do_QueryInterface(stmt, &rv);
1499
NS_ENSURE_SUCCESS(rv, rv);
1500
1501
int32_t index = -1;
1502
bool hasResult;
1503
while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
1504
rv = ProcessFolderNodeRow(row, aOptions, aChildren, index);
1505
NS_ENSURE_SUCCESS(rv, rv);
1506
}
1507
1508
return NS_OK;
1509
}
1510
1511
nsresult nsNavBookmarks::ProcessFolderNodeRow(
1512
mozIStorageValueArray* aRow, nsNavHistoryQueryOptions* aOptions,
1513
nsCOMArray<nsNavHistoryResultNode>* aChildren, int32_t& aCurrentIndex) {
1514
NS_ENSURE_ARG_POINTER(aRow);
1515
NS_ENSURE_ARG_POINTER(aOptions);
1516
NS_ENSURE_ARG_POINTER(aChildren);
1517
1518
// The results will be in order of aCurrentIndex. Even if we don't add a node
1519
// because it was excluded, we need to count its index, so do that before
1520
// doing anything else.
1521
aCurrentIndex++;
1522
1523
int32_t itemType;
1524
nsresult rv = aRow->GetInt32(kGetChildrenIndex_Type, &itemType);
1525
NS_ENSURE_SUCCESS(rv, rv);
1526
int64_t id;
1527
rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemId, &id);
1528
NS_ENSURE_SUCCESS(rv, rv);
1529
1530
RefPtr<nsNavHistoryResultNode> node;
1531
1532
if (itemType == TYPE_BOOKMARK) {
1533
nsNavHistory* history = nsNavHistory::GetHistoryService();
1534
NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
1535
rv = history->RowToResult(aRow, aOptions, getter_AddRefs(node));
1536
NS_ENSURE_SUCCESS(rv, rv);
1537
uint32_t nodeType;
1538
node->GetType(&nodeType);
1539
if (nodeType == nsINavHistoryResultNode::RESULT_TYPE_QUERY &&
1540
aOptions->ExcludeQueries()) {
1541
return NS_OK;
1542
}
1543
} else if (itemType == TYPE_FOLDER) {
1544
nsAutoCString title;
1545
bool isNull;
1546
rv = aRow->GetIsNull(nsNavHistory::kGetInfoIndex_Title, &isNull);
1547
NS_ENSURE_SUCCESS(rv, rv);
1548
if (!isNull) {
1549
rv = aRow->GetUTF8String(nsNavHistory::kGetInfoIndex_Title, title);
1550
NS_ENSURE_SUCCESS(rv, rv);
1551
}
1552
1553
// Don't use options from the parent to build the new folder node, it will
1554
// inherit those later when it's inserted in the result.
1555
node = new nsNavHistoryFolderResultNode(title,
1556
new nsNavHistoryQueryOptions(), id);
1557
1558
rv = aRow->GetUTF8String(kGetChildrenIndex_Guid, node->mBookmarkGuid);
1559
NS_ENSURE_SUCCESS(rv, rv);
1560
node->GetAsFolder()->mTargetFolderGuid = node->mBookmarkGuid;
1561
1562
rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemDateAdded,
1563
reinterpret_cast<int64_t*>(&node->mDateAdded));
1564
NS_ENSURE_SUCCESS(rv, rv);
1565
rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemLastModified,
1566
reinterpret_cast<int64_t*>(&node->mLastModified));
1567
NS_ENSURE_SUCCESS(rv, rv);
1568
} else {
1569
// This is a separator.
1570
node = new nsNavHistorySeparatorResultNode();
1571
1572
node->mItemId = id;
1573
rv = aRow->GetUTF8String(kGetChildrenIndex_Guid, node->mBookmarkGuid);
1574
NS_ENSURE_SUCCESS(rv, rv);
1575
rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemDateAdded,
1576
reinterpret_cast<int64_t*>(&node->mDateAdded));
1577
NS_ENSURE_SUCCESS(rv, rv);
1578
rv = aRow->GetInt64(nsNavHistory::kGetInfoIndex_ItemLastModified,
1579
reinterpret_cast<int64_t*>(&node->mLastModified));
1580
NS_ENSURE_SUCCESS(rv, rv);
1581
}
1582
1583
// Store the index of the node within this container. Note that this is not
1584
// moz_bookmarks.position.
1585
node->mBookmarkIndex = aCurrentIndex;
1586
1587
NS_ENSURE_TRUE(aChildren->AppendObject(node), NS_ERROR_OUT_OF_MEMORY);
1588
return NS_OK;
1589
}
1590
1591
nsresult nsNavBookmarks::QueryFolderChildrenAsync(
1592
nsNavHistoryFolderResultNode* aNode,
1593
mozIStoragePendingStatement** _pendingStmt) {
1594
NS_ENSURE_ARG_POINTER(aNode);
1595
NS_ENSURE_ARG_POINTER(_pendingStmt);
1596
1597
// Select all children of a given folder, sorted by position.
1598
// This is a LEFT JOIN because not all bookmarks types have a place.
1599
// We construct a result where the first columns exactly match those returned
1600
// by mDBGetURLPageInfo, and additionally contains columns for position,
1601
// item_child, and folder_child from moz_bookmarks.
1602
nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement(
1603
"SELECT h.id, h.url, b.title, h.rev_host, h.visit_count, "
1604
"h.last_visit_date, null, b.id, b.dateAdded, b.lastModified, "
1605
"b.parent, null, h.frecency, h.hidden, h.guid, null, null, null, "
1606
"b.guid, b.position, b.type, b.fk "
1607
"FROM moz_bookmarks b "
1608
"LEFT JOIN moz_places h ON b.fk = h.id "
1609
"WHERE b.parent = :parent "
1610
"AND (NOT :excludeItems OR "
1611
"b.type = :folder OR "
1612
"h.url_hash BETWEEN hash('place', 'prefix_lo') AND hash('place', "
1613
"'prefix_hi')) "
1614
"ORDER BY b.position ASC");
1615
NS_ENSURE_STATE(stmt);
1616
1617
nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"),
1618
aNode->mTargetFolderItemId);
1619
NS_ENSURE_SUCCESS(rv, rv);
1620
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("folder"), TYPE_FOLDER);
1621
NS_ENSURE_SUCCESS(rv, rv);
1622
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("excludeItems"),
1623
aNode->mOptions->ExcludeItems());
1624
NS_ENSURE_SUCCESS(rv, rv);
1625
1626
nsCOMPtr<mozIStoragePendingStatement> pendingStmt;
1627
rv = stmt->ExecuteAsync(aNode, getter_AddRefs(pendingStmt));
1628
NS_ENSURE_SUCCESS(rv, rv);
1629
1630
NS_IF_ADDREF(*_pendingStmt = pendingStmt);
1631
return NS_OK;
1632
}
1633
1634
nsresult nsNavBookmarks::FetchFolderInfo(int64_t aFolderId,
1635
int32_t* _folderCount,
1636
nsACString& _guid,
1637
int64_t* _parentId) {
1638
*_folderCount = 0;
1639
*_parentId = -1;
1640
1641
// This query has to always return results, so it can't be written as a join,
1642
// though a left join of 2 subqueries would have the same cost.
1643
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
1644
"SELECT count(*), "
1645
"(SELECT guid FROM moz_bookmarks WHERE id = :parent), "
1646
"(SELECT parent FROM moz_bookmarks WHERE id = :parent) "
1647
"FROM moz_bookmarks "
1648
"WHERE parent = :parent");
1649
NS_ENSURE_STATE(stmt);
1650
mozStorageStatementScoper scoper(stmt);
1651
1652
nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("parent"), aFolderId);
1653
NS_ENSURE_SUCCESS(rv, rv);
1654
1655
bool hasResult;
1656
rv = stmt->ExecuteStep(&hasResult);
1657
NS_ENSURE_SUCCESS(rv, rv);
1658
NS_ENSURE_TRUE(hasResult, NS_ERROR_UNEXPECTED);
1659
1660
// Ensure that the folder we are looking for exists.
1661
// Can't rely only on parent, since the root has parent 0, that doesn't exist.
1662
bool isNull;
1663
rv = stmt->GetIsNull(2, &isNull);
1664
NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && (!isNull || aFolderId == 0),
1665
NS_ERROR_INVALID_ARG);
1666
1667
rv = stmt->GetInt32(0, _folderCount);
1668
NS_ENSURE_SUCCESS(rv, rv);
1669
if (!isNull) {