2023-01-03 14:09:22 +01:00
|
|
|
//
|
|
|
|
// https://mczachurski.dev
|
|
|
|
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
|
|
|
// Licensed under the MIT License.
|
|
|
|
//
|
|
|
|
|
|
|
|
import Foundation
|
|
|
|
import CoreData
|
|
|
|
import MastodonSwift
|
|
|
|
|
|
|
|
public class TimelineService {
|
|
|
|
public static let shared = TimelineService()
|
|
|
|
|
|
|
|
public func onBottomOfList(for accountData: AccountData) async throws {
|
|
|
|
// Load data from API and operate on CoreData on background context.
|
|
|
|
let backgroundContext = CoreDataHandler.shared.newBackgroundContext()
|
|
|
|
|
|
|
|
// Get maximimum downloaded stauts id.
|
|
|
|
let statusDataHandler = StatusDataHandler()
|
|
|
|
let oldestStatus = statusDataHandler.getMinimumtatus(viewContext: backgroundContext)
|
|
|
|
|
|
|
|
guard let oldestStatus = oldestStatus else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
try await self.loadData(for: accountData, on: backgroundContext, maxId: oldestStatus.id)
|
|
|
|
}
|
|
|
|
|
|
|
|
public func onTopOfList(for accountData: AccountData) async throws {
|
|
|
|
// Load data from API and operate on CoreData on background context.
|
|
|
|
let backgroundContext = CoreDataHandler.shared.newBackgroundContext()
|
|
|
|
|
|
|
|
// Get maximimum downloaded stauts id.
|
|
|
|
let statusDataHandler = StatusDataHandler()
|
|
|
|
let newestStatus = statusDataHandler.getMaximumStatus(viewContext: backgroundContext)
|
2023-01-03 20:42:20 +01:00
|
|
|
|
|
|
|
try await self.loadData(for: accountData, on: backgroundContext, minId: newestStatus?.id)
|
2023-01-03 14:09:22 +01:00
|
|
|
}
|
|
|
|
|
2023-01-04 17:56:01 +01:00
|
|
|
public func getStatus(withId statusId: String, and accountData: AccountData) async throws -> Status? {
|
|
|
|
guard let accessToken = accountData.accessToken else {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
let client = MastodonClient(baseURL: accountData.serverUrl).getAuthenticated(token: accessToken)
|
|
|
|
return try await client.read(statusId: statusId)
|
|
|
|
}
|
|
|
|
|
|
|
|
public func updateStatus(statusData: StatusData, and accountData: AccountData) async throws -> StatusData? {
|
|
|
|
guard let accessToken = accountData.accessToken else {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load data from API and operate on CoreData on background context.
|
|
|
|
let backgroundContext = CoreDataHandler.shared.newBackgroundContext()
|
|
|
|
|
|
|
|
// Get new information from API.
|
|
|
|
let client = MastodonClient(baseURL: accountData.serverUrl).getAuthenticated(token: accessToken)
|
|
|
|
let status = try await client.read(statusId: statusData.id)
|
|
|
|
|
|
|
|
// Update status data in database.
|
|
|
|
try await self.updateStatusData(from: status, to: statusData, on: backgroundContext)
|
|
|
|
try backgroundContext.save()
|
|
|
|
|
|
|
|
return statusData
|
|
|
|
}
|
|
|
|
|
2023-01-03 14:09:22 +01:00
|
|
|
public func getComments(for statusId: String, and accountData: AccountData) async throws -> Context {
|
|
|
|
let client = MastodonClient(baseURL: accountData.serverUrl).getAuthenticated(token: accountData.accessToken ?? "")
|
|
|
|
return try await client.getContext(for: statusId)
|
|
|
|
}
|
|
|
|
|
|
|
|
private func loadData(for accountData: AccountData, on backgroundContext: NSManagedObjectContext, minId: String? = nil, maxId: String? = nil) async throws {
|
|
|
|
guard let accessToken = accountData.accessToken else {
|
|
|
|
return
|
|
|
|
}
|
2023-01-04 17:56:01 +01:00
|
|
|
|
2023-01-03 14:09:22 +01:00
|
|
|
// Retrieve statuses from API.
|
|
|
|
let client = MastodonClient(baseURL: accountData.serverUrl).getAuthenticated(token: accessToken)
|
|
|
|
let statuses = try await client.getHomeTimeline(maxId: maxId, minId: minId, limit: 40)
|
|
|
|
|
2023-01-04 17:56:01 +01:00
|
|
|
// Create handler for managing statuses in database.
|
|
|
|
let statusDataHandler = StatusDataHandler()
|
|
|
|
|
|
|
|
// Save status data in database.
|
2023-01-03 14:09:22 +01:00
|
|
|
for status in statuses {
|
2023-01-04 17:56:01 +01:00
|
|
|
let statusData = statusDataHandler.createStatusDataEntity(viewContext: backgroundContext)
|
|
|
|
try await self.updateStatusData(from: status, to: statusData, on: backgroundContext)
|
|
|
|
}
|
|
|
|
|
|
|
|
try backgroundContext.save()
|
|
|
|
}
|
|
|
|
|
|
|
|
private func updateStatusData(from status: Status, to statusData: StatusData, on backgroundContext: NSManagedObjectContext) async throws {
|
|
|
|
statusData.id = status.id
|
|
|
|
statusData.createdAt = status.createdAt
|
|
|
|
statusData.accountAvatar = status.account?.avatar
|
|
|
|
statusData.accountDisplayName = status.account?.displayName
|
|
|
|
statusData.accountId = status.account!.id
|
|
|
|
statusData.accountUsername = status.account!.username
|
|
|
|
statusData.applicationName = status.application?.name
|
|
|
|
statusData.applicationWebsite = status.application?.website
|
|
|
|
statusData.bookmarked = status.bookmarked
|
|
|
|
statusData.content = status.content
|
|
|
|
statusData.favourited = status.favourited
|
|
|
|
statusData.favouritesCount = Int32(status.favouritesCount)
|
|
|
|
statusData.inReplyToAccount = status.inReplyToAccount
|
|
|
|
statusData.inReplyToId = status.inReplyToId
|
|
|
|
statusData.muted = status.muted
|
|
|
|
statusData.pinned = status.pinned
|
|
|
|
statusData.reblogged = status.reblogged
|
|
|
|
statusData.reblogsCount = Int32(status.reblogsCount)
|
|
|
|
statusData.repliesCount = Int32(status.repliesCount)
|
|
|
|
statusData.sensitive = status.sensitive
|
|
|
|
statusData.spoilerText = status.spoilerText
|
|
|
|
statusData.uri = status.uri
|
|
|
|
statusData.url = status.url
|
|
|
|
statusData.visibility = status.visibility.rawValue
|
|
|
|
|
|
|
|
let attachmentDataHandler = AttachmentDataHandler()
|
|
|
|
|
|
|
|
for attachment in status.mediaAttachments {
|
|
|
|
let imageData = try await self.fetchImage(attachment: attachment)
|
2023-01-03 14:09:22 +01:00
|
|
|
|
2023-01-04 17:56:01 +01:00
|
|
|
guard let imageData = imageData else {
|
|
|
|
continue
|
|
|
|
}
|
2023-01-03 14:09:22 +01:00
|
|
|
|
2023-01-04 17:56:01 +01:00
|
|
|
/*
|
|
|
|
var exif = image.getExifData()
|
|
|
|
if let dict = exif as? [String: AnyObject] {
|
|
|
|
dict.keys.map { key in
|
|
|
|
print(key)
|
|
|
|
print(dict[key])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
|
|
// Save attachment in database.
|
|
|
|
let attachmentData = statusData.attachments().first { item in item.id == attachment.id }
|
|
|
|
?? attachmentDataHandler.createAttachmnentDataEntity(viewContext: backgroundContext)
|
|
|
|
|
|
|
|
attachmentData.id = attachment.id
|
|
|
|
attachmentData.url = attachment.url
|
|
|
|
attachmentData.blurhash = attachment.blurhash
|
|
|
|
attachmentData.previewUrl = attachment.previewUrl
|
|
|
|
attachmentData.remoteUrl = attachment.remoteUrl
|
|
|
|
attachmentData.text = attachment.description
|
|
|
|
attachmentData.type = attachment.type.rawValue
|
|
|
|
|
|
|
|
attachmentData.statusId = statusData.id
|
|
|
|
attachmentData.data = imageData
|
|
|
|
|
|
|
|
if attachmentData.isInserted {
|
|
|
|
attachmentData.statusRelation = statusData
|
|
|
|
statusData.addToAttachmentRelation(attachmentData)
|
2023-01-03 14:09:22 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private func fetchImage(attachment: Attachment) async throws -> Data? {
|
|
|
|
guard let data = try await RemoteFileService.shared.fetchData(url: attachment.url) else {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return data
|
|
|
|
}
|
|
|
|
}
|