Skip to content

Commit 9be85f1

Browse files
committed
EPC: Begin stashing storage mng facilities for later
Removed: - persisting input queue - persisting output queue Changed: - fire-and-forget POSTing of events There are some open questions around storage behavior we need to resolve.
1 parent 0699c3e commit 9be85f1

File tree

4 files changed

+122
-280
lines changed

4 files changed

+122
-280
lines changed

WMF Framework/Event Platform Client/EPCNetworkManager.swift

Lines changed: 26 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,38 @@
22
import Foundation
33

44
class EPCNetworkManager: EPCNetworkManaging {
5-
6-
private let storageManager: EPCStorageManaging
5+
76
private let session: Session
87
private let operationQueue: OperationQueue
8+
9+
private var outputQueue = [(url: URL, body: NSDictionary)]()
910

10-
init(storageManager: EPCStorageManaging, session: Session = Session.shared) {
11-
self.storageManager = storageManager
11+
init(session: Session = Session.shared) {
1212
self.session = session
1313

1414
operationQueue = OperationQueue()
1515
operationQueue.maxConcurrentOperationCount = 1
1616
}
17-
18-
func httpPost(url: URL, body: NSDictionary) {
19-
storageManager.createAndSavePost(with: url, body: body)
17+
18+
func schedulePost(url: URL, body: NSDictionary) {
19+
outputQueue.append((url: url, body: body))
2020
}
2121

2222
func httpDownload(url: URL, completion: @escaping (Data?) -> Void) {
23-
httpDownload(url: url, maxAttempts: 5, attemptDelay: 2, completion: completion)
23+
httpDownload(url: url, retriesRemaining: 5, attemptDelay: 2, completion: completion)
2424
}
2525

2626
/**
2727
* Download data over HTTP with capacity for retries
2828
* - Parameters:
2929
* - url: where to request data from
30-
* - maxAttempts: maximum number of retries allowed for this download operation
30+
* - maxRetries: maximum number of retries allowed for this download operation
3131
* - attemptDelay: time between each retry
3232
* - completion: what to do with the downloaded data
3333
*/
34-
private func httpDownload(url: URL, maxAttempts: Int, attemptDelay: TimeInterval, completion: @escaping (Data?) -> Void) {
34+
private func httpDownload(url: URL, retriesRemaining: Int, attemptDelay: TimeInterval, completion: @escaping (Data?) -> Void) {
3535

36-
guard maxAttempts > 0 else {
36+
if retriesRemaining < 0 {
3737
completion(nil)
3838
return
3939
}
@@ -42,7 +42,7 @@ class EPCNetworkManager: EPCNetworkManaging {
4242

4343
let failureBlock = {
4444
dispatchOnMainQueueAfterDelayInSeconds(attemptDelay) {
45-
self.httpDownload(url: url, maxAttempts: maxAttempts - 1, attemptDelay: attemptDelay, completion: completion)
45+
self.httpDownload(url: url, retriesRemaining: retriesRemaining - 1, attemptDelay: attemptDelay, completion: completion)
4646
}
4747
}
4848

@@ -69,83 +69,32 @@ class EPCNetworkManager: EPCNetworkManaging {
6969
task?.resume()
7070
}
7171

72-
func httpTryPost(_ completion: (() -> Void)? = nil) {
72+
func httpTryPost() {
7373

7474
let operation = AsyncBlockOperation { (operation) in
75-
76-
self.storageManager.deleteStalePosts()
77-
let postItems = self.storageManager.fetchPostsForPosting()
78-
79-
self.postItems(postItems) {
80-
operation.finish()
81-
}
82-
}
83-
84-
operationQueue.addOperation(operation)
85-
guard let completion = completion else {
86-
return
87-
}
88-
let completionBlockOp = BlockOperation(block: completion)
89-
completionBlockOp.addDependency(operation)
90-
operationQueue.addOperation(completion)
91-
}
92-
93-
private func postItems(_ items: [EPCPost], completion: @escaping () -> Void) {
9475

95-
let taskGroup = WMFTaskGroup()
96-
97-
var completedIDs = Set<NSManagedObjectID>()
98-
var failedIDs = Set<NSManagedObjectID>()
99-
100-
for item in items {
101-
let moid = item.objectID
102-
guard let urlAndBody = storageManager.urlAndBodyOfPost(item) else {
103-
failedIDs.insert(moid)
104-
continue
105-
}
106-
taskGroup.enter()
107-
let userAgent = item.userAgent ?? WikipediaAppUtils.versionedUserAgent()
108-
submit(url: urlAndBody.url, payload: urlAndBody.body, userAgent: userAgent) { (error) in
109-
if let error = error {
110-
if error != .network {
111-
failedIDs.insert(moid)
112-
}
113-
} else {
114-
completedIDs.insert(moid)
76+
var queuedEvent: (url: URL, body: NSDictionary)?
77+
while !self.outputQueue.isEmpty {
78+
queuedEvent = self.outputQueue.remove(at: 0)
79+
if let queuedEvent = queuedEvent {
80+
self.submit(url: queuedEvent.url, payload: queuedEvent.body)
11581
}
116-
taskGroup.leave()
11782
}
83+
84+
operation.finish()
11885
}
11986

120-
taskGroup.waitInBackground {
121-
if (completedIDs.count == items.count) {
122-
DDLogDebug("EPCNetworkManager: All records succeeded")
123-
} else {
124-
DDLogDebug("EPCNetworkManager: Some records failed")
125-
}
126-
self.storageManager.updatePosts(completedIDs: completedIDs, failedIDs: failedIDs)
127-
completion()
128-
}
87+
operationQueue.addOperation(operation)
12988
}
130-
131-
private func submit(url: URL, payload: NSDictionary, userAgent: String, completion: @escaping (EventLoggingError?) -> Void) {
13289

133-
var request = session.request(with: url, method: .post, bodyParameters: payload, bodyEncoding: .json)
134-
request.setValue(userAgent, forHTTPHeaderField: "User-Agent")
90+
private func submit(url: URL, payload: Any? = nil) {
91+
let request = session.request(with: url, method: .post, bodyParameters: payload, bodyEncoding: .json)
13592
let task = session.dataTask(with: request, completionHandler: { (_, response, error) in
136-
guard error == nil,
137-
let httpResponse = response as? HTTPURLResponse,
138-
httpResponse.statusCode / 100 == 2 else {
139-
if let error = error as NSError?, error.domain == NSURLErrorDomain {
140-
completion(EventLoggingError.network)
141-
} else {
142-
completion(EventLoggingError.generic)
143-
}
144-
return
93+
if error != nil {
94+
DDLogError("EPCNetworkManager: An error occurred sending the request")
14595
}
146-
completion(nil)
14796
})
14897
task?.resume()
14998
}
150-
99+
151100
}

WMF Framework/Event Platform Client/EPCStorageManager.swift

Lines changed: 1 addition & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class EPCStorageManager: EPCStorageManaging {
1111
private var libraryValueCache: [String: NSCoding] = [:]
1212
private let cachesLibraryValues: Bool
1313

14-
private let pruningAge: TimeInterval = 60*60*24*30 // 30 days
14+
private let pruningAge: TimeInterval = 60*60*12 // 12 hours
1515
private let postBatchSize: Int
1616

1717
private let legacyEventLoggingService: EventLoggingService
@@ -95,120 +95,11 @@ class EPCStorageManager: EPCStorageManaging {
9595
var sharingUsageData: Bool {
9696
return legacyEventLoggingService.isEnabled
9797
}
98-
99-
func createAndSavePost(with url: URL, body: NSDictionary) {
100-
101-
let now = Date()
102-
perform { moc in
103-
if let post = NSEntityDescription.insertNewObject(forEntityName: "EPCPost", into: self.managedObjectContext) as? EPCPost {
104-
post.body = body
105-
post.recorded = now
106-
post.userAgent = WikipediaAppUtils.versionedUserAgent()
107-
post.url = url
108-
109-
DDLogDebug("EPCStorageManaager: \(post.objectID) recorded!")
110-
111-
self.save(moc)
112-
}
113-
}
114-
}
115-
116-
func updatePosts(completedIDs: Set<NSManagedObjectID>, failedIDs: Set<NSManagedObjectID>) {
117-
118-
perform { moc in
119-
for moid in completedIDs {
120-
let mo = try? moc.existingObject(with: moid)
121-
guard let post = mo as? EPCPost else {
122-
continue
123-
}
124-
125-
post.posted = Date()
126-
}
127-
128-
for moid in failedIDs {
129-
let mo = try? moc.existingObject(with: moid)
130-
guard let post = mo as? EPCPost else {
131-
continue
132-
}
133-
134-
post.failed = true
135-
}
136-
137-
self.save(moc)
138-
}
139-
140-
}
141-
142-
func urlAndBodyOfPost(_ post: EPCPost) -> (url: URL, body: NSDictionary)? {
143-
var result: (url: URL, body: NSDictionary)?
144-
performAndWait { moc in
145-
guard let url = post.url,
146-
let body = post.body as? NSDictionary else {
147-
return
148-
}
149-
150-
result = (url: url, body: body)
151-
}
152-
153-
return result
154-
}
155-
156-
func deleteStalePosts() {
157-
158-
perform { (moc) in
159-
160-
let pruneFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "EPCPost")
161-
pruneFetch.returnsObjectsAsFaults = false
162-
163-
let pruneDate = Date().addingTimeInterval(-(self.pruningAge)) as NSDate
164-
pruneFetch.predicate = NSPredicate(format: "(recorded < %@) OR (posted != nil) OR (failed == TRUE)", pruneDate)
165-
let delete = NSBatchDeleteRequest(fetchRequest: pruneFetch)
166-
delete.resultType = .resultTypeCount
167-
168-
do {
169-
let result = try self.managedObjectContext.execute(delete)
170-
guard let deleteResult = result as? NSBatchDeleteResult else {
171-
DDLogError("EPCStorageManager: Could not read NSBatchDeleteResult")
172-
return
173-
}
174-
175-
guard let count = deleteResult.result as? Int else {
176-
DDLogError("EPCStorageManager: Could not read NSBatchDeleteResult count")
177-
return
178-
}
179-
DDLogInfo("EPCStorageManager: Pruned \(count) events")
180-
181-
} catch let error {
182-
DDLogError("EPCStorageManager: Error pruning events: \(error.localizedDescription)")
183-
}
184-
185-
}
186-
}
187-
188-
func fetchPostsForPosting() -> [EPCPost] {
189-
190-
var events: [EPCPost] = []
191-
performAndWait { (moc) in
192-
let fetch: NSFetchRequest<EPCPost> = EPCPost.fetchRequest()
193-
fetch.sortDescriptors = [NSSortDescriptor(keyPath: \EPCPost.recorded, ascending: true)]
194-
fetch.predicate = NSPredicate(format: "(posted == nil) AND (failed != TRUE)")
195-
fetch.fetchLimit = self.postBatchSize
196-
197-
do {
198-
events = try moc.fetch(fetch)
199-
} catch let error {
200-
DDLogError(error.localizedDescription)
201-
}
202-
}
203-
204-
return events
205-
}
20698
}
20799

208100
//MARK: Utility methods duplicated from EventLoggingService
209101

210102
private extension EPCStorageManager {
211-
212103
func save(_ moc: NSManagedObjectContext) {
213104
guard moc.hasChanges else {
214105
return

0 commit comments

Comments
 (0)