Cleanup orphaned local attachments
This commit is contained in:
parent
d84d402271
commit
6b57ec8b97
|
@ -18,6 +18,10 @@ private let encoder = PropertyListEncoder()
|
||||||
@objc
|
@objc
|
||||||
public final class DraftAttachment: NSManagedObject, Identifiable {
|
public final class DraftAttachment: NSManagedObject, Identifiable {
|
||||||
|
|
||||||
|
@nonobjc public class func fetchRequest() -> NSFetchRequest<DraftAttachment> {
|
||||||
|
return NSFetchRequest<DraftAttachment>(entityName: "DraftAttachment")
|
||||||
|
}
|
||||||
|
|
||||||
@NSManaged internal var assetID: String?
|
@NSManaged internal var assetID: String?
|
||||||
@NSManaged public var attachmentDescription: String
|
@NSManaged public var attachmentDescription: String
|
||||||
@NSManaged internal private(set) var drawingData: Data?
|
@NSManaged internal private(set) var drawingData: Data?
|
||||||
|
@ -154,9 +158,13 @@ extension DraftAttachment: NSItemProviderReading {
|
||||||
return attachment
|
return attachment
|
||||||
}
|
}
|
||||||
|
|
||||||
static func writeDataToFile(_ data: Data, id: UUID, type: UTType) throws -> URL {
|
static var attachmentsDirectory: URL {
|
||||||
let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.space.vaccor.Tusker")!
|
let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.space.vaccor.Tusker")!
|
||||||
let directoryURL = containerURL.appendingPathComponent("Documents").appendingPathComponent("attachments")
|
return containerURL.appendingPathComponent("Documents").appendingPathComponent("attachments")
|
||||||
|
}
|
||||||
|
|
||||||
|
static func writeDataToFile(_ data: Data, id: UUID, type: UTType) throws -> URL {
|
||||||
|
let directoryURL = attachmentsDirectory
|
||||||
try FileManager.default.createDirectory(at: directoryURL, withIntermediateDirectories: true)
|
try FileManager.default.createDirectory(at: directoryURL, withIntermediateDirectories: true)
|
||||||
let attachmentURL = directoryURL.appendingPathComponent(id.uuidString, conformingTo: type)
|
let attachmentURL = directoryURL.appendingPathComponent(id.uuidString, conformingTo: type)
|
||||||
try data.write(to: attachmentURL)
|
try data.write(to: attachmentURL)
|
||||||
|
|
|
@ -157,6 +157,29 @@ public class DraftsPersistentContainer: NSPersistentContainer {
|
||||||
draft.attachments.add(draftAttachment)
|
draft.attachments.add(draftAttachment)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func removeOrphanedAttachments(completion: @escaping () -> Void) {
|
||||||
|
guard let files = try? FileManager.default.contentsOfDirectory(at: DraftAttachment.attachmentsDirectory, includingPropertiesForKeys: nil),
|
||||||
|
!files.isEmpty else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
performBackgroundTask { context in
|
||||||
|
let allAttachmentsReq = DraftAttachment.fetchRequest()
|
||||||
|
allAttachmentsReq.predicate = NSPredicate(format: "fileURL != nil")
|
||||||
|
guard let allAttachments = try? context.fetch(allAttachmentsReq) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let orphaned = Set(files).subtracting(allAttachments.lazy.compactMap(\.fileURL))
|
||||||
|
for url in orphaned {
|
||||||
|
do {
|
||||||
|
try FileManager.default.removeItem(at: url)
|
||||||
|
} catch {
|
||||||
|
logger.error("Failed to remove orphaned attachment: \(String(describing: error), privacy: .public)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
completion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@objc private func remoteChanges(_ notification: Foundation.Notification) {
|
@objc private func remoteChanges(_ notification: Foundation.Notification) {
|
||||||
guard let newHistoryToken = notification.userInfo?[NSPersistentHistoryTokenKey] as? NSPersistentHistoryToken else {
|
guard let newHistoryToken = notification.userInfo?[NSPersistentHistoryTokenKey] as? NSPersistentHistoryToken else {
|
||||||
return
|
return
|
||||||
|
|
|
@ -303,6 +303,7 @@
|
||||||
D6D79F2F2A0D6A7F00AB2315 /* StatusEditPollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F2E2A0D6A7F00AB2315 /* StatusEditPollView.swift */; };
|
D6D79F2F2A0D6A7F00AB2315 /* StatusEditPollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F2E2A0D6A7F00AB2315 /* StatusEditPollView.swift */; };
|
||||||
D6D79F532A0FFE3200AB2315 /* ToggleableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F522A0FFE3200AB2315 /* ToggleableButton.swift */; };
|
D6D79F532A0FFE3200AB2315 /* ToggleableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F522A0FFE3200AB2315 /* ToggleableButton.swift */; };
|
||||||
D6D79F572A1160B800AB2315 /* AccountDisplayNameLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F562A1160B800AB2315 /* AccountDisplayNameLabel.swift */; };
|
D6D79F572A1160B800AB2315 /* AccountDisplayNameLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F562A1160B800AB2315 /* AccountDisplayNameLabel.swift */; };
|
||||||
|
D6D79F592A13293200AB2315 /* BackgroundManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D79F582A13293200AB2315 /* BackgroundManager.swift */; };
|
||||||
D6D94955298963A900C59229 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D94954298963A900C59229 /* Colors.swift */; };
|
D6D94955298963A900C59229 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D94954298963A900C59229 /* Colors.swift */; };
|
||||||
D6D9498D298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D9498C298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift */; };
|
D6D9498D298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D9498C298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift */; };
|
||||||
D6D9498F298EB79400C59229 /* CopyableLable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D9498E298EB79400C59229 /* CopyableLable.swift */; };
|
D6D9498F298EB79400C59229 /* CopyableLable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D9498E298EB79400C59229 /* CopyableLable.swift */; };
|
||||||
|
@ -704,6 +705,7 @@
|
||||||
D6D79F2E2A0D6A7F00AB2315 /* StatusEditPollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusEditPollView.swift; sourceTree = "<group>"; };
|
D6D79F2E2A0D6A7F00AB2315 /* StatusEditPollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusEditPollView.swift; sourceTree = "<group>"; };
|
||||||
D6D79F522A0FFE3200AB2315 /* ToggleableButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleableButton.swift; sourceTree = "<group>"; };
|
D6D79F522A0FFE3200AB2315 /* ToggleableButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleableButton.swift; sourceTree = "<group>"; };
|
||||||
D6D79F562A1160B800AB2315 /* AccountDisplayNameLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDisplayNameLabel.swift; sourceTree = "<group>"; };
|
D6D79F562A1160B800AB2315 /* AccountDisplayNameLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDisplayNameLabel.swift; sourceTree = "<group>"; };
|
||||||
|
D6D79F582A13293200AB2315 /* BackgroundManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundManager.swift; sourceTree = "<group>"; };
|
||||||
D6D94954298963A900C59229 /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = "<group>"; };
|
D6D94954298963A900C59229 /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = "<group>"; };
|
||||||
D6D9498C298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingStatusCollectionViewCell.swift; sourceTree = "<group>"; };
|
D6D9498C298CBB4000C59229 /* TrendingStatusCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingStatusCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||||
D6D9498E298EB79400C59229 /* CopyableLable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyableLable.swift; sourceTree = "<group>"; };
|
D6D9498E298EB79400C59229 /* CopyableLable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyableLable.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1476,6 +1478,7 @@
|
||||||
D671A6BE299DA96100A81FEA /* Tusker-Bridging-Header.h */,
|
D671A6BE299DA96100A81FEA /* Tusker-Bridging-Header.h */,
|
||||||
D6D4DDCF212518A000E1C4BB /* AppDelegate.swift */,
|
D6D4DDCF212518A000E1C4BB /* AppDelegate.swift */,
|
||||||
D6CA6A91249FAD8900AD45C1 /* AudioSessionHelper.swift */,
|
D6CA6A91249FAD8900AD45C1 /* AudioSessionHelper.swift */,
|
||||||
|
D6D79F582A13293200AB2315 /* BackgroundManager.swift */,
|
||||||
D61F75B6293C119700C0B37F /* Filterer.swift */,
|
D61F75B6293C119700C0B37F /* Filterer.swift */,
|
||||||
D6E4269C2532A3E100C02E1C /* FuzzyMatcher.swift */,
|
D6E4269C2532A3E100C02E1C /* FuzzyMatcher.swift */,
|
||||||
D61F75BA293C183100C0B37F /* HTMLConverter.swift */,
|
D61F75BA293C183100C0B37F /* HTMLConverter.swift */,
|
||||||
|
@ -2091,6 +2094,7 @@
|
||||||
D6C94D892139E6EC00CB5196 /* AttachmentView.swift in Sources */,
|
D6C94D892139E6EC00CB5196 /* AttachmentView.swift in Sources */,
|
||||||
D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */,
|
D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */,
|
||||||
D6C94D872139E62700CB5196 /* LargeImageViewController.swift in Sources */,
|
D6C94D872139E62700CB5196 /* LargeImageViewController.swift in Sources */,
|
||||||
|
D6D79F592A13293200AB2315 /* BackgroundManager.swift in Sources */,
|
||||||
D6E4269D2532A3E100C02E1C /* FuzzyMatcher.swift in Sources */,
|
D6E4269D2532A3E100C02E1C /* FuzzyMatcher.swift in Sources */,
|
||||||
D6969EA0240C8384002843CE /* EmojiLabel.swift in Sources */,
|
D6969EA0240C8384002843CE /* EmojiLabel.swift in Sources */,
|
||||||
D6F6A550291F058600F496A8 /* CreateListService.swift in Sources */,
|
D6F6A550291F058600F496A8 /* CreateListService.swift in Sources */,
|
||||||
|
|
|
@ -76,6 +76,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BackgroundManager.shared.registerHandlers()
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
//
|
||||||
|
// BackgroundManager.swift
|
||||||
|
// Tusker
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 5/15/23.
|
||||||
|
// Copyright © 2023 Shadowfacts. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import BackgroundTasks
|
||||||
|
import OSLog
|
||||||
|
import ComposeUI
|
||||||
|
|
||||||
|
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "BackgroundManager")
|
||||||
|
|
||||||
|
struct BackgroundManager {
|
||||||
|
static let shared = BackgroundManager()
|
||||||
|
|
||||||
|
private init() {}
|
||||||
|
|
||||||
|
static let cleanupAttachmentsIdentifier = "\(Bundle.main.bundleIdentifier!).cleanup-attachments"
|
||||||
|
|
||||||
|
func scheduleTasks() {
|
||||||
|
BGTaskScheduler.shared.getPendingTaskRequests { requests in
|
||||||
|
if !requests.contains(where: { $0.identifier == Self.cleanupAttachmentsIdentifier }) {
|
||||||
|
let request = BGProcessingTaskRequest(identifier: Self.cleanupAttachmentsIdentifier)
|
||||||
|
do {
|
||||||
|
try BGTaskScheduler.shared.submit(request)
|
||||||
|
} catch {
|
||||||
|
logger.error("Failed to schedule cleanup attachments: \(String(describing: error), privacy: .public)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerHandlers() {
|
||||||
|
BGTaskScheduler.shared.register(forTaskWithIdentifier: Self.cleanupAttachmentsIdentifier, using: nil, launchHandler: handleCleanupAttachments(_:))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handleCleanupAttachments(_ task: BGTask) {
|
||||||
|
DraftsPersistentContainer.shared.removeOrphanedAttachments() {
|
||||||
|
task.setTaskCompleted(success: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,8 +2,10 @@
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||||
<false/>
|
<array>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER).cleanup-attachments</string>
|
||||||
|
</array>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
|
@ -33,6 +35,8 @@
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
|
<false/>
|
||||||
<key>LSApplicationCategoryType</key>
|
<key>LSApplicationCategoryType</key>
|
||||||
<string>public.app-category.social-networking</string>
|
<string>public.app-category.social-networking</string>
|
||||||
<key>LSApplicationQueriesSchemes</key>
|
<key>LSApplicationQueriesSchemes</key>
|
||||||
|
@ -141,6 +145,7 @@
|
||||||
</dict>
|
</dict>
|
||||||
<key>UIBackgroundModes</key>
|
<key>UIBackgroundModes</key>
|
||||||
<array>
|
<array>
|
||||||
|
<string>processing</string>
|
||||||
<string>remote-notification</string>
|
<string>remote-notification</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
|
|
|
@ -159,6 +159,8 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate, TuskerSceneDelegate
|
||||||
|
|
||||||
try? context.save()
|
try? context.save()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BackgroundManager.shared.scheduleTasks()
|
||||||
}
|
}
|
||||||
|
|
||||||
func showAppOrOnboardingUI(session: UISceneSession? = nil) {
|
func showAppOrOnboardingUI(session: UISceneSession? = nil) {
|
||||||
|
|
Loading…
Reference in New Issue