diff --git a/RWFramework/RWFramework/Playlist/Playlist.swift b/RWFramework/RWFramework/Playlist/Playlist.swift index b48168a..58c75e5 100644 --- a/RWFramework/RWFramework/Playlist/Playlist.swift +++ b/RWFramework/RWFramework/Playlist/Playlist.swift @@ -179,7 +179,7 @@ extension Playlist { }.filter { (asset, rank) in rank != .discard }.sorted { a, b in - a.1.rawValue < b.1.rawValue + a.1.rawValue <= b.1.rawValue }.sorted { a, b in // play less played assets first let dataA = userAssetData[a.0.id] @@ -269,16 +269,21 @@ extension Playlist { opts["created__gte"] = dateFormatter.string(from: date) } - return rw.apiGetAssets(opts).then { data -> () in + return Promise { + let data = try await(rw.apiGetAssets(opts)) self.lastUpdate = Date() self.allAssets.append(contentsOf: data) - + print("\(data.count) added assets") + + // Ensure all sort methods are setup before sorting. + _ = try await(all(self.sortMethods.map { $0.onRefreshAssets(in: self) })) + + // Sort the asset pool. for sortMethod in self.sortMethods { self.allAssets.sort(by: { a, b in sortMethod.sortRanking(for: a, in: self) < sortMethod.sortRanking(for: b, in: self) }) } - print("\(data.count) added assets") }.catch { err in print(err) self.lastUpdate = Date() @@ -322,6 +327,8 @@ extension Playlist { } func start() { + DispatchQueue.promises = .global() + RWFramework.sharedInstance.isPlaying = false // Starts a session and retrieves project-wide config. @@ -339,6 +346,8 @@ extension Playlist { self.sortMethods = [SortRandomly()] case "by_weight": self.sortMethods = [SortByWeight()] + case "by_like": + self.sortMethods = [SortByLikes()] default: break } } diff --git a/RWFramework/RWFramework/Playlist/SortMethod.swift b/RWFramework/RWFramework/Playlist/SortMethod.swift index 46bf55c..bc0074e 100644 --- a/RWFramework/RWFramework/Playlist/SortMethod.swift +++ b/RWFramework/RWFramework/Playlist/SortMethod.swift @@ -1,8 +1,25 @@ import Foundation +import SwiftyJSON +import Promises protocol SortMethod { + /** + The sorting ranking of the given asset. + Assets will be sorted in ascending order of their rank. + Thus, returning a negatized result effectively causes + assets to be in descending order. + */ func sortRanking(for asset: Asset, in playlist: Playlist) -> Double + /// Load any data required before sorting. + func onRefreshAssets(in playlist: Playlist) -> Promise +} + +extension SortMethod { + /// By default does nothing when assets are refreshed. + func onRefreshAssets(in playlist: Playlist) -> Promise { + return Promise(()) + } } @@ -16,4 +33,31 @@ struct SortByWeight: SortMethod { func sortRanking(for asset: Asset, in playlist: Playlist) -> Double { return -asset.weight } -} \ No newline at end of file +} + +class SortByLikes: SortMethod { + private var assetVotes: [Int: Int]? = nil + + func sortRanking(for asset: Asset, in playlist: Playlist) -> Double { + if let votes = assetVotes?[asset.id] { + return Double(-votes) + } else { + return 0.0 + } + } + + func onRefreshAssets(in playlist: Playlist) -> Promise { + let projectId = playlist.project.id + return RWFramework.sharedInstance.apiGetVotesSummary( + type: "like", + projectId: projectId.description + ).then { data -> Void in + let voteData = try JSON(data: data).array + self.assetVotes = voteData?.reduce(into: [Int: Int]()) { acc, data in + let assetId = data["asset_id"].int! + let votes = data["asset_votes"].int! + acc[assetId] = votes + } + } + } +} diff --git a/RWFramework/RWFramework/RWFramework.swift b/RWFramework/RWFramework/RWFramework.swift index 2f56086..574d70d 100644 --- a/RWFramework/RWFramework/RWFramework.swift +++ b/RWFramework/RWFramework/RWFramework.swift @@ -55,6 +55,7 @@ private lazy var __once: () = { () -> Void in DynamicTagFilter("_ten_most_recent_days", MostRecentFilter(days: 10)) ], sortBy: [ SortRandomly(), + SortByLikes(), ]) // Audio - Stream (see RWFrameworkAudioPlayer.swift) diff --git a/RWFramework/RWFramework/RWFrameworkAPI.swift b/RWFramework/RWFramework/RWFrameworkAPI.swift index 1174703..49de401 100644 --- a/RWFramework/RWFramework/RWFrameworkAPI.swift +++ b/RWFramework/RWFramework/RWFrameworkAPI.swift @@ -558,7 +558,6 @@ extension RWFramework { }.catch { error in self.rwPostAssetsIdVotesFailure(error) self.apiProcessError(nil, error: error, caller: "apiPostAssetsIdVotes") - } } @@ -572,6 +571,10 @@ extension RWFramework { self.apiProcessError(nil, error: error, caller: "apiGetAssetsIdVotes") } } + + func apiGetVotesSummary(type: String? = nil, projectId: String? = nil, assetId: String? = nil) -> Promise { + return httpGetVotesSummary(type: type, projectId: projectId, assetId: assetId) + } public func apiGetSpeakers(_ dict: [String:String]) -> Promise<[Speaker]> { return httpGetSpeakers(dict).then { data -> [Speaker] in diff --git a/RWFramework/RWFramework/RWFrameworkHTTP.swift b/RWFramework/RWFramework/RWFrameworkHTTP.swift index bc60843..fa15992 100644 --- a/RWFramework/RWFramework/RWFrameworkHTTP.swift +++ b/RWFramework/RWFramework/RWFrameworkHTTP.swift @@ -192,6 +192,14 @@ extension RWFramework: URLSessionDelegate, URLSessionTaskDelegate, URLSessionDat func httpGetAssetsIdVotes(_ asset_id: String) -> Promise { return getData(from: RWFrameworkURLFactory.getAssetsIdVotesURL(asset_id)) } + + func httpGetVotesSummary(type: String?, projectId: String?, assetId: String?) -> Promise { + return getData(from: RWFrameworkURLFactory.getVotesSummaryURL([ + "type": type ?? "", + "asset_id": assetId ?? "", + "project_id": projectId ?? "" + ])) + } func httpGetSpeakers(_ dict: [String:String]) -> Promise { diff --git a/RWFramework/RWFramework/RWFrameworkURLFactory.swift b/RWFramework/RWFramework/RWFrameworkURLFactory.swift index 3402045..a292a5e 100644 --- a/RWFramework/RWFramework/RWFrameworkURLFactory.swift +++ b/RWFramework/RWFramework/RWFrameworkURLFactory.swift @@ -110,6 +110,10 @@ open class RWFrameworkURLFactory { return "\(api())/assets/\(asset_id)/votes/" } + class func getVotesSummaryURL(_ dict: [String:String]) -> String { + return "\(api())/votes/summary/\(dict.toUrlQuery())" + } + class func postEventsURL() -> String { return "\(api())/events/" } @@ -126,7 +130,9 @@ fileprivate extension Dictionary where Key == String, Value == String { result += "?" } for (key, value) in self { - result += (key + "=" + value + "&") + if !value.isEmpty { + result += (key + "=" + value + "&") + } } return result }