Skip to content

Commit 238994f

Browse files
author
Ernest Fan
authored
Merge pull request #1610 from NYPL-Simplified/release/simplye/3.9.1
Merge SE 3.9.1 release branch to master
2 parents 8ea4d55 + 2dc9eb5 commit 238994f

57 files changed

Lines changed: 1456 additions & 935 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Axis-iOS

CardCreator-iOS

NYPLAEToolkit

Simplified.xcodeproj/project.pbxproj

Lines changed: 130 additions & 47 deletions
Large diffs are not rendered by default.

Simplified.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
//
2+
// NYPLAudiobookBookmarksBusinessLogic.swift
3+
// Simplified
4+
//
5+
// Created by Ernest Fan on 2022-08-12.
6+
// Copyright © 2022 NYPL. All rights reserved.
7+
//
8+
#if FEATURE_AUDIOBOOKS
9+
import Foundation
10+
import NYPLAudiobookToolkit
11+
12+
class NYPLAudiobookBookmarksBusinessLogic: NYPLAudiobookBookmarksBusinessLogicDelegate {
13+
14+
// MARK: - Properties
15+
16+
var bookmarks: [NYPLAudiobookBookmark]
17+
18+
let book: NYPLBook
19+
private let drmDeviceID: String?
20+
private let bookRegistry: NYPLAudiobookRegistryProvider
21+
private let currentLibraryAccountProvider: NYPLCurrentLibraryAccountProvider
22+
private let annotationsSynchronizer: NYPLAnnotationSyncing.Type
23+
24+
var bookmarksCount: Int {
25+
return bookmarks.count
26+
}
27+
28+
// MARK: - Init
29+
30+
init(book: NYPLBook,
31+
drmDeviceID: String?,
32+
bookRegistryProvider: NYPLAudiobookRegistryProvider,
33+
currentLibraryAccountProvider: NYPLCurrentLibraryAccountProvider,
34+
annotationsSynchronizer: NYPLAnnotationSyncing.Type) {
35+
self.bookmarks = [NYPLAudiobookBookmark]()
36+
self.book = book
37+
self.drmDeviceID = drmDeviceID
38+
self.bookRegistry = bookRegistryProvider
39+
self.currentLibraryAccountProvider = currentLibraryAccountProvider
40+
self.annotationsSynchronizer = annotationsSynchronizer
41+
}
42+
43+
// MARK: - NYPLAudiobookBookmarksBusinessLogicDelegate
44+
45+
func bookmark(at index: Int) -> NYPLAudiobookBookmark? {
46+
guard index >= 0 && index < bookmarksCount else {
47+
return nil
48+
}
49+
50+
return bookmarks[index]
51+
}
52+
53+
func addAudiobookBookmark(_ chapterLocation: ChapterLocation) {
54+
// Check if bookmark already existing
55+
guard bookmarkExisting(at: chapterLocation) == nil else {
56+
return
57+
}
58+
59+
// Create audiobook bookmark with given location
60+
let bookmark = NYPLAudiobookBookmark(chapterLocation: chapterLocation,
61+
device: drmDeviceID,
62+
creationTime: Date())
63+
64+
// Store bookmark to local storage
65+
bookmarks.append(bookmark)
66+
bookmarks.sort{ $0 < $1 }
67+
68+
// Upload bookmark to server and store to local storage
69+
postBookmark(bookmark)
70+
71+
// TODO: Should this function return bookmark for UI update?
72+
}
73+
74+
/// Delete a bookmark from local storage and server if sync permission is granted
75+
/// - Parameter index: The index of the bookmark.
76+
func deleteAudiobookBookmark(at index: Int) {
77+
guard index >= 0 && index < bookmarks.count else {
78+
return
79+
}
80+
81+
let bookmark = bookmarks.remove(at: index)
82+
didDeleteBookmark(bookmark)
83+
84+
// TODO: Should this function return bookmark for UI update?
85+
}
86+
87+
/// Sync bookmarks from server and filter results with local bookmarks and deleted bookmarks.
88+
/// - Parameter completion: completion block to be executed when sync succeed or fail.
89+
func syncBookmarks(completion: @escaping (Bool) -> ()) {
90+
NYPLReachability.shared()?.reachability(for: NYPLConfiguration.mainFeedURL,
91+
timeoutInternal: 8.0,
92+
handler: { (reachable) in
93+
guard reachable else {
94+
self.handleBookmarksSyncFail(message: "Error: host was not reachable for bookmark sync attempt.",
95+
completion: completion)
96+
return
97+
}
98+
99+
Log.debug(#file, "Syncing bookmarks...")
100+
// First check for and upload any local bookmarks that have never been saved to the server.
101+
// Wait til that's finished, then download the server's bookmark list and filter out any that can be deleted.
102+
let localBookmarks = self.bookRegistry.audiobookBookmarks(for: self.book.identifier)
103+
self.annotationsSynchronizer.uploadLocalBookmarks(localBookmarks, forBook: self.book.identifier) { (bookmarksUploaded, bookmarksFailedToUpload) in
104+
105+
for localBookmark in localBookmarks {
106+
for uploadedBookmark in bookmarksUploaded {
107+
if localBookmark.isEqual(uploadedBookmark) {
108+
self.bookRegistry.replaceAudiobookBookmark(localBookmark,
109+
with: uploadedBookmark,
110+
for: self.book.identifier)
111+
}
112+
}
113+
}
114+
115+
self.annotationsSynchronizer.getServerBookmarks(of: NYPLAudiobookBookmark.self,
116+
forBook: self.book.identifier,
117+
publication: nil,
118+
atURL: self.book.annotationsURL) { serverBookmarks in
119+
guard let serverBookmarks = serverBookmarks else {
120+
self.handleBookmarksSyncFail(message: "Ending sync without running completion. Returning original list of bookmarks.",
121+
completion: completion)
122+
return
123+
}
124+
125+
Log.debug(#file, serverBookmarks.count == 0 ? "No server bookmarks" : "Server bookmarks count: \(serverBookmarks.count)")
126+
127+
self.updateLocalBookmarks(serverBookmarks: serverBookmarks,
128+
localBookmarks: localBookmarks,
129+
bookmarksFailedToUpload: bookmarksFailedToUpload)
130+
{ [weak self] in
131+
guard let self = self else {
132+
completion(false)
133+
return
134+
}
135+
self.bookmarks = self.bookRegistry.audiobookBookmarks(for: self.book.identifier)
136+
completion(true)
137+
}
138+
}
139+
}
140+
141+
})
142+
}
143+
144+
// MARK: - Helper
145+
146+
/// Store a bookmark to local storage and upload it to server if sync permission is granted
147+
/// - Parameter bookmark: The bookmark to be stored and uploaded.
148+
private func postBookmark(_ bookmark: NYPLAudiobookBookmark) {
149+
guard
150+
let currentAccount = currentLibraryAccountProvider.currentAccount,
151+
let accountDetails = currentAccount.details,
152+
accountDetails.syncPermissionGranted else {
153+
self.bookRegistry.addAudiobookBookmark(bookmark, for: book.identifier)
154+
return
155+
}
156+
157+
annotationsSynchronizer.postBookmark(bookmark, forBookID: book.identifier) { [weak self] serverAnnotationID in
158+
Log.info(#function, serverAnnotationID != nil ? "Bookmark upload succeed" : "Bookmark failed to upload")
159+
guard let self = self else {
160+
return
161+
}
162+
bookmark.annotationId = serverAnnotationID
163+
self.bookRegistry.addAudiobookBookmark(bookmark, for: self.book.identifier)
164+
}
165+
}
166+
167+
/// Delete a bookmark from local storage and server if sync permission is granted
168+
/// - Parameter bookmark: The bookmark to be removed.
169+
private func didDeleteBookmark(_ bookmark: NYPLAudiobookBookmark) {
170+
bookRegistry.deleteAudiobookBookmark(bookmark, for: book.identifier)
171+
172+
guard let currentAccount = currentLibraryAccountProvider.currentAccount,
173+
let details = currentAccount.details,
174+
let annotationId = bookmark.annotationId else {
175+
Log.info(#file, "Delete on Server skipped: Annotation ID did not exist for bookmark.")
176+
return
177+
}
178+
179+
if details.syncPermissionGranted && annotationId.count > 0 {
180+
annotationsSynchronizer.deleteBookmark(annotationId: annotationId) { (success) in
181+
Log.info(#file, success ?
182+
"Bookmark successfully deleted" :
183+
"Failed to delete bookmark from server. Will attempt again on next Sync")
184+
}
185+
}
186+
}
187+
188+
func updateLocalBookmarks(serverBookmarks: [NYPLAudiobookBookmark],
189+
localBookmarks: [NYPLAudiobookBookmark],
190+
bookmarksFailedToUpload: [NYPLAudiobookBookmark],
191+
completion: @escaping () -> ())
192+
{
193+
// Bookmarks that are present on the client, and have a corresponding version on the server
194+
// with matching annotation ID's should be kept on the client.
195+
var localBookmarksToKeep = [NYPLAudiobookBookmark]()
196+
// Bookmarks that are present on the server, but not the client, should be added to this
197+
// client as long as they were not created on this device originally.
198+
var serverBookmarksToKeep = serverBookmarks
199+
// Bookmarks present on the server, that were originally created on this device,
200+
// and are no longer present on the client, should be deleted on the server.
201+
var serverBookmarksToDelete = [NYPLAudiobookBookmark]()
202+
203+
for serverBookmark in serverBookmarks {
204+
let matched = localBookmarks.contains{ $0.annotationId == serverBookmark.annotationId }
205+
206+
if matched {
207+
localBookmarksToKeep.append(serverBookmark)
208+
}
209+
210+
if let deviceID = serverBookmark.device,
211+
let drmDeviceID = drmDeviceID,
212+
deviceID == drmDeviceID
213+
&& !matched
214+
{
215+
serverBookmarksToDelete.append(serverBookmark)
216+
if let indexToRemove = serverBookmarksToKeep.firstIndex(of: serverBookmark) {
217+
serverBookmarksToKeep.remove(at: indexToRemove)
218+
}
219+
}
220+
}
221+
222+
for localBookmark in localBookmarks {
223+
if !localBookmarksToKeep.contains(localBookmark) {
224+
bookRegistry.deleteAudiobookBookmark(localBookmark, for: book.identifier)
225+
}
226+
}
227+
228+
var bookmarksToAdd = serverBookmarks + bookmarksFailedToUpload
229+
230+
// Look for duplicates in server and local bookmarks, remove them from bookmarksToAdd
231+
let duplicatedBookmarks = Set(serverBookmarksToKeep).intersection(Set(localBookmarksToKeep))
232+
bookmarksToAdd = Array(Set(bookmarksToAdd).subtracting(duplicatedBookmarks))
233+
234+
for bookmark in bookmarksToAdd {
235+
bookRegistry.addAudiobookBookmark(bookmark, for: book.identifier)
236+
}
237+
238+
annotationsSynchronizer.deleteBookmarks(serverBookmarksToDelete)
239+
240+
completion()
241+
}
242+
243+
private func handleBookmarksSyncFail(message: String,
244+
completion: @escaping (Bool) -> ()) {
245+
Log.info(#file, message)
246+
247+
bookmarks = self.bookRegistry.audiobookBookmarks(for: book.identifier)
248+
completion(false)
249+
}
250+
251+
/// Verifies if a bookmark exists at the given location.
252+
/// - Parameter location: The audiobook location to be checked.
253+
/// - Returns: The bookmark at the given `location` if it exists,
254+
/// otherwise nil.
255+
private func bookmarkExisting(at location: ChapterLocation) -> NYPLAudiobookBookmark? {
256+
return bookmarks.first {
257+
$0.locationMatches(location)
258+
}
259+
}
260+
}
261+
#endif

Simplified/AxisService/Download/NYPLAxisBookDownloadMediator.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ class NYPLAxisBookDownloadMediator: NYPLAxisBookDownloadListening {
2525
delegate?.downloadProgressDidUpdate(to: progress, forBook: book)
2626
}
2727

28-
func downloadDidFail() {
29-
delegate?.failDownloadWithAlert(forBook: book)
28+
func downloadDidFail(with error: Error) {
29+
delegate?.failDownloadWithAlert(forBook: book, error: error)
3030
}
3131

3232
func didFinishDownloadingBook(to sourceLocation: URL) {

Simplified/AxisService/NYPLAxisServiceAdapter.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import NYPLAxis
1111

1212
@objc protocol NYPLBookDownloadBroadcasting {
1313
func downloadProgressDidUpdate(to progress: Double, forBook book: NYPLBook)
14-
func failDownloadWithAlert(forBook book: NYPLBook)
14+
func failDownloadWithAlert(forBook book: NYPLBook, error: Error?)
1515
func replaceBook(_ book: NYPLBook,
1616
withFileAtURL sourceLocation: URL,
1717
forDownloadTask downloadtask: URLSessionDownloadTask) -> Bool

0 commit comments

Comments
 (0)