You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
413 lines
14 KiB
413 lines
14 KiB
//
|
|
// MastodonDataManager.swift
|
|
// elpha-ios
|
|
//
|
|
// Created by Dwayne Harris on 10/1/18.
|
|
// Copyright © 2018 Elpha. All rights reserved.
|
|
//
|
|
|
|
import CoreData
|
|
import Foundation
|
|
|
|
class UpsertResult<T: NSManagedObject> {
|
|
var object: T
|
|
var new: Bool = false
|
|
|
|
init(object: T, new: Bool) {
|
|
self.object = object
|
|
self.new = new
|
|
}
|
|
}
|
|
|
|
@objc public class AccountField: NSObject, NSCoding {
|
|
let name: String
|
|
let value: String
|
|
|
|
init(name: String, value: String) {
|
|
self.name = name
|
|
self.value = value
|
|
}
|
|
|
|
public func encode(with aCoder: NSCoder) {
|
|
aCoder.encode(name, forKey: "name")
|
|
aCoder.encode(value, forKey: "value")
|
|
}
|
|
|
|
convenience required public init?(coder aDecoder: NSCoder) {
|
|
guard
|
|
let name = aDecoder.decodeObject(forKey: "name") as? String,
|
|
let value = aDecoder.decodeObject(forKey: "value") as? String
|
|
else {
|
|
return nil
|
|
}
|
|
|
|
self.init(name: name, value: value)
|
|
}
|
|
}
|
|
|
|
@objc public class Mention: NSObject, NSCoding {
|
|
let id: String
|
|
let url: URL
|
|
let username: String
|
|
let acct: String
|
|
|
|
init(id: String, url: URL, username: String, acct: String) {
|
|
self.id = id
|
|
self.url = url
|
|
self.username = username
|
|
self.acct = acct
|
|
}
|
|
|
|
public func encode(with aCoder: NSCoder) {
|
|
aCoder.encode(id, forKey: "id")
|
|
aCoder.encode(url, forKey: "url")
|
|
aCoder.encode(username, forKey: "username")
|
|
aCoder.encode(acct, forKey: "acct")
|
|
}
|
|
|
|
convenience required public init?(coder aDecoder: NSCoder) {
|
|
guard
|
|
let id = aDecoder.decodeObject(forKey: "id") as? String,
|
|
let url = aDecoder.decodeObject(forKey: "url") as? URL,
|
|
let username = aDecoder.decodeObject(forKey: "username") as? String,
|
|
let acct = aDecoder.decodeObject(forKey: "acct") as? String
|
|
else {
|
|
return nil
|
|
}
|
|
|
|
self.init(id: id, url: url, username: username, acct: acct)
|
|
}
|
|
}
|
|
|
|
@objc public class Emoji: NSObject, NSCoding {
|
|
let shortcode: String
|
|
let staticURL: URL
|
|
let url: URL
|
|
let visibleInPicker: Bool
|
|
|
|
init(shortcode: String, staticURL: URL, url: URL, visibleInPicker: Bool) {
|
|
self.shortcode = shortcode
|
|
self.staticURL = staticURL
|
|
self.url = url
|
|
self.visibleInPicker = visibleInPicker
|
|
}
|
|
|
|
public func encode(with aCoder: NSCoder) {
|
|
aCoder.encode(shortcode, forKey: "shortcode")
|
|
aCoder.encode(staticURL, forKey: "staticURL")
|
|
aCoder.encode(url, forKey: "url")
|
|
aCoder.encode(visibleInPicker, forKey: "visibleInPicker")
|
|
}
|
|
|
|
convenience required public init?(coder aDecoder: NSCoder) {
|
|
guard
|
|
let shortcode = aDecoder.decodeObject(forKey: "shortcode") as? String,
|
|
let staticURL = aDecoder.decodeObject(forKey: "staticURL") as? URL,
|
|
let url = aDecoder.decodeObject(forKey: "url") as? URL
|
|
else {
|
|
return nil
|
|
}
|
|
|
|
self.init(shortcode: shortcode, staticURL: staticURL, url: url, visibleInPicker: aDecoder.decodeBool(forKey: "acct"))
|
|
}
|
|
}
|
|
|
|
@objc public class PaginationMarker: NSObject, NSCoding {
|
|
let context: String
|
|
let item: PaginationItem
|
|
|
|
init(context: String, item: PaginationItem) {
|
|
self.context = context
|
|
self.item = item
|
|
}
|
|
|
|
public func encode(with aCoder: NSCoder) {
|
|
aCoder.encode(context, forKey: "context")
|
|
aCoder.encode(item, forKey: "item")
|
|
}
|
|
|
|
convenience required public init?(coder aDecoder: NSCoder) {
|
|
guard
|
|
let context = aDecoder.decodeObject(forKey: "context") as? String,
|
|
let item = aDecoder.decodeObject(forKey: "item") as? PaginationItem
|
|
else {
|
|
return nil
|
|
}
|
|
|
|
self.init(context: context, item: item)
|
|
}
|
|
}
|
|
|
|
public class MastodonDataManager {
|
|
static var dateFormatter: DateFormatter {
|
|
let dateFormatter = DateFormatter()
|
|
|
|
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SZ"
|
|
dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
|
|
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
|
|
|
|
return dateFormatter
|
|
}
|
|
|
|
static func setAccount(_ account: AccountMO, withData data: JSONObject) -> AccountMO {
|
|
let dateFormatter = self.dateFormatter
|
|
|
|
account.fetchedAt = Date()
|
|
account.id = data["id"] as? String
|
|
account.username = data["username"] as? String
|
|
account.acct = data["acct"] as? String
|
|
account.displayName = data["display_name"] as? String
|
|
account.note = data["note"] as? String
|
|
account.url = URL(string: data["url"] as! String)
|
|
account.avatarURL = URL(string: data["avatar"] as! String)
|
|
account.avatarStaticURL = URL(string: data["avatar_static"] as! String)
|
|
account.headerURL = URL(string: data["header"] as! String)
|
|
account.headerStaticURL = URL(string: data["header_static"] as! String)
|
|
account.locked = data["locked"] as? Bool ?? false
|
|
account.createdAt = dateFormatter.date(from: data["created_at"] as! String)
|
|
account.followersCount = data["followers_count"] as! Int32
|
|
account.followingCount = data["following_count"] as! Int32
|
|
account.statusesCount = data["statuses_count"] as! Int32
|
|
|
|
if let fields = data["fields"] as? [[String: Any]] {
|
|
account.fields = fields.map { AccountField(name: $0["name"] as! String, value: $0["value"] as! String) }
|
|
}
|
|
|
|
return account
|
|
}
|
|
|
|
static func upsertAccount(_ data: JSONObject) -> AccountMO? {
|
|
let request = NSFetchRequest<AccountMO>(entityName: "Account")
|
|
request.predicate = NSPredicate(format: "id == %@", data["id"] as! String)
|
|
|
|
do {
|
|
let results = try CoreDataManager.shared.context.fetch(request)
|
|
if let account = results.first {
|
|
return setAccount(account, withData: data)
|
|
} else {
|
|
return setAccount(AccountMO(context: CoreDataManager.shared.context), withData: data)
|
|
}
|
|
} catch {
|
|
print("\(error)")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
static func setAttachment(_ attachment: AttachmentMO, withData data: JSONObject) -> AttachmentMO {
|
|
attachment.id = data["id"] as? String
|
|
attachment.type = data["type"] as? String
|
|
attachment.url = URL(string: data["url"] as! String)
|
|
attachment.previewURL = URL(string: data["preview_url"] as! String)
|
|
|
|
if let remoteURL = data["remote_url"] as? String {
|
|
attachment.remoteURL = URL(string: remoteURL)
|
|
}
|
|
|
|
if let textURL = data["text_url"] as? String {
|
|
attachment.textURL = URL(string: textURL)
|
|
}
|
|
|
|
attachment.fetchedAt = Date()
|
|
|
|
return attachment
|
|
}
|
|
|
|
static func upsertAttachment(_ data: JSONObject) -> AttachmentMO? {
|
|
let request = NSFetchRequest<AttachmentMO>(entityName: "Attachment")
|
|
request.predicate = NSPredicate(format: "id == %@", data["id"] as! String)
|
|
|
|
do {
|
|
let results = try CoreDataManager.shared.context.fetch(request)
|
|
if let attachment = results.first {
|
|
return setAttachment(attachment, withData: data)
|
|
} else {
|
|
return setAttachment(AttachmentMO(context: CoreDataManager.shared.context), withData: data)
|
|
}
|
|
} catch {
|
|
print("\(error)")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
static func setTag(_ tag: TagMO, withData data: JSONObject) -> TagMO {
|
|
tag.name = data["name"] as? String
|
|
tag.url = URL(string: data["url"] as! String)
|
|
|
|
return tag
|
|
}
|
|
|
|
static func upsertTag(_ data: JSONObject) -> TagMO? {
|
|
let request = NSFetchRequest<TagMO>(entityName: "Tag")
|
|
request.predicate = NSPredicate(format: "name == %@", data["name"] as! String)
|
|
|
|
do {
|
|
let results = try CoreDataManager.shared.context.fetch(request)
|
|
if let tag = results.first {
|
|
return setTag(tag, withData: data)
|
|
} else {
|
|
return setTag(TagMO(context: CoreDataManager.shared.context), withData: data)
|
|
}
|
|
} catch {
|
|
print("\(error)")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
static func setApp(_ app: AppMO, withData data: JSONObject) -> AppMO {
|
|
app.name = data["name"] as? String
|
|
|
|
if let website = data["website"] as? String {
|
|
app.website = URL(string: website)
|
|
}
|
|
|
|
return app
|
|
}
|
|
|
|
static func upsertApp(_ data: JSONObject) -> AppMO? {
|
|
let request = NSFetchRequest<AppMO>(entityName: "App")
|
|
request.predicate = NSPredicate(format: "name == %@", data["name"] as! String)
|
|
|
|
do {
|
|
let results = try CoreDataManager.shared.context.fetch(request)
|
|
if let app = results.first {
|
|
return setApp(app, withData: data)
|
|
} else {
|
|
return setApp(AppMO(context: CoreDataManager.shared.context), withData: data)
|
|
}
|
|
} catch {
|
|
print("\(error)")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
static func setStatus(_ status: StatusMO, withData data: JSONObject) -> StatusMO {
|
|
let dateFormatter = self.dateFormatter
|
|
|
|
status.id = data["id"] as? String
|
|
status.uri = URL(string: data["uri"] as! String)
|
|
status.account = MastodonDataManager.upsertAccount(data["account"] as! JSONObject)
|
|
status.inReplyToID = data["in_reply_to_id"] as? String
|
|
status.inReplyToAccountID = data["in_reply_to_account_id"] as? String
|
|
status.content = data["content"] as? String
|
|
status.createdAt = dateFormatter.date(from: data["created_at"] as! String)
|
|
status.repliesCount = data["replies_count"] as! Int32
|
|
status.reblogsCount = data["reblogs_count"] as! Int32
|
|
status.favoritesCount = data["favourites_count"] as! Int32
|
|
status.reblogged = data["reblogged"] as? Bool ?? false
|
|
status.favorited = data["favourited"] as? Bool ?? false
|
|
status.sensitive = data["sensitive"] as? Bool ?? false
|
|
status.pinned = data["pinned"] as? Bool ?? false
|
|
status.spoilerText = data["spoiler_text"] as? String
|
|
status.visibility = data["visibility"] as? String
|
|
status.attributedContent = status.content?.htmlAttributed(size: 15.0)
|
|
|
|
status.fetchedAt = Date()
|
|
|
|
if let url = data["url"] as? String {
|
|
status.url = URL(string: url)
|
|
}
|
|
|
|
if let app = data["application"] as? JSONObject {
|
|
status.app = MastodonDataManager.upsertApp(app)
|
|
}
|
|
|
|
if let attachments = data["media_attachments"] as? [JSONObject] {
|
|
attachments.forEach { attachment in
|
|
if let attachment = MastodonDataManager.upsertAttachment(attachment) {
|
|
status.addToAttachments(attachment)
|
|
}
|
|
}
|
|
}
|
|
|
|
if let mentions = data["mentions"] as? [JSONObject] {
|
|
status.mentions = mentions.map { mention in
|
|
return Mention(
|
|
id: mention["id"] as! String,
|
|
url: URL(string: mention["url"] as! String)!,
|
|
username: mention["username"] as! String,
|
|
acct: mention["acct"] as! String
|
|
)
|
|
}
|
|
}
|
|
|
|
if let tags = data["tags"] as? [JSONObject] {
|
|
tags.forEach { tag in
|
|
if let tag = MastodonDataManager.upsertTag(tag) {
|
|
status.addToTags(tag)
|
|
}
|
|
}
|
|
}
|
|
|
|
if let emojis = data["emojis"] as? [JSONObject] {
|
|
status.emojis = emojis.map { emoji in
|
|
return Emoji(
|
|
shortcode: emoji["shortcode"] as! String,
|
|
staticURL: URL(string: emoji["static_url"] as! String)!,
|
|
url: URL(string: emoji["url"] as! String)!,
|
|
visibleInPicker: emoji["visible_in_picker"] as? Bool ?? false
|
|
)
|
|
}
|
|
}
|
|
|
|
if let reblog = data["reblog"] as? JSONObject {
|
|
let upsertResult = upsertStatus(reblog)
|
|
status.reblog = upsertResult?.object
|
|
}
|
|
|
|
return status
|
|
}
|
|
|
|
static func upsertStatus(_ data: JSONObject) -> UpsertResult<StatusMO>? {
|
|
let request = NSFetchRequest<StatusMO>(entityName: "Status")
|
|
request.predicate = NSPredicate(format: "id == %@", data["id"] as! String)
|
|
|
|
do {
|
|
let results = try CoreDataManager.shared.context.fetch(request)
|
|
if let status = results.first {
|
|
return UpsertResult(
|
|
object: setStatus(status, withData: data),
|
|
new: false
|
|
)
|
|
} else {
|
|
let status = StatusMO(context: CoreDataManager.shared.context)
|
|
status.hidden = !(data["spoiler_text"] as! String).isEmpty
|
|
|
|
return UpsertResult(
|
|
object: setStatus(status, withData: data),
|
|
new: true
|
|
)
|
|
}
|
|
} catch {
|
|
print("\(error)")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
static func saveClient(id: String, clientID: String, clientSecret: String, host: String) -> ClientMO {
|
|
let client = ClientMO(context: CoreDataManager.shared.context)
|
|
client.id = id
|
|
client.clientID = clientID
|
|
client.clientSecret = clientSecret
|
|
client.host = host
|
|
|
|
CoreDataManager.shared.saveContext()
|
|
return client
|
|
}
|
|
|
|
static func account(id: String) -> AccountMO? {
|
|
let request = NSFetchRequest<AccountMO>(entityName: "Account")
|
|
request.predicate = NSPredicate(format: "id == %@", id)
|
|
|
|
do {
|
|
let results = try CoreDataManager.shared.context.fetch(request)
|
|
guard let account = results.first else {
|
|
return nil
|
|
}
|
|
|
|
return account
|
|
} catch {
|
|
print("\(error)")
|
|
return nil
|
|
}
|
|
}
|
|
}
|