Skip to content

Commit b473e97

Browse files
committed
Library: fix duplicate resolver watchdog timeout
1 parent 2dcf6e1 commit b473e97

File tree

3 files changed

+150
-45
lines changed

3 files changed

+150
-45
lines changed

Amperfy/Common/AppDelegate.swift

+4-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
2424
lazy var library = {
2525
return LibraryStorage(context: persistentStorage.context)
2626
}()
27+
lazy var duplicateEntitiesResolver = {
28+
return DuplicateEntitiesResolver(persistentStorage: persistentStorage)
29+
}()
2730
lazy var eventLogger = {
2831
return EventLogger(alertDisplayer: self, persistentContainer: persistentStorage.persistentContainer)
2932
}()
@@ -179,7 +182,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
179182
return true
180183
}
181184
libraryUpdater.performBlockingLibraryUpdatesIfNeeded()
182-
library.resolveDuplicates()
185+
duplicateEntitiesResolver.start()
183186
artworkDownloadManager.start()
184187
playableDownloadManager.start()
185188
backgroundLibrarySyncer.start()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import Foundation
2+
import os.log
3+
4+
class DuplicateEntitiesResolver {
5+
6+
private let log = OSLog(subsystem: AppDelegate.name, category: "DuplicateEntitiesResolver")
7+
private let persistentStorage: PersistentStorage
8+
private let activeDispatchGroup = DispatchGroup()
9+
private let mainFlowSemaphore = DispatchSemaphore(value: 1)
10+
private var isRunning = false
11+
private var isActive = false
12+
13+
init(persistentStorage: PersistentStorage) {
14+
self.persistentStorage = persistentStorage
15+
}
16+
17+
func start() {
18+
isRunning = true
19+
if !isActive {
20+
isActive = true
21+
resolveDuplicatesInBackground()
22+
}
23+
}
24+
25+
func stopAndWait() {
26+
isRunning = false
27+
activeDispatchGroup.wait()
28+
}
29+
30+
private func resolveDuplicatesInBackground() {
31+
DispatchQueue.global().async {
32+
self.activeDispatchGroup.enter()
33+
os_log("start", log: self.log, type: .info)
34+
35+
if self.isRunning {
36+
self.mainFlowSemaphore.wait()
37+
self.persistentStorage.persistentContainer.performBackgroundTask() { (context) in
38+
defer { self.mainFlowSemaphore.signal() }
39+
let library = LibraryStorage(context: context)
40+
let duplicates = library.findDuplicates(for: Genre.typeName)
41+
library.resolveGenresDuplicates(duplicates: duplicates)
42+
library.saveContext()
43+
}
44+
}
45+
46+
if self.isRunning {
47+
self.mainFlowSemaphore.wait()
48+
self.persistentStorage.persistentContainer.performBackgroundTask() { (context) in
49+
defer { self.mainFlowSemaphore.signal() }
50+
let library = LibraryStorage(context: context)
51+
let duplicates = library.findDuplicates(for: Artist.typeName)
52+
library.resolveArtistsDuplicates(duplicates: duplicates)
53+
library.saveContext()
54+
}
55+
}
56+
57+
if self.isRunning {
58+
self.mainFlowSemaphore.wait()
59+
self.persistentStorage.persistentContainer.performBackgroundTask() { (context) in
60+
defer { self.mainFlowSemaphore.signal() }
61+
let library = LibraryStorage(context: context)
62+
let duplicates = library.findDuplicates(for: Album.typeName)
63+
library.resolveAlbumsDuplicates(duplicates: duplicates)
64+
library.saveContext()
65+
}
66+
}
67+
68+
if self.isRunning {
69+
self.mainFlowSemaphore.wait()
70+
self.persistentStorage.persistentContainer.performBackgroundTask() { (context) in
71+
defer { self.mainFlowSemaphore.signal() }
72+
let library = LibraryStorage(context: context)
73+
let duplicates = library.findDuplicates(for: Song.typeName)
74+
library.resolveSongsDuplicates(duplicates: duplicates)
75+
library.saveContext()
76+
}
77+
}
78+
79+
if self.isRunning {
80+
self.mainFlowSemaphore.wait()
81+
self.persistentStorage.persistentContainer.performBackgroundTask() { (context) in
82+
defer { self.mainFlowSemaphore.signal() }
83+
let library = LibraryStorage(context: context)
84+
let duplicates = library.findDuplicates(for: PodcastEpisode.typeName)
85+
library.resolvePodcastEpisodesDuplicates(duplicates: duplicates)
86+
library.saveContext()
87+
}
88+
}
89+
90+
if self.isRunning {
91+
self.mainFlowSemaphore.wait()
92+
self.persistentStorage.persistentContainer.performBackgroundTask() { (context) in
93+
defer { self.mainFlowSemaphore.signal() }
94+
let library = LibraryStorage(context: context)
95+
let duplicates = library.findDuplicates(for: Podcast.typeName)
96+
library.resolvePodcastsDuplicates(duplicates: duplicates)
97+
library.saveContext()
98+
}
99+
}
100+
101+
if self.isRunning {
102+
self.mainFlowSemaphore.wait()
103+
self.persistentStorage.persistentContainer.performBackgroundTask() { (context) in
104+
defer { self.mainFlowSemaphore.signal() }
105+
let library = LibraryStorage(context: context)
106+
let duplicates = library.findDuplicates(for: Playlist.typeName).filter{ $0.id != "" }
107+
library.resolvePlaylistsDuplicates(duplicates: duplicates)
108+
library.saveContext()
109+
}
110+
}
111+
112+
os_log("stopped", log: self.log, type: .info)
113+
self.isActive = false
114+
self.activeDispatchGroup.leave()
115+
}
116+
}
117+
118+
}

Amperfy/Storage/LibraryStorage.swift

+28-44
Original file line numberDiff line numberDiff line change
@@ -32,59 +32,53 @@ class LibraryStorage: PlayableFileCachable {
3232
self.context = context
3333
}
3434

35-
func resolveDuplicates() {
36-
var isDuplicateFound = false
37-
38-
let allGenreDuplicates = findDuplicates(for: Genre.typeName)
39-
allGenreDuplicates.forEach {
35+
func resolveGenresDuplicates(duplicates: [LibraryDuplicateInfo]) {
36+
duplicates.forEach {
4037
var genreDuplicates = getGenres(id: $0.id)
4138
if genreDuplicates.count > 1 {
42-
isDuplicateFound = true
4339
let leadGenre = genreDuplicates.removeFirst()
4440
os_log("Duplicated Genre (count %i) (id: %s): %s", log: log, type: .info, $0.count, $0.id, leadGenre.name)
4541
for genre in genreDuplicates {
4642
genre.managedObject.passOwnership(to: leadGenre.managedObject)
4743
context.delete(genre.managedObject)
4844
}
4945
}
50-
saveContext()
5146
}
52-
53-
let allArtistDuplicates = findDuplicates(for: Artist.typeName)
54-
allArtistDuplicates.forEach {
55-
isDuplicateFound = true
47+
}
48+
49+
func resolveArtistsDuplicates(duplicates: [LibraryDuplicateInfo]) {
50+
duplicates.forEach {
5651
var artistDuplicates = getArtists(id: $0.id)
5752
let leadArtist = artistDuplicates.removeFirst()
5853
os_log("Duplicated Artist (count %i) (id: %s): %s", log: log, type: .info, $0.count, $0.id, leadArtist.name)
5954
for artist in artistDuplicates {
6055
artist.managedObject.passOwnership(to: leadArtist.managedObject)
6156
context.delete(artist.managedObject)
6257
}
63-
saveContext()
6458
}
65-
66-
let allAlbumDuplicates = findDuplicates(for: Album.typeName)
67-
allAlbumDuplicates.forEach {
68-
isDuplicateFound = true
59+
}
60+
61+
62+
func resolveAlbumsDuplicates(duplicates: [LibraryDuplicateInfo]) {
63+
duplicates.forEach {
6964
var albumDuplicates = getAlbums(id: $0.id)
7065
let leadAlbum = albumDuplicates.removeFirst()
7166
os_log("Duplicated Album (count %i) (id: %s): %s", log: log, type: .info, $0.count, $0.id, leadAlbum.name)
7267
for album in albumDuplicates {
7368
album.managedObject.passOwnership(to: leadAlbum.managedObject)
7469
context.delete(album.managedObject)
7570
}
76-
saveContext()
7771
}
78-
79-
let allSongDuplicates = findDuplicates(for: Song.typeName)
80-
allSongDuplicates.forEach {
81-
isDuplicateFound = true
72+
}
73+
74+
func resolveSongsDuplicates(duplicates: [LibraryDuplicateInfo]) {
75+
duplicates.forEach {
8276
var songDuplicates = getSongs(id: $0.id)
8377
let leadSong = songDuplicates.removeFirst()
8478
os_log("Duplicated Song (count %i) (id: %s): %s", log: log, type: .info, $0.count, $0.id, leadSong.displayString)
8579
for song in songDuplicates {
8680
song.managedObject.passOwnership(to: leadSong.managedObject)
87-
deleteCache(ofPlayable: leadSong)
81+
deleteCache(ofPlayable: song)
8882
if let embeddedArtwork = song.managedObject.embeddedArtwork {
8983
context.delete(embeddedArtwork)
9084
}
@@ -93,12 +87,11 @@ class LibraryStorage: PlayableFileCachable {
9387
}
9488
context.delete(song.managedObject)
9589
}
96-
saveContext()
9790
}
98-
99-
let allPodcastEpisodesDuplicates = findDuplicates(for: PodcastEpisode.typeName)
100-
allPodcastEpisodesDuplicates.forEach {
101-
isDuplicateFound = true
91+
}
92+
93+
func resolvePodcastEpisodesDuplicates(duplicates: [LibraryDuplicateInfo]) {
94+
duplicates.forEach {
10295
var podcastEpisodesDuplicates = getPodcastEpisodes(id: $0.id)
10396
let leadPodcastEpisodes = podcastEpisodesDuplicates.removeFirst()
10497
os_log("Duplicated Podcast Episode (count %i) (id: %s): %s", log: log, type: .info, $0.count, $0.id, leadPodcastEpisodes.displayString)
@@ -113,39 +106,30 @@ class LibraryStorage: PlayableFileCachable {
113106
}
114107
context.delete(podcastEpisode.managedObject)
115108
}
116-
saveContext()
117109
}
118-
119-
let allPodcastDuplicates = findDuplicates(for: Podcast.typeName)
120-
allPodcastDuplicates.forEach {
121-
isDuplicateFound = true
110+
}
111+
112+
func resolvePodcastsDuplicates(duplicates: [LibraryDuplicateInfo]) {
113+
duplicates.forEach {
122114
var podcastDuplicates = getPodcasts(id: $0.id)
123115
let leadPodcast = podcastDuplicates.removeFirst()
124116
os_log("Duplicated Podcast (count %i) (id: %s): %s", log: log, type: .info, $0.count, $0.id, leadPodcast.name)
125117
for podcast in podcastDuplicates {
126118
podcast.managedObject.passOwnership(to: leadPodcast.managedObject)
127119
context.delete(podcast.managedObject)
128120
}
129-
saveContext()
130121
}
131-
132-
let allPlaylistDuplicates = findDuplicates(for: Playlist.typeName).filter{ $0.id != "" }
133-
allPlaylistDuplicates.forEach {
134-
isDuplicateFound = true
122+
}
123+
124+
func resolvePlaylistsDuplicates(duplicates: [LibraryDuplicateInfo]) {
125+
duplicates.forEach {
135126
var playlistDuplicates = getPlaylists(id: $0.id)
136127
let leadPlaylist = playlistDuplicates.removeFirst()
137128
os_log("Duplicated Playlist (count %i) (id: %s): %s", log: log, type: .info, $0.count, $0.id, leadPlaylist.name)
138129
for playlist in playlistDuplicates {
139130
playlist.managedObject.passOwnership(to: leadPlaylist.managedObject)
140131
deletePlaylist(playlist)
141132
}
142-
saveContext()
143-
}
144-
145-
if isDuplicateFound {
146-
os_log("Duplicate Scan: Some duplicates have been found", log: log, type: .info)
147-
} else {
148-
os_log("Duplicate Scan: No duplicates have been found", log: log, type: .info)
149133
}
150134
}
151135

0 commit comments

Comments
 (0)