Compare commits

..

3 Commits

4 changed files with 55 additions and 49 deletions

View File

@ -1,5 +1,8 @@
# Changelog # Changelog
## 2023.5 (89)
This build is a hotfix for an issue loading notifications in certain circumstances. The changelong for the previous build (adding post editing) is included below.
## 2023.5 (85) ## 2023.5 (85)
This build adds support for editing posts and showing edit timestamps and history. This build adds support for editing posts and showing edit timestamps and history.

View File

@ -2370,7 +2370,7 @@
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements; CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 85; CURRENT_PROJECT_VERSION = 89;
INFOPLIST_FILE = Tusker/Info.plist; INFOPLIST_FILE = Tusker/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
IPHONEOS_DEPLOYMENT_TARGET = 15.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
@ -2436,7 +2436,7 @@
CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements; CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 85; CURRENT_PROJECT_VERSION = 89;
INFOPLIST_FILE = OpenInTusker/Info.plist; INFOPLIST_FILE = OpenInTusker/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1; IPHONEOS_DEPLOYMENT_TARGET = 14.1;
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3; "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;
@ -2462,7 +2462,7 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 85; CURRENT_PROJECT_VERSION = 89;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = ShareExtension/Info.plist; INFOPLIST_FILE = ShareExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension; INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
@ -2491,7 +2491,7 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 85; CURRENT_PROJECT_VERSION = 89;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = ShareExtension/Info.plist; INFOPLIST_FILE = ShareExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension; INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
@ -2520,7 +2520,7 @@
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements; CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 85; CURRENT_PROJECT_VERSION = 89;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = ShareExtension/Info.plist; INFOPLIST_FILE = ShareExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension; INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
@ -2675,7 +2675,7 @@
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements; CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 85; CURRENT_PROJECT_VERSION = 89;
INFOPLIST_FILE = Tusker/Info.plist; INFOPLIST_FILE = Tusker/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
IPHONEOS_DEPLOYMENT_TARGET = 15.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
@ -2706,7 +2706,7 @@
CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements; CODE_SIGN_ENTITLEMENTS = Tusker/Tusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 85; CURRENT_PROJECT_VERSION = 89;
INFOPLIST_FILE = Tusker/Info.plist; INFOPLIST_FILE = Tusker/Info.plist;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
IPHONEOS_DEPLOYMENT_TARGET = 15.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
@ -2812,7 +2812,7 @@
CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements; CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 85; CURRENT_PROJECT_VERSION = 89;
INFOPLIST_FILE = OpenInTusker/Info.plist; INFOPLIST_FILE = OpenInTusker/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1; IPHONEOS_DEPLOYMENT_TARGET = 14.1;
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3; "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;
@ -2838,7 +2838,7 @@
CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements; CODE_SIGN_ENTITLEMENTS = OpenInTusker/OpenInTusker.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 85; CURRENT_PROJECT_VERSION = 89;
INFOPLIST_FILE = OpenInTusker/Info.plist; INFOPLIST_FILE = OpenInTusker/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1; IPHONEOS_DEPLOYMENT_TARGET = 14.1;
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3; "IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;

View File

@ -31,18 +31,21 @@ class MastodonCachePersistentStore: NSPersistentCloudKitContainer {
context.persistentStoreCoordinator = self.persistentStoreCoordinator context.persistentStoreCoordinator = self.persistentStoreCoordinator
context.automaticallyMergesChangesFromParent = true context.automaticallyMergesChangesFromParent = true
context.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump context.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
context.name = "Background"
return context return context
}() }()
private(set) lazy var prefetchBackgroundContext: NSManagedObjectContext = { // remote change processing happens on its own context, since it can sometimes take
// a really long time (upwards of a minute) and shouldn't block other things using the background context
private lazy var remoteChangesBackgroundContext: NSManagedObjectContext = {
let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
context.persistentStoreCoordinator = self.persistentStoreCoordinator context.persistentStoreCoordinator = self.persistentStoreCoordinator
context.automaticallyMergesChangesFromParent = true context.automaticallyMergesChangesFromParent = true
context.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump context.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
context.name = "RemoteChanges"
return context return context
}() }()
private var remoteChangeHandlerQueue = DispatchQueue(label: "PersistentStore remote changes")
private var lastRemoteChangeToken: NSPersistentHistoryToken? private var lastRemoteChangeToken: NSPersistentHistoryToken?
// TODO: consider sending managed objects through this to avoid re-fetching things unnecessarily // TODO: consider sending managed objects through this to avoid re-fetching things unnecessarily
@ -182,6 +185,7 @@ class MastodonCachePersistentStore: NSPersistentCloudKitContainer {
viewContext.automaticallyMergesChangesFromParent = true viewContext.automaticallyMergesChangesFromParent = true
viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
viewContext.name = "View"
NotificationCenter.default.addObserver(self, selector: #selector(managedObjectsDidChange), name: .NSManagedObjectContextObjectsDidChange, object: viewContext) NotificationCenter.default.addObserver(self, selector: #selector(managedObjectsDidChange), name: .NSManagedObjectContextObjectsDidChange, object: viewContext)
NotificationCenter.default.addObserver(self, selector: #selector(remoteChanges), name: .NSPersistentStoreRemoteChange, object: persistentStoreCoordinator) NotificationCenter.default.addObserver(self, selector: #selector(remoteChanges), name: .NSPersistentStoreRemoteChange, object: persistentStoreCoordinator)
@ -509,50 +513,48 @@ class MastodonCachePersistentStore: NSPersistentCloudKitContainer {
guard let token = notification.userInfo?[NSPersistentHistoryTokenKey] as? NSPersistentHistoryToken else { guard let token = notification.userInfo?[NSPersistentHistoryTokenKey] as? NSPersistentHistoryToken else {
return return
} }
remoteChangeHandlerQueue.async { remoteChangesBackgroundContext.perform {
defer { defer {
self.lastRemoteChangeToken = token self.lastRemoteChangeToken = token
} }
let req = NSPersistentHistoryChangeRequest.fetchHistory(after: self.lastRemoteChangeToken) let req = NSPersistentHistoryChangeRequest.fetchHistory(after: self.lastRemoteChangeToken)
self.backgroundContext.performAndWait { if let result = try? self.remoteChangesBackgroundContext.execute(req) as? NSPersistentHistoryResult,
if let result = try? self.backgroundContext.execute(req) as? NSPersistentHistoryResult, let transactions = result.result as? [NSPersistentHistoryTransaction],
let transactions = result.result as? [NSPersistentHistoryTransaction], !transactions.isEmpty {
!transactions.isEmpty { var changedHashtags = false
var changedHashtags = false var changedInstances = false
var changedInstances = false var changedTimelinePositions = Set<NSManagedObjectID>()
var changedTimelinePositions = Set<NSManagedObjectID>() var changedAccountPrefs = false
var changedAccountPrefs = false outer: for transaction in transactions {
outer: for transaction in transactions { for change in transaction.changes ?? [] {
for change in transaction.changes ?? [] { if change.changedObjectID.entity.name == "SavedHashtag" {
if change.changedObjectID.entity.name == "SavedHashtag" { changedHashtags = true
changedHashtags = true } else if change.changedObjectID.entity.name == "SavedInstance" {
} else if change.changedObjectID.entity.name == "SavedInstance" { changedInstances = true
changedInstances = true } else if change.changedObjectID.entity.name == "TimelinePosition" {
} else if change.changedObjectID.entity.name == "TimelinePosition" { changedTimelinePositions.insert(change.changedObjectID)
changedTimelinePositions.insert(change.changedObjectID) } else if change.changedObjectID.entity.name == "AccountPreferences" {
} else if change.changedObjectID.entity.name == "AccountPreferences" { changedAccountPrefs = true
changedAccountPrefs = true
}
} }
} }
DispatchQueue.main.async { }
if changedHashtags { DispatchQueue.main.async {
NotificationCenter.default.post(name: .savedHashtagsChanged, object: nil) if changedHashtags {
} NotificationCenter.default.post(name: .savedHashtagsChanged, object: nil)
if changedInstances { }
NotificationCenter.default.post(name: .savedInstancesChanged, object: nil) if changedInstances {
} NotificationCenter.default.post(name: .savedInstancesChanged, object: nil)
for id in changedTimelinePositions { }
guard let timelinePosition = try? self.viewContext.existingObject(with: id) as? TimelinePosition else { for id in changedTimelinePositions {
continue guard let timelinePosition = try? self.viewContext.existingObject(with: id) as? TimelinePosition else {
} continue
// the kvo observer that clears the LazilyDecoding cache doesn't always fire on remote changes, so do it manually
timelinePosition.changedRemotely()
NotificationCenter.default.post(name: .timelinePositionChanged, object: timelinePosition)
}
if changedAccountPrefs {
NotificationCenter.default.post(name: .accountPreferencesChangedRemotely, object: nil)
} }
// the kvo observer that clears the LazilyDecoding cache doesn't always fire on remote changes, so do it manually
timelinePosition.changedRemotely()
NotificationCenter.default.post(name: .timelinePositionChanged, object: timelinePosition)
}
if changedAccountPrefs {
NotificationCenter.default.post(name: .accountPreferencesChangedRemotely, object: nil)
} }
} }
} }

View File

@ -22,12 +22,13 @@ struct Logging {
let entries = try store.getEntries() let entries = try store.getEntries()
var data = Data() var data = Data()
let subsystem = Bundle.main.bundleIdentifier! let subsystem = Bundle.main.bundleIdentifier!
let format = Date.ISO8601FormatStyle(includingFractionalSeconds: true)
for entry in entries { for entry in entries {
guard let entry = entry as? OSLogEntryLog, guard let entry = entry as? OSLogEntryLog,
entry.subsystem == subsystem else { entry.subsystem == subsystem else {
continue continue
} }
data.append(contentsOf: entry.date.formatted(.iso8601).utf8) data.append(contentsOf: entry.date.formatted(format).utf8)
data.append(32) // ' ' data.append(32) // ' '
data.append(91) // '[' data.append(91) // '['
data.append(contentsOf: entry.category.utf8) data.append(contentsOf: entry.category.utf8)