Skip to content

Commit 12bb3e5

Browse files
committed
Moved transcript indexing to an XPC service
1 parent 25cdb3c commit 12bb3e5

File tree

9 files changed

+458
-68
lines changed

9 files changed

+458
-68
lines changed

ConfCore/Storage.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,22 @@ public final class Storage {
2222

2323
self.realmConfig = config
2424
self.realm = try Realm(configuration: config)
25+
26+
DistributedNotificationCenter.default().addObserver(forName: .TranscriptIndexingDidStart, object: nil, queue: OperationQueue.main) { [unowned self] _ in
27+
#if DEBUG
28+
NSLog("[Storage] Locking realm autoupdates until transcript indexing is finished")
29+
#endif
30+
31+
self.realm.autorefresh = false
32+
}
33+
34+
DistributedNotificationCenter.default().addObserver(forName: .TranscriptIndexingDidStop, object: nil, queue: OperationQueue.main) { [unowned self] _ in
35+
#if DEBUG
36+
NSLog("[Storage] Realm autoupdates unlocked")
37+
#endif
38+
39+
self.realm.autorefresh = true
40+
}
2541
}
2642

2743
internal static func migrate(migration: Migration, oldVersion: UInt64) {

ConfCore/SyncEngine.swift

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,24 @@ public final class SyncEngine {
1818
public let storage: Storage
1919
public let client: AppleAPIClient
2020

21-
public let transcriptIndexer: TranscriptIndexer
21+
private lazy var transcriptIndexingConnection: NSXPCConnection = {
22+
let c = NSXPCConnection(serviceName: "io.wwdc.app.TranscriptIndexingService")
23+
24+
c.remoteObjectInterface = NSXPCInterface(with: TranscriptIndexingServiceProtocol.self)
25+
26+
return c
27+
}()
28+
29+
private var transcriptIndexingService: TranscriptIndexingServiceProtocol? {
30+
return transcriptIndexingConnection.remoteObjectProxy as? TranscriptIndexingServiceProtocol
31+
}
2232

2333
public init(storage: Storage, client: AppleAPIClient) {
2434
self.storage = storage
2535
self.client = client
2636

27-
self.transcriptIndexer = TranscriptIndexer(storage)
28-
2937
NotificationCenter.default.addObserver(forName: .SyncEngineDidSyncSessionsAndSchedule, object: nil, queue: OperationQueue.main) { [unowned self] _ in
30-
self.transcriptIndexer.downloadTranscriptsIfNeeded()
38+
self.startTranscriptIndexingIfNeeded()
3139
}
3240
}
3341

@@ -53,4 +61,12 @@ public final class SyncEngine {
5361
}
5462
}
5563

64+
private func startTranscriptIndexingIfNeeded() {
65+
guard let url = storage.realmConfig.fileURL else { return }
66+
67+
transcriptIndexingConnection.resume()
68+
69+
transcriptIndexingService?.indexTranscriptsIfNeeded(storageURL: url, schemaVersion: storage.realmConfig.schemaVersion)
70+
}
71+
5672
}

ConfCore/TranscriptIndexer.swift

Lines changed: 83 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import RealmSwift
1111
import SwiftyJSON
1212

1313
extension Notification.Name {
14-
public static let TranscriptIndexingDidStart = Notification.Name("TranscriptIndexingDidStartNotification")
15-
public static let TranscriptIndexingDidStop = Notification.Name("TranscriptIndexingDidStopNotification")
14+
public static let TranscriptIndexingDidStart = Notification.Name("io.wwdc.app.TranscriptIndexingDidStartNotification")
15+
public static let TranscriptIndexingDidStop = Notification.Name("io.wwdc.app.TranscriptIndexingDidStopNotification")
1616
}
1717

1818
public final class TranscriptIndexer {
@@ -23,35 +23,12 @@ public final class TranscriptIndexer {
2323
self.storage = storage
2424
}
2525

26-
/// Whether transcripts are currently being indexed
27-
public var isIndexingTranscripts = false {
28-
didSet {
29-
guard oldValue != isIndexingTranscripts else { return }
30-
31-
let notificationName: Notification.Name = isIndexingTranscripts ? .TranscriptIndexingDidStart : .TranscriptIndexingDidStop
32-
33-
DispatchQueue.main.async {
34-
NotificationCenter.default.post(name: notificationName, object: nil)
35-
}
36-
}
37-
}
38-
3926
/// The progress when the transcripts are being downloaded/indexed
40-
public var transcriptIndexingProgress: Progress? {
41-
didSet {
42-
isIndexingTranscripts = (transcriptIndexingProgress != nil)
43-
44-
transcriptIndexingStartedCallback?()
45-
}
46-
}
47-
48-
/// Called when transcript downloading/indexing starts,
49-
/// use `transcriptIndexingProgress` to track progress
50-
public var transcriptIndexingStartedCallback: (() -> Void)?
27+
public var transcriptIndexingProgress: Progress?
5128

5229
private let asciiWWDCURL = "http://asciiwwdc.com/"
5330

54-
fileprivate let bgThread = DispatchQueue.global(qos: .background)
31+
fileprivate let bgThread = DispatchQueue.global(qos: .utility)
5532

5633
fileprivate lazy var backgroundOperationQueue: OperationQueue = {
5734
let q = OperationQueue()
@@ -63,17 +40,16 @@ public final class TranscriptIndexer {
6340
}()
6441

6542
/// Try to download transcripts for sessions that don't have transcripts yet
66-
func downloadTranscriptsIfNeeded() {
43+
public func downloadTranscriptsIfNeeded() {
6744

68-
let transcriptedSessions = storage.realm.objects(Session.self).filter("transcript == nil AND SUBQUERY(assets, $asset, $asset.rawAssetType == %@).@count > 0", SessionAssetType.streamingVideo.rawValue)
45+
let transcriptedSessions = storage.realm.objects(Session.self).filter("year > 2012 AND transcript == nil AND SUBQUERY(assets, $asset, $asset.rawAssetType == %@).@count > 0", SessionAssetType.streamingVideo.rawValue)
6946

7047
let sessionKeys: [String] = transcriptedSessions.map({ $0.identifier })
7148

7249
self.indexTranscriptsForSessionsWithKeys(sessionKeys)
7350
}
7451

7552
func indexTranscriptsForSessionsWithKeys(_ sessionKeys: [String]) {
76-
guard !isIndexingTranscripts else { return }
7753
guard sessionKeys.count > 0 else { return }
7854

7955
transcriptIndexingProgress = Progress(totalUnitCount: Int64(sessionKeys.count))
@@ -87,6 +63,8 @@ public final class TranscriptIndexer {
8763
}
8864
}
8965

66+
fileprivate var downloadedTranscripts: [Transcript] = []
67+
9068
fileprivate func indexTranscript(for sessionNumber: String, in year: Int, primaryKey: String) {
9169
guard let url = URL(string: "\(asciiWWDCURL)\(year)//sessions/\(sessionNumber)") else { return }
9270

@@ -95,52 +73,93 @@ public final class TranscriptIndexer {
9573

9674
let task = URLSession.shared.dataTask(with: request) { [unowned self] data, response, error in
9775
guard let jsonData = data else {
98-
print("No data returned from ASCIIWWDC for \(primaryKey)")
76+
self.transcriptIndexingProgress?.completedUnitCount += 1
77+
self.checkForCompletion()
78+
79+
NSLog("No data returned from ASCIIWWDC for \(primaryKey)")
80+
9981
return
10082
}
10183

10284
self.backgroundOperationQueue.addOperation {
103-
do {
104-
let bgRealm = try Realm(configuration: self.storage.realmConfig)
105-
106-
guard let session = bgRealm.object(ofType: Session.self, forPrimaryKey: primaryKey) else { return }
107-
108-
let result = TranscriptsJSONAdapter().adapt(JSON(data: jsonData))
109-
110-
guard case .success(let transcript) = result else {
111-
NSLog("Error parsing transcript for \(primaryKey)")
112-
return
113-
}
114-
115-
bgRealm.beginWrite()
116-
bgRealm.add(transcript)
117-
session.transcript = transcript
118-
119-
try bgRealm.commitWrite()
120-
85+
defer {
12186
self.transcriptIndexingProgress?.completedUnitCount += 1
122-
} catch let error {
123-
NSLog("Error indexing transcript for \(primaryKey): \(error)")
87+
88+
self.checkForCompletion()
12489
}
12590

126-
if let progress = self.transcriptIndexingProgress {
127-
#if DEBUG
128-
NSLog("Completed: \(progress.completedUnitCount) Total: \(progress.totalUnitCount)")
129-
#endif
130-
131-
if progress.completedUnitCount >= progress.totalUnitCount - 1 {
132-
DispatchQueue.main.async {
133-
#if DEBUG
134-
NSLog("Transcript indexing finished")
135-
#endif
136-
self.isIndexingTranscripts = false
137-
}
138-
}
91+
let result = TranscriptsJSONAdapter().adapt(JSON(data: jsonData))
92+
93+
guard case .success(let transcript) = result else {
94+
NSLog("Error parsing transcript for \(primaryKey)")
95+
return
96+
}
97+
98+
DispatchQueue.main.sync {
99+
self.downloadedTranscripts.append(transcript)
139100
}
140101
}
141102
}
142103

143104
task.resume()
144105
}
145106

107+
private func checkForCompletion() {
108+
guard let progress = self.transcriptIndexingProgress else { return }
109+
110+
#if DEBUG
111+
NSLog("Completed: \(progress.completedUnitCount) Total: \(progress.totalUnitCount)")
112+
#endif
113+
114+
if progress.completedUnitCount >= progress.totalUnitCount - 1 {
115+
DispatchQueue.main.async {
116+
#if DEBUG
117+
NSLog("Transcript indexing finished")
118+
#endif
119+
120+
self.storeDownloadedTranscripts()
121+
}
122+
}
123+
}
124+
125+
private var isStoring = false
126+
127+
private func storeDownloadedTranscripts() {
128+
guard !isStoring else { return }
129+
isStoring = true
130+
131+
DispatchQueue.main.async {
132+
DistributedNotificationCenter.default().post(name: .TranscriptIndexingDidStart, object: nil)
133+
}
134+
135+
self.backgroundOperationQueue.addOperation { [unowned self] in
136+
guard let realm = try? Realm(configuration: self.storage.realmConfig) else { return }
137+
138+
realm.beginWrite()
139+
140+
self.downloadedTranscripts.forEach { transcript in
141+
guard let session = realm.object(ofType: Session.self, forPrimaryKey: transcript.identifier) else {
142+
NSLog("Session not found for \(transcript.identifier)")
143+
return
144+
}
145+
146+
session.transcript = transcript
147+
148+
realm.add(transcript)
149+
}
150+
151+
self.downloadedTranscripts.removeAll()
152+
153+
do {
154+
try realm.commitWrite()
155+
156+
DispatchQueue.main.async {
157+
DistributedNotificationCenter.default().post(name: .TranscriptIndexingDidStop, object: nil)
158+
}
159+
} catch {
160+
NSLog("Error writing indexed transcripts to storage: \(error)")
161+
}
162+
}
163+
}
164+
146165
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>CFBundleDevelopmentRegion</key>
6+
<string>en</string>
7+
<key>CFBundleDisplayName</key>
8+
<string>TranscriptIndexingService</string>
9+
<key>CFBundleExecutable</key>
10+
<string>$(EXECUTABLE_NAME)</string>
11+
<key>NSAppTransportSecurity</key>
12+
<dict>
13+
<key>NSAllowsArbitraryLoads</key>
14+
<true/>
15+
</dict>
16+
<key>CFBundleIdentifier</key>
17+
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
18+
<key>CFBundleInfoDictionaryVersion</key>
19+
<string>6.0</string>
20+
<key>CFBundleName</key>
21+
<string>$(PRODUCT_NAME)</string>
22+
<key>CFBundlePackageType</key>
23+
<string>XPC!</string>
24+
<key>CFBundleShortVersionString</key>
25+
<string>1.0</string>
26+
<key>CFBundleVersion</key>
27+
<string>1</string>
28+
<key>NSHumanReadableCopyright</key>
29+
<string>Copyright © 2017 Guilherme Rambo. All rights reserved.</string>
30+
<key>XPCService</key>
31+
<dict>
32+
<key>ServiceType</key>
33+
<string>Application</string>
34+
</dict>
35+
</dict>
36+
</plist>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
//
2+
// Use this file to import your target's public headers that you would like to expose to Swift.
3+
//
4+
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//
2+
// TranscriptIndexingService.swift
3+
// WWDC
4+
//
5+
// Created by Guilherme Rambo on 28/05/17.
6+
// Copyright © 2017 Guilherme Rambo. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import ConfCore
11+
import RealmSwift
12+
13+
final class TranscriptIndexingService: NSObject, TranscriptIndexingServiceProtocol {
14+
15+
private var transcriptIndexer: TranscriptIndexer!
16+
17+
func indexTranscriptsIfNeeded(storageURL: URL, schemaVersion: UInt64) {
18+
if transcriptIndexer == nil {
19+
do {
20+
let config = Realm.Configuration(fileURL: storageURL, schemaVersion: schemaVersion)
21+
let storage = try Storage(config)
22+
transcriptIndexer = TranscriptIndexer(storage)
23+
} catch {
24+
NSLog("[TranscriptIndexingService] Error initializing: \(error)")
25+
return
26+
}
27+
}
28+
29+
transcriptIndexer.downloadTranscriptsIfNeeded()
30+
}
31+
32+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//
2+
// TranscriptIndexingServiceProtocol.swift
3+
// WWDC
4+
//
5+
// Created by Guilherme Rambo on 28/05/17.
6+
// Copyright © 2017 Guilherme Rambo. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
@objc protocol TranscriptIndexingServiceProtocol: NSObjectProtocol {
12+
13+
func indexTranscriptsIfNeeded(storageURL: URL, schemaVersion: UInt64)
14+
15+
}

0 commit comments

Comments
 (0)