ReadKeep/readeck/Data/Mappers/BookmarkEntityMapper.swift
Ilyas Hallak ef13faeff7 feat: Add offline bookmark sync functionality
Add comprehensive offline bookmark support with sync capabilities:
- Implement offline bookmark storage using Core Data with App Group sharing
- Add Share Extension support for saving bookmarks when server unavailable
- Create LocalBookmarksSyncView for managing offline bookmark queue
- Add OfflineSyncManager for automatic and manual sync operations
- Implement ServerConnectivity monitoring and status handling
- Add badge notifications on More tab for pending offline bookmarks
- Fix tag pagination in Share Extension with unique IDs for proper rendering
- Update PhoneTabView with event-based badge count updates
- Add App Group entitlements for data sharing between main app and extension

The offline system provides seamless bookmark saving when disconnected,
with automatic sync when connection is restored and manual sync options.
2025-08-16 22:32:20 +02:00

214 lines
7.0 KiB
Swift

import Foundation
import CoreData
// MARK: - DTO -> Entity
extension BookmarkDto {
func toEntity(context: NSManagedObjectContext) -> BookmarkEntity {
let entity = BookmarkEntity(context: context)
entity.title = self.title
entity.url = self.url
entity.authors = self.authors.first
entity.desc = self.description
entity.created = self.created
entity.siteName = self.siteName
entity.site = self.site
entity.authors = self.authors.first // TODO: support multiple authors
entity.published = self.published
entity.created = self.created
entity.update = self.updated
entity.readingTime = Int16(self.readingTime ?? 0)
entity.readProgress = Int16(self.readProgress)
entity.wordCount = Int64(self.wordCount ?? 0)
entity.isArchived = self.isArchived
entity.isMarked = self.isMarked
entity.hasArticle = self.hasArticle
entity.loaded = self.loaded
entity.hasDeleted = self.isDeleted
entity.documentType = self.documentType
entity.href = self.href
entity.lang = self.lang
entity.textDirection = self.textDirection
entity.type = self.type
entity.state = Int16(self.state)
// entity.resources = self.resources.toEntity(context: context)
return entity
}
}
extension BookmarkResourcesDto {
func toEntity(context: NSManagedObjectContext) -> BookmarkResourcesEntity {
let entity = BookmarkResourcesEntity(context: context)
entity.article = self.article?.toEntity(context: context)
entity.icon = self.icon?.toEntity(context: context)
entity.image = self.image?.toEntity(context: context)
entity.log = self.log?.toEntity(context: context)
entity.props = self.props?.toEntity(context: context)
entity.thumbnail = self.thumbnail?.toEntity(context: context)
return entity
}
}
extension ImageResourceDto {
func toEntity(context: NSManagedObjectContext) -> ImageResourceEntity {
let entity = ImageResourceEntity(context: context)
entity.src = self.src
entity.width = Int64(self.width)
entity.height = Int64(self.height)
return entity
}
}
extension ResourceDto {
func toEntity(context: NSManagedObjectContext) -> ResourceEntity {
let entity = ResourceEntity(context: context)
entity.src = self.src
return entity
}
}
// ------------------------------------------------
// MARK: - BookmarkEntity to Domain Mapping
extension BookmarkEntity {
}
// MARK: - Domain to BookmarkEntity Mapping
extension Bookmark {
func toEntity(context: NSManagedObjectContext) -> BookmarkEntity {
let entity = BookmarkEntity(context: context)
entity.populateFrom(bookmark: self)
return entity
}
func updateEntity(_ entity: BookmarkEntity) {
entity.populateFrom(bookmark: self)
}
}
extension Resource {
func toEntity(context: NSManagedObjectContext) -> ResourceEntity {
let entity = ResourceEntity(context: context)
entity.populateFrom(resource: self)
return entity
}
}
// MARK: - Private Helper Methods
private extension BookmarkEntity {
func populateFrom(bookmark: Bookmark) {
self.id = bookmark.id
self.title = bookmark.title
self.url = bookmark.url
self.desc = bookmark.description
self.siteName = bookmark.siteName
self.site = bookmark.site
self.authors = bookmark.authors.first // TODO: support multiple authors
self.published = bookmark.published
self.created = bookmark.created
self.update = bookmark.updated
self.readingTime = Int16(bookmark.readingTime ?? 0)
self.readProgress = Int16(bookmark.readProgress)
self.wordCount = Int64(bookmark.wordCount ?? 0)
self.isArchived = bookmark.isArchived
self.isMarked = bookmark.isMarked
self.hasArticle = bookmark.hasArticle
self.loaded = bookmark.loaded
self.hasDeleted = bookmark.isDeleted
self.documentType = bookmark.documentType
self.href = bookmark.href
self.lang = bookmark.lang
self.textDirection = bookmark.textDirection
self.type = bookmark.type
self.state = Int16(bookmark.state)
}
}
// MARK: - BookmarkState Mapping
private extension BookmarkState {
static func fromRawValue(_ value: Int) -> BookmarkState {
switch value {
case 0: return .unread
case 1: return .favorite
case 2: return .archived
default: return .unread
}
}
}
private extension BookmarkResourcesEntity {
func populateFrom(bookmarkResources: BookmarkResources) {
}
}
private extension ImageResourceEntity {
func populateFrom(imageResource: ImageResource) {
self.src = imageResource.src
self.height = Int64(imageResource.height)
self.width = Int64(imageResource.width)
}
}
private extension ResourceEntity {
func populateFrom(resource: Resource) {
self.src = resource.src
}
}
// MARK: - Date Conversion Helpers
private extension String {
func toDate() -> Date? {
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
return formatter.date(from: self) ??
ISO8601DateFormatter().date(from: self)
}
}
private extension Date {
func toISOString() -> String {
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
return formatter.string(from: self)
}
}
// MARK: - Array Mapping Extensions
extension Array where Element == BookmarkEntity {
func toDomain() -> [Bookmark] {
return [] // self.map { $0.toDomain() }
}
}
extension Array where Element == Bookmark {
func toEntities(context: NSManagedObjectContext) -> [BookmarkEntity] {
return self.map { $0.toEntity(context: context) }
}
}
/*
extension BookmarkEntity {
func toDomain() -> Bookmark {
return Bookmark(id: id ?? "", title: title ?? "", url: url!, href: href ?? "", description: description, authors: [authors ?? ""], created: created ?? "", published: published, updated: update!, siteName: siteName ?? "", site: site!, readingTime: Int(readingTime), wordCount: Int(wordCount), hasArticle: hasArticle, isArchived: isArchived, isDeleted: isDeleted, isMarked: isMarked, labels: [], lang: lang, loaded: loaded, readProgress: Int(readProgress), documentType: documentType ?? "", state: Int(state), textDirection: textDirection ?? "", type: type ?? "", resources: resources.toDomain())
)
}
}
extension BookmarkResourcesEntity {
func toDomain() -> BookmarkResources {
return BookmarkResources(article: ar, icon: <#T##ImageResource?#>, image: <#T##ImageResource?#>, log: <#T##Resource?#>, props: <#T##Resource?#>, thumbnail: <#T##ImageResource?#>
}
}
extension ImageResourceEntity {
}
*/