<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */


import UIKit
import CoreData
import Foundation
import Shared

class Bookmark: NSManagedObject {
    
    @NSManaged var isFolder: Bool
    @NSManaged var title: String?
    @NSManaged var customTitle: String?
    @NSManaged var url: String?
    @NSManaged var visits: Int32
    @NSManaged var lastVisited: NSDate?
    @NSManaged var created: NSDate?
    @NSManaged var order: Int16
    @NSManaged var tags: [String]?
    
    /// Should not be set directly, due to specific formatting required, use `syncUUID` instead
    /// CD does not allow (easily) searching on transformable properties, could use binary, but would still require tranformtion
    //  syncUUID should never change
    @NSManaged var syncDisplayUUID: String?
    @NSManaged var syncParentDisplayUUID: String?
    @NSManaged var parentFolder: Bookmark?
    @NSManaged var children: Set<Bookmark>?
    
    @NSManaged var domain: Domain?

    // To trigger fetchedResultsController to update, change this value.
    // For instance, when a favicon is set on a domain, to notify any bookmarks or history items that
    // are displayed in a table and waiting for a favicon, you can change markDirty, and the favicon will update
    @NSManaged var markDirty: Int16

    // Is conveted to better store in CD
    var syncUUID: [Int]? {
        get { return syncUUID(fromString: syncDisplayUUID) }
        set(value) { syncDisplayUUID = Bookmark.syncDisplay(fromUUID: value) }
    }
    
    var syncParentUUID: [Int]? {
        get { return syncUUID(fromString: syncParentDisplayUUID) }
        set(value) {
            // Save actual instance variable
            syncParentDisplayUUID = Bookmark.syncDisplay(fromUUID: value)

            // Attach parent, only works if parent exists.
            let parent = Bookmark.get(parentSyncUUID: value)
            parentFolder = parent
        }
    }
    
    var displayTitle: String? {
        if let custom = customTitle where !custom.isEmpty {
            return customTitle
        }
        
        if let t = title where !t.isEmpty {
            return title
        }
        
        // Want to return nil so less checking on frontend
        return nil
    }
    
    override func awakeFromInsert() {
        super.awakeFromInsert()
        created = NSDate()
        lastVisited = created
    }
    
    func asDictionary(deviceId deviceId: [Int]?, action: Int?) -> [String: AnyObject] {
        return SyncRoot(bookmark: self, deviceId: deviceId, action: action).dictionaryRepresentation()
    }

    static func entity(context:NSManagedObjectContext) -> NSEntityDescription {
        return NSEntityDescription.entityForName("Bookmark", inManagedObjectContext: context)!
    }

    class func frc(parentFolder parentFolder: Bookmark?) -> NSFetchedResultsController {
        let fetchRequest = NSFetchRequest()
        fetchRequest.entity = Bookmark.entity(DataController.moc)
        fetchRequest.fetchBatchSize = 20
        fetchRequest.fetchLimit = 200
        fetchRequest.sortDescriptors = [NSSortDescriptor(key:"order", ascending: true), NSSortDescriptor(key:"created", ascending: false)]
        if let parentFolder = parentFolder {
            fetchRequest.predicate = NSPredicate(format: "parentFolder == %@", parentFolder)
        } else {
            fetchRequest.predicate = NSPredicate(format: "parentFolder == nil")
        }

        return NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext:DataController.moc, sectionNameKeyPath: nil, cacheName: nil)
    }
    
    func update(rootObject root: SyncRoot, save: Bool = false) {
        guard let bm = root.bookmark, let site = bm.site else { return }
        title = site.title
        customTitle = site.customTitle
        url = site.location
        lastVisited = NSDate(timeIntervalSince1970:(Double(site.lastAccessedTime ?? 0) / 1000.0))
        syncParentUUID = bm.parentFolderObjectId
        
        if save {
            DataController.saveContext()
        }
    }
    
    func update(customTitle customTitle: String?, url: String?, save: Bool = false) {
        
        // See if there has been any change
        if self.customTitle == customTitle && self.url == url {
            return
        }
        
        self.customTitle = customTitle
        self.url = url
        
        if save {
            DataController.saveContext()
        }
        
        Sync.shared.sendSyncRecords(.bookmark, action: .update, bookmarks: [self])
    }

    // Should not be used for updating, modify to increase protection
    class func add(rootObject root: SyncRoot, save: Bool = false, sendToSync: Bool = false, parentFolder: Bookmark? = nil) -> Bookmark? {
        let bookmark = root.bookmark
        let site = bookmark?.site
     
        var bk: Bookmark!
        if let id = root.objectId, let foundBK = Bookmark.get(syncUUIDs: [id])?.first {
            // Found a pre-existing bookmark, cannot add duplicate
            // Turn into 'update' record instead
            bk = foundBK
        } else {
            bk = Bookmark(entity: Bookmark.entity(DataController.moc), insertIntoManagedObjectContext: DataController.moc)
        }
        
        
        let url = NSURL(string: site?.location ?? bk.url ?? "")
        if url?.absoluteString?.startsWith(WebServer.sharedInstance.base) ?? false {
            return nil
        }
        
        // Use new values, fallback to previous values
        bk.url = url?.absoluteString ?? bk.url
        bk.title = site?.title ?? bk.title
        bk.customTitle = site?.customTitle ?? bk.customTitle // TODO: Check against empty titles
        bk.isFolder = bookmark?.isFolder ?? bk.isFolder ?? false
        bk.syncUUID = root.objectId ?? bk.syncUUID ?? (1..<16).map { _ in Int(arc4random_uniform(256)) }
        
        if let created = site?.creationTime {
            bk.created = NSDate(timeIntervalSince1970:(Double(created) / 1000.0))<commit_msg>Fix #805: Properly highlight star if domain is bookmarked.<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */


import UIKit
import CoreData
import Foundation
import Shared

class Bookmark: NSManagedObject {
    
    @NSManaged var isFolder: Bool
    @NSManaged var title: String?
    @NSManaged var customTitle: String?
    @NSManaged var url: String?
    @NSManaged var visits: Int32
    @NSManaged var lastVisited: NSDate?
    @NSManaged var created: NSDate?
    @NSManaged var order: Int16
    @NSManaged var tags: [String]?
    
    /// Should not be set directly, due to specific formatting required, use `syncUUID` instead
    /// CD does not allow (easily) searching on transformable properties, could use binary, but would still require tranformtion
    //  syncUUID should never change
    @NSManaged var syncDisplayUUID: String?
    @NSManaged var syncParentDisplayUUID: String?
    @NSManaged var parentFolder: Bookmark?
    @NSManaged var children: Set<Bookmark>?
    
    @NSManaged var domain: Domain?

    // To trigger fetchedResultsController to update, change this value.
    // For instance, when a favicon is set on a domain, to notify any bookmarks or history items that
    // are displayed in a table and waiting for a favicon, you can change markDirty, and the favicon will update
    @NSManaged var markDirty: Int16

    // Is conveted to better store in CD
    var syncUUID: [Int]? {
        get { return syncUUID(fromString: syncDisplayUUID) }
        set(value) { syncDisplayUUID = Bookmark.syncDisplay(fromUUID: value) }
    }
    
    var syncParentUUID: [Int]? {
        get { return syncUUID(fromString: syncParentDisplayUUID) }
        set(value) {
            // Save actual instance variable
            syncParentDisplayUUID = Bookmark.syncDisplay(fromUUID: value)

            // Attach parent, only works if parent exists.
            let parent = Bookmark.get(parentSyncUUID: value)
            parentFolder = parent
        }
    }
    
    var displayTitle: String? {
        if let custom = customTitle where !custom.isEmpty {
            return customTitle
        }
        
        if let t = title where !t.isEmpty {
            return title
        }
        
        // Want to return nil so less checking on frontend
        return nil
    }
    
    override func awakeFromInsert() {
        super.awakeFromInsert()
        created = NSDate()
        lastVisited = created
    }
    
    func asDictionary(deviceId deviceId: [Int]?, action: Int?) -> [String: AnyObject] {
        return SyncRoot(bookmark: self, deviceId: deviceId, action: action).dictionaryRepresentation()
    }

    static func entity(context:NSManagedObjectContext) -> NSEntityDescription {
        return NSEntityDescription.entityForName("Bookmark", inManagedObjectContext: context)!
    }

    class func frc(parentFolder parentFolder: Bookmark?) -> NSFetchedResultsController {
        let fetchRequest = NSFetchRequest()
        fetchRequest.entity = Bookmark.entity(DataController.moc)
        fetchRequest.fetchBatchSize = 20
        fetchRequest.fetchLimit = 200
        fetchRequest.sortDescriptors = [NSSortDescriptor(key:"order", ascending: true), NSSortDescriptor(key:"created", ascending: false)]
        if let parentFolder = parentFolder {
            fetchRequest.predicate = NSPredicate(format: "parentFolder == %@", parentFolder)
        } else {
            fetchRequest.predicate = NSPredicate(format: "parentFolder == nil")
        }

        return NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext:DataController.moc, sectionNameKeyPath: nil, cacheName: nil)
    }
    
    func update(rootObject root: SyncRoot, save: Bool = false) {
        guard let bm = root.bookmark, let site = bm.site else { return }
        title = site.title
        customTitle = site.customTitle
        url = site.location
        lastVisited = NSDate(timeIntervalSince1970:(Double(site.lastAccessedTime ?? 0) / 1000.0))
        syncParentUUID = bm.parentFolderObjectId
        
        if save {
            DataController.saveContext()
        }
    }
    
    func update(customTitle customTitle: String?, url: String?, save: Bool = false) {
        
        // See if there has been any change
        if self.customTitle == customTitle && self.url == url {
            return
        }
        
        self.customTitle = customTitle
        self.url = url
        
        if save {
            DataController.saveContext()
        }
        
        Sync.shared.sendSyncRecords(.bookmark, action: .update, bookmarks: [self])
    }

    // Should not be used for updating, modify to increase protection
    class func add(rootObject root: SyncRoot, save: Bool = false, sendToSync: Bool = false, parentFolder: Bookmark? = nil) -> Bookmark? {
        let bookmark = root.bookmark
        let site = bookmark?.site
     
        var bk: Bookmark!
        if let id = root.objectId, let foundBK = Bookmark.get(syncUUIDs: [id])?.first {
            // Found a pre-existing bookmark, cannot add duplicate
            // Turn into 'update' record instead
            bk = foundBK
        } else {
            bk = Bookmark(entity: Bookmark.entity(DataController.moc), insertIntoManagedObjectContext: DataController.moc)
        }
        
        
        let url = NSURL(string: site?.location ?? bk.url ?? "")
        if url?.absoluteDisplayString()?.startsWith(WebServer.sharedInstance.base) ?? false {
            return nil
        }
        
        // Use new values, fallback to previous values
        bk.url = url?.absoluteDisplayString() ?? bk.url
        bk.title = site?.title ?? bk.title
        bk.customTitle = site?.customTitle ?? bk.customTitle // TODO: Check against empty titles
        bk.isFolder = bookmark?.isFolder ?? bk.isFolder ?? false
        bk.syncUUID = root.objectId ?? bk.syncUUID ?? (1..<16).map { _ in Int(arc4random_uniform(256)) }
        
        if let created = site?.creationTime {
            bk.created = NSDate(timeIntervalSince1970:(Double(created) / 1000.0))
{'fraction_non_alphanumeric': 0.08773348427266112, 'fraction_numerical': 0.006873130104309857, 'mean_word_length': 2.4741573033707867, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3370195', 'n_tokens_mistral': 3445, 'n_tokens_neox': 3355, 'n_words': 1029}