[ABANDONED] Mastodon iOS client.
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

//
// 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
}
}
}