Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stats: apply API changes to prevent reset #2541

Merged
merged 7 commits into from
Dec 18, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -3138,6 +3138,11 @@ struct Api_UserPodcastResponse: @unchecked Sendable {
/// Clears the value of `settings`. Subsequent reads from it will return its default value.
mutating func clearSettings() {_uniqueStorage()._settings = nil}

var descriptionHtml: String {
get {return _storage._descriptionHtml}
set {_uniqueStorage()._descriptionHtml = newValue}
}

var unknownFields = SwiftProtobuf.UnknownStorage()

init() {}
Expand Down Expand Up @@ -4948,9 +4953,20 @@ struct Api_SyncUpdateRequest: Sendable {

var records: [Api_Record] = []

var deviceType: SwiftProtobuf.Google_Protobuf_Int32Value {
get {return _deviceType ?? SwiftProtobuf.Google_Protobuf_Int32Value()}
set {_deviceType = newValue}
}
/// Returns true if `deviceType` has been explicitly set.
var hasDeviceType: Bool {return self._deviceType != nil}
/// Clears the value of `deviceType`. Subsequent reads from it will return its default value.
mutating func clearDeviceType() {self._deviceType = nil}

var unknownFields = SwiftProtobuf.UnknownStorage()

init() {}

fileprivate var _deviceType: SwiftProtobuf.Google_Protobuf_Int32Value? = nil
}

struct Api_SyncUpdateResponse: Sendable {
Expand Down Expand Up @@ -10828,6 +10844,7 @@ extension Api_UserPodcastResponse: SwiftProtobuf.Message, SwiftProtobuf._Message
15: .standard(proto: "sort_position"),
16: .standard(proto: "date_added"),
17: .same(proto: "settings"),
18: .standard(proto: "description_html"),
]

fileprivate class _StorageClass {
Expand All @@ -10848,6 +10865,7 @@ extension Api_UserPodcastResponse: SwiftProtobuf.Message, SwiftProtobuf._Message
var _sortPosition: SwiftProtobuf.Google_Protobuf_Int32Value? = nil
var _dateAdded: SwiftProtobuf.Google_Protobuf_Timestamp? = nil
var _settings: Api_PodcastSettings? = nil
var _descriptionHtml: String = String()

#if swift(>=5.10)
// This property is used as the initial default value for new instances of the type.
Expand Down Expand Up @@ -10879,6 +10897,7 @@ extension Api_UserPodcastResponse: SwiftProtobuf.Message, SwiftProtobuf._Message
_sortPosition = source._sortPosition
_dateAdded = source._dateAdded
_settings = source._settings
_descriptionHtml = source._descriptionHtml
}
}

Expand Down Expand Up @@ -10914,6 +10933,7 @@ extension Api_UserPodcastResponse: SwiftProtobuf.Message, SwiftProtobuf._Message
case 15: try { try decoder.decodeSingularMessageField(value: &_storage._sortPosition) }()
case 16: try { try decoder.decodeSingularMessageField(value: &_storage._dateAdded) }()
case 17: try { try decoder.decodeSingularMessageField(value: &_storage._settings) }()
case 18: try { try decoder.decodeSingularStringField(value: &_storage._descriptionHtml) }()
default: break
}
}
Expand Down Expand Up @@ -10977,6 +10997,9 @@ extension Api_UserPodcastResponse: SwiftProtobuf.Message, SwiftProtobuf._Message
try { if let v = _storage._settings {
try visitor.visitSingularMessageField(value: v, fieldNumber: 17)
} }()
if !_storage._descriptionHtml.isEmpty {
try visitor.visitSingularStringField(value: _storage._descriptionHtml, fieldNumber: 18)
}
}
try unknownFields.traverse(visitor: &visitor)
}
Expand All @@ -11003,6 +11026,7 @@ extension Api_UserPodcastResponse: SwiftProtobuf.Message, SwiftProtobuf._Message
if _storage._sortPosition != rhs_storage._sortPosition {return false}
if _storage._dateAdded != rhs_storage._dateAdded {return false}
if _storage._settings != rhs_storage._settings {return false}
if _storage._descriptionHtml != rhs_storage._descriptionHtml {return false}
return true
}
if !storagesAreEqual {return false}
Expand Down Expand Up @@ -14873,6 +14897,7 @@ extension Api_SyncUpdateRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageIm
3: .same(proto: "country"),
4: .standard(proto: "device_id"),
5: .same(proto: "records"),
6: .standard(proto: "device_type"),
]

mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
Expand All @@ -14886,12 +14911,17 @@ extension Api_SyncUpdateRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageIm
case 3: try { try decoder.decodeSingularStringField(value: &self.country) }()
case 4: try { try decoder.decodeSingularStringField(value: &self.deviceID) }()
case 5: try { try decoder.decodeRepeatedMessageField(value: &self.records) }()
case 6: try { try decoder.decodeSingularMessageField(value: &self._deviceType) }()
default: break
}
}
}

func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
// The use of inline closures is to circumvent an issue where the compiler
// allocates stack space for every if/case branch local when no optimizations
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
// https://github.com/apple/swift-protobuf/issues/1182
if self.deviceUtcTimeMs != 0 {
try visitor.visitSingularInt64Field(value: self.deviceUtcTimeMs, fieldNumber: 1)
}
Expand All @@ -14907,6 +14937,9 @@ extension Api_SyncUpdateRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageIm
if !self.records.isEmpty {
try visitor.visitRepeatedMessageField(value: self.records, fieldNumber: 5)
}
try { if let v = self._deviceType {
try visitor.visitSingularMessageField(value: v, fieldNumber: 6)
} }()
try unknownFields.traverse(visitor: &visitor)
}

Expand All @@ -14916,6 +14949,7 @@ extension Api_SyncUpdateRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageIm
if lhs.country != rhs.country {return false}
if lhs.deviceID != rhs.deviceID {return false}
if lhs.records != rhs.records {return false}
if lhs._deviceType != rhs._deviceType {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
return true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,39 @@ public class StatsManager {
}
}

func updateStatsIfNeeded(savedDynamicSpeed: TimeInterval, savedVariableSpeed: TimeInterval, totalListenedTo: TimeInterval, totalSkipped: TimeInterval, savedAutoSkipping: TimeInterval) {
let minimumStatsChangeToUpdate: TimeInterval = 100

updateQueue.sync {
if savedDynamicSpeed - self.savedDynamicSpeed > minimumStatsChangeToUpdate {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Do you want to save only if the difference is positive right? No need to use abs ?

FileLog.shared.addMessage("[StatsManager] Changing savedDynamicSpeed from \(self.savedDynamicSpeed) to \(savedDynamicSpeed)")
self.savedDynamicSpeed = savedDynamicSpeed
}

if savedVariableSpeed - self.savedVariableSpeed > minimumStatsChangeToUpdate {
FileLog.shared.addMessage("[StatsManager] Changing savedVariableSpeed from \(self.savedVariableSpeed) to \(savedVariableSpeed)")
self.savedVariableSpeed = savedVariableSpeed
}

if totalListenedTo - self.totalListenedTo > minimumStatsChangeToUpdate {
FileLog.shared.addMessage("[StatsManager] Changing totalListenedTo from \(self.totalListenedTo) to \(totalListenedTo)")
self.totalListenedTo = totalListenedTo
}

if totalSkipped - self.totalSkipped > minimumStatsChangeToUpdate {
FileLog.shared.addMessage("[StatsManager] Changing totalSkipped from \(self.totalSkipped) to \(totalSkipped)")
self.totalSkipped = totalSkipped
}

if savedAutoSkipping - self.savedAutoSkipping > minimumStatsChangeToUpdate {
FileLog.shared.addMessage("[StatsManager] Changing savedAutoSkipping from \(self.savedAutoSkipping) to \(savedAutoSkipping)")
self.savedAutoSkipping = savedAutoSkipping
}

persistTimes()
}
}

// MARK: - dynamic speed

public func timeSavedDynamicSpeed() -> TimeInterval {
Expand Down Expand Up @@ -144,60 +177,6 @@ public class StatsManager {
}
}

public func updateLocalStatsIfNeeded(completion: ((Bool) -> Void)?) {
guard FeatureFlag.syncStats.enabled else {
completion?(false)
return
}

ApiServerHandler.shared.loadStatsRequest(getFullData: true) { [weak self] remoteStats in
guard let self, let remoteStats = remoteStats else { return }

var didChange = false

if Int64(timeSavedDynamicSpeedInclusive()) < remoteStats.silenceRemovalTime {
didChange = true
updateQueue.sync {
self.savedDynamicSpeed = Double(remoteStats.silenceRemovalTime) - self.timeSavedDynamicSpeedInclusive()
}
}

if Int64(totalAutoSkippedTimeInclusive()) < remoteStats.autoSkipTime {
didChange = true
updateQueue.sync {
self.savedAutoSkipping = Double(remoteStats.autoSkipTime) - self.totalAutoSkippedTimeInclusive()
}
}

if Int64(totalSkippedTimeInclusive()) < remoteStats.skipTime {
didChange = true
updateQueue.sync {
self.totalSkipped = Double(remoteStats.skipTime) - self.totalSkippedTimeInclusive()
}
}

if Int64(totalListeningTimeInclusive()) < remoteStats.totalListenTime {
didChange = true
updateQueue.sync {
self.totalListenedTo = Double(remoteStats.totalListenTime) - self.totalListeningTimeInclusive()
}
}

if Int64(timeSavedVariableSpeedInclusive()) < remoteStats.variableSpeedTime {
didChange = true
updateQueue.sync {
self.savedVariableSpeed = Double(remoteStats.variableSpeedTime) - self.timeSavedVariableSpeedInclusive()
}
}

if didChange {
persistTimes()
}

completion?(didChange)
}
}

public func statsStartedAt() -> Int64 {
Int64(UserDefaults.standard.integer(forKey: ServerConstants.UserDefaults.statsStartedDateServer))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,13 @@ extension SyncTask {
case .bookmark:
bookmarksToImport.append(item.bookmark)
case .device:
continue // we aren't expecting the server to send us devices
StatsManager.shared.updateStatsIfNeeded(
savedDynamicSpeed: TimeInterval(item.device.timeSilenceRemoval.value),
savedVariableSpeed: TimeInterval(item.device.timeVariableSpeed.value),
totalListenedTo: TimeInterval(item.device.timeListened.value),
totalSkipped: TimeInterval(item.device.timeSkipping.value),
savedAutoSkipping: TimeInterval(item.device.timeIntroSkipping.value)
)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Foundation
import SwiftProtobuf
import PocketCastsDataModel
import PocketCastsUtils

Expand Down Expand Up @@ -287,6 +288,7 @@ class SyncTask: ApiBaseTask {
syncRequest.country = country
}
syncRequest.deviceID = ServerConfig.shared.syncDelegate?.uniqueAppId() ?? ""
syncRequest.deviceType = Google_Protobuf_Int32Value(ServerConstants.Values.deviceTypeiOS)

return try syncRequest.serializedData()
} catch {}
Expand Down
8 changes: 3 additions & 5 deletions podcasts/StatsViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -160,11 +160,9 @@ class StatsViewController: UIViewController, UITableViewDelegate, UITableViewDat
self?.requestReviewIfPossible()
}

StatsManager.shared.updateLocalStatsIfNeeded { updated in
if updated {
DispatchQueue.main.async { [weak self] in
self?.statsTable.reloadData()
}
RefreshManager.shared.refreshPodcasts { _ in
DispatchQueue.main.async { [weak self] in
self?.statsTable.reloadData()
}
}
}
Expand Down