@@ -4,62 +4,168 @@ import com.intellij.openapi.components.PersistentStateComponent
44import com.intellij.openapi.components.Service
55import com.intellij.openapi.components.State
66import com.intellij.openapi.components.Storage
7+ import com.intellij.openapi.components.service
78import com.intellij.util.xmlb.Converter
89import com.intellij.util.xmlb.annotations.OptionTag
910import com.intellij.util.xmlb.annotations.XMap
11+ import com.jetbrains.rd.util.ConcurrentHashMap
12+ import kotlinx.datetime.DatePeriod
1013import kotlinx.datetime.LocalDate
14+ import kotlinx.datetime.plus
15+ import java.util.concurrent.locks.ReentrantLock
16+ import kotlin.concurrent.withLock
1117
18+ @Service(Service .Level .APP )
19+ class EngagementScorePersistenceService {
20+
21+ private val persistence = service<EngagementScorePersistence >()
22+
23+ fun removeOldEntries (today : LocalDate , periodToTrack : DatePeriod ) {
24+ persistence.removeOldEntries(today, periodToTrack)
25+ }
26+
27+ fun getLastEventTime (): LocalDate ? {
28+ return persistence.getLastEventTime()
29+ }
30+
31+ fun getDaysForAverage (today : LocalDate ): Map <String , Int > {
32+ return persistence.getDaysForAverage(today)
33+ }
34+
35+ fun setLastEventTime (today : LocalDate ) {
36+ persistence.setLastEventTime(today)
37+ }
38+
39+ fun setLatestRegisteredActiveDays (activeDays : Long ) {
40+ persistence.setLatestRegisteredActiveDays(activeDays)
41+ }
42+
43+ fun setLatestRegisteredAverage (average : Long ) {
44+ persistence.setLatestRegisteredAverage(average)
45+ }
46+
47+ fun increment (today : LocalDate ) {
48+ persistence.increment(today)
49+ }
50+
51+ fun getLatestRegisteredActiveDays (): Long {
52+ return persistence.getLatestRegisteredActiveDays()
53+ }
54+
55+ fun getLatestRegisteredAverage (): Long {
56+ return persistence.getLatestRegisteredAverage()
57+ }
1258
59+ }
60+
61+
62+ // don't use directly, use EngagementScorePersistenceService for thread safety
1363@State(
1464 name = " org.digma.intellij.plugin.engagement.EngagementScorePersistence" ,
1565 storages = [Storage (" DigmaEngagementScorePersistence.xml" )]
1666)
1767@Service(Service .Level .APP )
18- class EngagementScorePersistence : PersistentStateComponent <EngagementScoreData > {
68+ private class EngagementScorePersistence : PersistentStateComponent <EngagementScorePersistence . EngagementScoreData > {
1969
2070 private var myPersistenceData = EngagementScoreData ()
2171
72+ private val lock = ReentrantLock (true )
73+
74+ // don't use getState to get properties from EngagementScoreData, always use dedicated methods that use myPersistenceData.
75+ // getState is called by the intellij framework and because it has a map it may happen that we write
76+ // to the map while the framework is serializing. getState returns a copy to avoid corrupted serialization.
2277 override fun getState (): EngagementScoreData {
23- return myPersistenceData
78+ lock.withLock {
79+ return EngagementScoreData (myPersistenceData)
80+ }
2481 }
2582
2683 override fun loadState (state : EngagementScoreData ) {
27- myPersistenceData = state
84+ lock.withLock {
85+ myPersistenceData = EngagementScoreData (state)
86+ }
2887 }
29- }
3088
31- class EngagementScoreData {
32- @OptionTag(converter = LocalDateConverter ::class )
33- var lastEventTime: LocalDate ? = null
89+ fun removeOldEntries (today : LocalDate , periodToTrack : DatePeriod ) {
90+ lock.withLock {
91+ val oldEntries = myPersistenceData.meaningfulActionsCounters.keys.filter {
92+ LocalDate .parse(it).plus(periodToTrack) < today
93+ }
3494
35- // using string as day and not LocalDate because it's a bit messy to serialise LocalDate as map keys
36- // with this persistence framework.
37- @get:XMap(keyAttributeName = " day" , valueAttributeName = " count" )
38- var meaningfulActionsCounters = mutableMapOf<String , Int >()
39-
40- var latestRegisteredActiveDays: Long = 0
41- var latestRegisteredAverage: Long = 0
95+ oldEntries.forEach {
96+ myPersistenceData.meaningfulActionsCounters.remove(it)
97+ }
98+ }
99+ }
42100
101+ fun setLastEventTime (today : LocalDate ) {
102+ lock.withLock {
103+ myPersistenceData.lastEventTime = today
104+ }
105+ }
43106
44- fun put (date : LocalDate , count : Int ) {
45- meaningfulActionsCounters[date.toString()] = count
107+ fun setLatestRegisteredActiveDays (activeDays : Long ) {
108+ lock.withLock {
109+ myPersistenceData.latestRegisteredActiveDays = activeDays
110+ }
46111 }
47112
48- fun get (date : LocalDate ): Int? {
49- return meaningfulActionsCounters[date.toString()]
113+ fun setLatestRegisteredAverage (average : Long ) {
114+ lock.withLock {
115+ myPersistenceData.latestRegisteredAverage = average
116+ }
50117 }
51118
52119 fun increment (date : LocalDate ) {
53- val count = meaningfulActionsCounters[date.toString()]
54- meaningfulActionsCounters[date.toString()] = count.increment()
120+ lock.withLock {
121+ val count = myPersistenceData.meaningfulActionsCounters[date.toString()]
122+ myPersistenceData.meaningfulActionsCounters[date.toString()] = count.increment()
123+ }
124+ }
125+
126+ fun getLastEventTime (): LocalDate ? {
127+ return myPersistenceData.lastEventTime
55128 }
56129
57- fun remove (date : LocalDate ) {
58- meaningfulActionsCounters.remove(date.toString())
130+ fun getDaysForAverage (today : LocalDate ): Map <String , Int > {
131+ return myPersistenceData.meaningfulActionsCounters.filter {
132+ LocalDate .parse(it.key) != today
133+ }
134+ }
135+
136+ fun getLatestRegisteredActiveDays (): Long {
137+ return myPersistenceData.latestRegisteredActiveDays
138+ }
139+
140+ fun getLatestRegisteredAverage (): Long {
141+ return myPersistenceData.latestRegisteredAverage
59142 }
60143
61144 fun remove (date : String ) {
62- meaningfulActionsCounters.remove(date)
145+ lock.withLock {
146+ myPersistenceData.meaningfulActionsCounters.remove(date)
147+ }
148+ }
149+
150+ class EngagementScoreData () {
151+
152+ constructor (toCopy: EngagementScoreData ) : this () {
153+ this .lastEventTime = toCopy.lastEventTime
154+ this .latestRegisteredAverage = toCopy.latestRegisteredAverage
155+ this .latestRegisteredActiveDays = toCopy.latestRegisteredActiveDays
156+ this .meaningfulActionsCounters.putAll(toCopy.meaningfulActionsCounters)
157+ }
158+
159+ @OptionTag(converter = LocalDateConverter ::class )
160+ var lastEventTime: LocalDate ? = null
161+
162+ // using string as day and not LocalDate because it's a bit messy to serialise LocalDate as map keys
163+ // with this persistence framework.
164+ @get:XMap(keyAttributeName = " day" , valueAttributeName = " count" )
165+ var meaningfulActionsCounters = ConcurrentHashMap (mutableMapOf<String , Int >())
166+
167+ var latestRegisteredActiveDays: Long = 0
168+ var latestRegisteredAverage: Long = 0
63169 }
64170}
65171
0 commit comments