Skip to content

Commit

Permalink
add Firefox Mobile Bookmarks import support
Browse files Browse the repository at this point in the history
  • Loading branch information
mallexxx committed Jan 16, 2024
1 parent 10f817c commit edb36b0
Show file tree
Hide file tree
Showing 10 changed files with 74 additions and 35 deletions.
3 changes: 3 additions & 0 deletions DuckDuckGo/Common/Localizables/UserText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,9 @@ struct UserText {
static let bookmarksManageBookmarks = NSLocalizedString("bookmarks.manage-bookmarks", value: "Manage Bookmarks", comment: "Menu item for opening the bookmarks management interface")
static let bookmarkImportedFromFolder = NSLocalizedString("bookmarks.imported.from.folder", value: "Imported from", comment: "Name of the folder the imported bookmarks are saved into")

static let otherBookmarksImportedFolderTitle = NSLocalizedString("bookmarks.imported.other.folder.title", value: "Other bookmarks", comment: "Name of the \"Other bookmarks\" folder imported from other browser")
static let mobileBookmarksImportedFolderTitle = NSLocalizedString("bookmarks.imported.mobile.folder.title", value: "Mobile bookmarks", comment: "Name of the \"Mobile bookmarks\" folder imported from other browser")

static let zoom = NSLocalizedString("zoom", value: "Zoom", comment: "Menu with Zooming commands")

static let emailOptionsMenuItem = NSLocalizedString("email.optionsMenu", value: "Email Protection", comment: "Menu item email feature")
Expand Down
15 changes: 10 additions & 5 deletions DuckDuckGo/DataImport/Bookmarks/Chromium/ImportedBookmarks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,23 @@ struct ImportedBookmarks: Codable, Equatable {

struct BookmarkOrFolder: Codable, Equatable {
let name: String
let type: String

enum EntityType: String, Codable {
case bookmark
case folder
}
let type: EntityType
let urlString: String?
var isDDGFavorite: Bool = false

let children: [BookmarkOrFolder]?

static func bookmark(name: String, urlString: String?, isDDGFavorite: Bool) -> BookmarkOrFolder {
.init(name: name, type: "bookmark", urlString: urlString, children: nil, isDDGFavorite: isDDGFavorite)
.init(name: name, type: .bookmark, urlString: urlString, children: nil, isDDGFavorite: isDDGFavorite)
}

static func folder(name: String, children: [BookmarkOrFolder]) -> BookmarkOrFolder {
.init(name: name, type: "folder", urlString: nil, children: children)
.init(name: name, type: .folder, urlString: nil, children: children)
}

var url: URL? {
Expand All @@ -45,7 +50,7 @@ struct ImportedBookmarks: Codable, Equatable {
}

var isFolder: Bool {
return type == "folder"
return type == .folder
}

fileprivate var numberOfBookmarks: Int {
Expand All @@ -62,7 +67,7 @@ struct ImportedBookmarks: Codable, Equatable {
case children
}

init(name: String, type: String, urlString: String?, children: [BookmarkOrFolder]?, isDDGFavorite: Bool = false) {
init(name: String, type: EntityType, urlString: String?, children: [BookmarkOrFolder]?, isDDGFavorite: Bool = false) {
self.name = name.trimmingWhitespace()
self.type = type
self.urlString = urlString
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ final class FirefoxBookmarksReader {
static let toolbar = "toolbar_____"
static let tags = "tags________"
static let unfiled = "unfiled_____"
static let mobile = "mobile______"
}

struct ImportError: DataImportError {
Expand Down Expand Up @@ -119,7 +120,7 @@ final class FirefoxBookmarksReader {
return importedBookmarks
}

fileprivate class DatabaseBookmarks {
fileprivate struct DatabaseBookmarks {
let topLevelFolders: [FolderRow]
let foldersByParent: [Int: [FolderRow]]
let bookmarksByFolder: [Int: [BookmarkRow]]
Expand Down Expand Up @@ -165,18 +166,24 @@ final class FirefoxBookmarksReader {
let menu = databaseBookmarks.topLevelFolders.first(where: { $0.guid == BookmarkGUID.menu })
let toolbar = databaseBookmarks.topLevelFolders.first(where: { $0.guid == BookmarkGUID.toolbar })
let unfiled = databaseBookmarks.topLevelFolders.first(where: { $0.guid == BookmarkGUID.unfiled })
let mobile = databaseBookmarks.topLevelFolders.first(where: { $0.guid == BookmarkGUID.mobile })
let filteredGUIDs = Set([BookmarkGUID.menu, BookmarkGUID.toolbar, BookmarkGUID.unfiled, BookmarkGUID.mobile, BookmarkGUID.tags])
let otherFolders = databaseBookmarks.topLevelFolders.filter { !filteredGUIDs.contains($0.guid) }

let menuBookmarksAndFolders = children(parentID: menu?.id, bookmarks: databaseBookmarks)
let toolbarBookmarksAndFolders = children(parentID: toolbar?.id, bookmarks: databaseBookmarks)
let syncedBookmarksAndFolders = children(parentID: mobile?.id, bookmarks: databaseBookmarks)
let unfiledBookmarksAndFolders = children(parentID: unfiled?.id, bookmarks: databaseBookmarks)
+ otherFolders.flatMap { children(parentID: $0.id, bookmarks: databaseBookmarks) }

let toolbarFolder = ImportedBookmarks.BookmarkOrFolder(name: "bar",
type: "folder",
type: .folder,
urlString: nil,
children: toolbarBookmarksAndFolders + menuBookmarksAndFolders)

let unfiledFolder = ImportedBookmarks.BookmarkOrFolder(name: "other", type: "folder", urlString: nil, children: unfiledBookmarksAndFolders)
let folders = ImportedBookmarks.TopLevelFolders(bookmarkBar: toolbarFolder, otherBookmarks: unfiledFolder, syncedBookmarks: nil)
let unfiledFolder = ImportedBookmarks.BookmarkOrFolder(name: UserText.otherBookmarksImportedFolderTitle, type: .folder, urlString: nil, children: unfiledBookmarksAndFolders)
let syncedFolder = ImportedBookmarks.BookmarkOrFolder(name: UserText.mobileBookmarksImportedFolderTitle, type: .folder, urlString: nil, children: syncedBookmarksAndFolders)
let folders = ImportedBookmarks.TopLevelFolders(bookmarkBar: toolbarFolder, otherBookmarks: unfiledFolder, syncedBookmarks: syncedFolder)

return ImportedBookmarks(topLevelFolders: folders)
}
Expand Down Expand Up @@ -258,11 +265,11 @@ extension ImportedBookmarks.BookmarkOrFolder {

fileprivate static func from(folderRow: FirefoxBookmarksReader.FolderRow,
children: [ImportedBookmarks.BookmarkOrFolder]) -> ImportedBookmarks.BookmarkOrFolder {
return ImportedBookmarks.BookmarkOrFolder(name: folderRow.title, type: "folder", urlString: nil, children: children)
return ImportedBookmarks.BookmarkOrFolder(name: folderRow.title, type: .folder, urlString: nil, children: children)
}

fileprivate static func from(bookmarkRow: FirefoxBookmarksReader.BookmarkRow) -> ImportedBookmarks.BookmarkOrFolder {
return ImportedBookmarks.BookmarkOrFolder(name: bookmarkRow.title ?? "", type: "bookmark", urlString: bookmarkRow.url, children: nil)
return ImportedBookmarks.BookmarkOrFolder(name: bookmarkRow.title ?? "", type: .bookmark, urlString: bookmarkRow.url, children: nil)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ final class BookmarkHTMLReader {
bookmarkBar = firstFolder
}

let otherBookmarks = ImportedBookmarks.BookmarkOrFolder.folder(name: "other", children: other)
let otherBookmarks = ImportedBookmarks.BookmarkOrFolder.folder(name: UserText.otherBookmarksImportedFolderTitle, children: other)
let allBookmarks = ImportedBookmarks(topLevelFolders: .init(bookmarkBar: bookmarkBar, otherBookmarks: otherBookmarks, syncedBookmarks: nil))
let result = HTMLImportedBookmarks(source: importSource, bookmarks: allBookmarks)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ final class SafariBookmarksReader {

}

let otherBookmarksFolder = ImportedBookmarks.BookmarkOrFolder(name: "other", type: "folder", urlString: nil, children: otherBookmarks)
let emptyFolder = ImportedBookmarks.BookmarkOrFolder(name: "bar", type: "folder", urlString: nil, children: [])
let otherBookmarksFolder = ImportedBookmarks.BookmarkOrFolder(name: UserText.otherBookmarksImportedFolderTitle, type: .folder, urlString: nil, children: otherBookmarks)
let emptyFolder = ImportedBookmarks.BookmarkOrFolder(name: "bar", type: .folder, urlString: nil, children: [])

let topLevelFolder = ImportedBookmarks.TopLevelFolders(bookmarkBar: bookmarksBar ?? emptyFolder, otherBookmarks: otherBookmarksFolder, syncedBookmarks: nil)
let importedBookmarks = ImportedBookmarks(topLevelFolders: topLevelFolder)
Expand All @@ -135,11 +135,11 @@ final class SafariBookmarksReader {
assert(entry[Constants.typeKey] as? String == Constants.listType)

let children = children.compactMap { bookmarkOrFolder(from: $0) }
return ImportedBookmarks.BookmarkOrFolder(name: title, type: "folder", urlString: nil, children: children)
return ImportedBookmarks.BookmarkOrFolder(name: title, type: .folder, urlString: nil, children: children)
} else if let url = entry[Constants.urlStringKey] as? String {
assert(entry[Constants.typeKey] as? String == Constants.leafType)

return ImportedBookmarks.BookmarkOrFolder(name: title, type: "bookmark", urlString: url, children: nil)
return ImportedBookmarks.BookmarkOrFolder(name: title, type: .bookmark, urlString: url, children: nil)
}

return nil
Expand Down
26 changes: 25 additions & 1 deletion DuckDuckGo/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -1455,6 +1455,30 @@
}
}
},
"bookmarks.imported.mobile.folder.title" : {
"comment" : "Name of the \"Mobile bookmarks\" folder imported from other browser",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Mobile bookmarks"
}
}
}
},
"bookmarks.imported.other.folder.title" : {
"comment" : "Name of the \"Other bookmarks\" folder imported from other browser",
"extractionState" : "extracted_with_value",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"value" : "Other bookmarks"
}
}
}
},
"bookmarks.manage" : {
"comment" : "Button for opening the bookmarks management interface",
"extractionState" : "extracted_with_value",
Expand Down Expand Up @@ -9336,4 +9360,4 @@
}
},
"version" : "1.0"
}
}
20 changes: 10 additions & 10 deletions UnitTests/Bookmarks/Services/LocalBookmarkStoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -933,11 +933,11 @@ final class LocalBookmarkStoreTests: XCTestCase {
let context = container.viewContext
let bookmarkStore = LocalBookmarkStore(context: context)

let bookmark = ImportedBookmarks.BookmarkOrFolder(name: "DuckDuckGo", type: "bookmark", urlString: "https://duckduckgo.com", children: nil)
let bookmarkBar = ImportedBookmarks.BookmarkOrFolder(name: "Bookmark Bar", type: "folder", urlString: nil, children: [bookmark])
let otherBookmarks = ImportedBookmarks.BookmarkOrFolder(name: "Other Bookmarks", type: "folder", urlString: nil, children: [])
let bookmark = ImportedBookmarks.BookmarkOrFolder(name: "DuckDuckGo", type: .bookmark, urlString: "https://duckduckgo.com", children: nil)
let bookmarkBar = ImportedBookmarks.BookmarkOrFolder(name: "Bookmark Bar", type: .folder, urlString: nil, children: [bookmark])
let otherBookmarks = ImportedBookmarks.BookmarkOrFolder(name: "Other Bookmarks", type: .folder, urlString: nil, children: [])

let topLevelFolders = ImportedBookmarks.TopLevelFolders(bookmarkBar: bookmarkBar, otherBookmarks: otherBookmarks)
let topLevelFolders = ImportedBookmarks.TopLevelFolders(bookmarkBar: bookmarkBar, otherBookmarks: otherBookmarks, syncedBookmarks: nil)
let importedBookmarks = ImportedBookmarks(topLevelFolders: topLevelFolders)

let result = bookmarkStore.importBookmarks(importedBookmarks, source: .thirdPartyBrowser(.safari))
Expand Down Expand Up @@ -1090,14 +1090,14 @@ final class LocalBookmarkStoreTests: XCTestCase {
}

private func createMockImportedBookmarks() -> ImportedBookmarks {
let bookmark1 = ImportedBookmarks.BookmarkOrFolder(name: "DuckDuckGo", type: "bookmark", urlString: "https://duckduckgo.com", children: nil)
let bookmark2 = ImportedBookmarks.BookmarkOrFolder(name: "Duck", type: "bookmark", urlString: "https://duck.com", children: nil)
let folder1 = ImportedBookmarks.BookmarkOrFolder(name: "Folder", type: "folder", urlString: nil, children: [bookmark2])
let bookmark1 = ImportedBookmarks.BookmarkOrFolder(name: "DuckDuckGo", type: .bookmark, urlString: "https://duckduckgo.com", children: nil)
let bookmark2 = ImportedBookmarks.BookmarkOrFolder(name: "Duck", type: .bookmark, urlString: "https://duck.com", children: nil)
let folder1 = ImportedBookmarks.BookmarkOrFolder(name: "Folder", type: .folder, urlString: nil, children: [bookmark2])

let bookmarkBar = ImportedBookmarks.BookmarkOrFolder(name: "Bookmark Bar", type: "folder", urlString: nil, children: [bookmark1, folder1])
let otherBookmarks = ImportedBookmarks.BookmarkOrFolder(name: "Other Bookmarks", type: "folder", urlString: nil, children: [])
let bookmarkBar = ImportedBookmarks.BookmarkOrFolder(name: "Bookmark Bar", type: .folder, urlString: nil, children: [bookmark1, folder1])
let otherBookmarks = ImportedBookmarks.BookmarkOrFolder(name: "Other Bookmarks", type: .folder, urlString: nil, children: [])

let topLevelFolders = ImportedBookmarks.TopLevelFolders(bookmarkBar: bookmarkBar, otherBookmarks: otherBookmarks)
let topLevelFolders = ImportedBookmarks.TopLevelFolders(bookmarkBar: bookmarkBar, otherBookmarks: otherBookmarks, syncedBookmarks: nil)

return ImportedBookmarks(topLevelFolders: topLevelFolders)
}
Expand Down
4 changes: 2 additions & 2 deletions UnitTests/DataImport/ChromiumBookmarksReaderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ class ChromiumBookmarksReaderTests: XCTestCase {
return
}

XCTAssertEqual(bookmarks.topLevelFolders.bookmarkBar.type, "folder")
XCTAssertEqual(bookmarks.topLevelFolders.otherBookmarks.type, "folder")
XCTAssertEqual(bookmarks.topLevelFolders.bookmarkBar?.type, .folder)
XCTAssertEqual(bookmarks.topLevelFolders.otherBookmarks?.type, .folder)
}

private func resourceURL() -> URL {
Expand Down
8 changes: 4 additions & 4 deletions UnitTests/DataImport/FirefoxBookmarksReaderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ class FirefoxBookmarksReaderTests: XCTestCase {
return
}

XCTAssertEqual(bookmarks.topLevelFolders.bookmarkBar.type, "folder")
XCTAssertEqual(bookmarks.topLevelFolders.otherBookmarks.type, "folder")
XCTAssertEqual(bookmarks.topLevelFolders.bookmarkBar?.type, .folder)
XCTAssertEqual(bookmarks.topLevelFolders.otherBookmarks?.type, .folder)

XCTAssertTrue(bookmarks.topLevelFolders.bookmarkBar.children!.contains(where: { bookmark in
XCTAssertEqual(bookmarks.topLevelFolders.bookmarkBar?.children?.contains(where: { bookmark in
bookmark.url?.absoluteString == "https://duckduckgo.com/"
}))
}), true)
}

private func resourceURL() -> URL {
Expand Down
4 changes: 2 additions & 2 deletions UnitTests/DataImport/SafariBookmarksReaderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ class SafariBookmarksReaderTests: XCTestCase {
return
}

XCTAssertEqual(bookmarks.topLevelFolders.bookmarkBar.type, "folder")
XCTAssertEqual(bookmarks.topLevelFolders.otherBookmarks.type, "folder")
XCTAssertEqual(bookmarks.topLevelFolders.bookmarkBar?.type, .folder)
XCTAssertEqual(bookmarks.topLevelFolders.otherBookmarks?.type, .folder)
}

private func bookmarksFileURL() -> URL {
Expand Down

0 comments on commit edb36b0

Please sign in to comment.