Skip to content
This repository was archived by the owner on Jun 17, 2024. It is now read-only.

Commit e55ba1b

Browse files
committed
[components] Closes mozilla-mobile/android-components#163: Remove duplicated state in SessionProvider/Storage
1 parent 7428d68 commit e55ba1b

File tree

10 files changed

+112
-137
lines changed

10 files changed

+112
-137
lines changed

android-components/components/browser/session/src/main/java/mozilla/components/browser/session/Session.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44

55
package mozilla.components.browser.session
66

7+
import java.util.UUID
78
import kotlin.properties.Delegates
89

910
/**
1011
* Value type that represents the state of a browser session. Changes can be observed.
1112
*/
1213
class Session(
13-
initialUrl: String
14+
initialUrl: String,
15+
val id: String = UUID.randomUUID().toString()
1416
) {
1517
/**
1618
* Interface to be implemented by classes that want to observe a session.

android-components/components/browser/session/src/main/java/mozilla/components/browser/session/SessionManager.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,12 @@ class SessionManager(initialSession: Session) {
2626
/**
2727
* Adds the provided session.
2828
*/
29-
fun add(session: Session) {
29+
fun add(session: Session, selected: Boolean = false) {
3030
sessions.add(session)
31+
32+
if (selected) {
33+
select(session)
34+
}
3135
}
3236

3337
/**

android-components/components/browser/session/src/test/java/mozilla/components/browser/session/SessionManagerTest.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ class SessionManagerTest {
1515
@Test
1616
fun `session can be added`() {
1717
val manager = SessionManager(Session("http://www.mozilla.org"))
18-
manager.add(Session("http://www.firefox.com"))
18+
manager.add(Session("http://getpocket.com"))
19+
manager.add(Session("http://www.firefox.com"), true)
1920

20-
assertEquals(2, manager.size)
21-
assertEquals("http://www.mozilla.org", manager.selectedSession.url)
21+
assertEquals(3, manager.size)
22+
assertEquals("http://www.firefox.com", manager.selectedSession.url)
2223
}
2324

2425
@Test

android-components/components/browser/session/src/test/java/mozilla/components/browser/session/SessionTest.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package mozilla.components.browser.session
66

77
import org.junit.Assert.assertEquals
8+
import org.junit.Assert.assertNotNull
89
import org.junit.Test
910
import org.mockito.Mockito.mock
1011
import org.mockito.Mockito.never
@@ -128,4 +129,14 @@ class SessionTest {
128129

129130
assertEquals("https://www.mozilla.org", session.url)
130131
}
132+
133+
@Test
134+
fun `Session always has an ID`() {
135+
var session = Session("https://www.mozilla.org")
136+
assertNotNull(session.id)
137+
138+
session = Session("https://www.mozilla.org", "s1")
139+
assertNotNull(session.id)
140+
assertEquals("s1", session.id)
141+
}
131142
}

android-components/components/feature/session/src/main/java/mozilla/components/feature/session/DefaultSessionStorage.kt

Lines changed: 18 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ import android.content.Context
88
import mozilla.components.browser.session.Session
99
import mozilla.components.concept.engine.Engine
1010
import mozilla.components.concept.engine.EngineSession
11+
import org.json.JSONException
1112
import org.json.JSONObject
1213
import java.io.FileNotFoundException
13-
import java.util.UUID
1414

1515
const val SELECTED_SESSION_KEY = "selectedSession"
1616
const val SESSION_KEY = "session"
@@ -38,36 +38,11 @@ const val FILE_NAME = "mozilla_components_session_storage.json"
3838
* }
3939
*/
4040
class DefaultSessionStorage(private val context: Context) : SessionStorage {
41-
private val sessions = mutableMapOf<String, SessionProxy>()
42-
private var selectedSession: Session? = null
4341

4442
@Synchronized
45-
override fun add(session: SessionProxy): String {
46-
val id = UUID.randomUUID().toString()
47-
sessions.put(id, session)
48-
return id
49-
}
50-
51-
@Synchronized
52-
override fun remove(id: String): Boolean {
53-
return sessions.remove(id) != null
54-
}
55-
56-
@Synchronized
57-
override fun get(id: String): SessionProxy? {
58-
return sessions[id]
59-
}
60-
61-
@Synchronized
62-
override fun getSelected(): Session? {
63-
return selectedSession
64-
}
65-
66-
@Synchronized
67-
override fun restore(engine: Engine): List<SessionProxy> {
43+
override fun restore(engine: Engine): Pair<Map<Session, EngineSession>, String> {
44+
val sessions = mutableMapOf<Session, EngineSession>()
6845
return try {
69-
sessions.clear()
70-
7146
context.openFileInput(FILE_NAME).use {
7247
val json = it.bufferedReader().use {
7348
it.readText()
@@ -84,44 +59,39 @@ class DefaultSessionStorage(private val context: Context) : SessionStorage {
8459
val jsonSession = jsonRoot.getJSONObject(it)
8560
val session = deserializeSession(jsonSession.getJSONObject(SESSION_KEY))
8661
val engineSession = deserializeEngineSession(engine, jsonSession.getJSONObject(ENGINE_SESSION_KEY))
87-
sessions.put(it, SessionProxy(session, engineSession))
62+
sessions.put(session, engineSession)
8863
}
89-
selectedSession = sessions[selectedSessionId]?.session
64+
Pair(sessions, selectedSessionId)
9065
}
91-
sessions.values.toList()
9266
} catch (_: FileNotFoundException) {
93-
emptyList()
67+
Pair(emptyMap(), "")
68+
} catch (_: JSONException) {
69+
Pair(emptyMap(), "")
9470
}
9571
}
9672

9773
@Synchronized
98-
override fun persist(selectedSession: Session?): Boolean {
74+
override fun persist(sessions: Map<Session, EngineSession>, selectedSession: String): Boolean {
9975
return try {
10076
val json = JSONObject()
10177
json.put(VERSION_KEY, VERSION)
78+
json.put(SELECTED_SESSION_KEY, selectedSession)
10279

103-
var selectedSessionId = ""
104-
for ((key, value) in sessions) {
105-
if (value.session == selectedSession) {
106-
selectedSessionId = key
107-
}
108-
}
109-
json.put(SELECTED_SESSION_KEY, selectedSessionId)
110-
111-
sessions.forEach({ (key, value) ->
80+
sessions.forEach({ (session, engineSession) ->
11281
val sessionJson = JSONObject()
113-
sessionJson.put(SESSION_KEY, serializeSession(value.session))
114-
sessionJson.put(ENGINE_SESSION_KEY, serializeEngineSession(value.engineSession))
115-
json.put(key, sessionJson)
82+
sessionJson.put(SESSION_KEY, serializeSession(session))
83+
sessionJson.put(ENGINE_SESSION_KEY, serializeEngineSession(engineSession))
84+
json.put(session.id, sessionJson)
11685
})
11786

11887
context.openFileOutput(FILE_NAME, Context.MODE_PRIVATE).use {
119-
println(json.toString())
12088
it.write(json.toString().toByteArray())
12189
}
12290
true
12391
} catch (_: FileNotFoundException) {
12492
false
93+
} catch (_: JSONException) {
94+
false
12595
}
12696
}
12797

@@ -136,10 +106,12 @@ class DefaultSessionStorage(private val context: Context) : SessionStorage {
136106
}
137107

138108
private fun serializeEngineSession(engineSession: EngineSession): JSONObject {
109+
// We don't currently persist any engine session state
139110
return JSONObject()
140111
}
141112

142113
private fun deserializeEngineSession(engine: Engine, json: JSONObject): EngineSession {
114+
// We don't currently persist any engine session state
143115
return engine.createSession()
144116
}
145117
}

android-components/components/feature/session/src/main/java/mozilla/components/feature/session/SessionProvider.kt

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import mozilla.components.browser.session.Session
99
import mozilla.components.browser.session.SessionManager
1010
import mozilla.components.concept.engine.Engine
1111
import mozilla.components.concept.engine.EngineSession
12-
import java.util.WeakHashMap
1312
import java.util.concurrent.Executors
1413
import java.util.concurrent.ScheduledExecutorService
1514
import java.util.concurrent.TimeUnit
@@ -20,45 +19,47 @@ import java.util.concurrent.TimeUnit
2019
class SessionProvider(
2120
private val context: Context,
2221
initialSession: Session = Session(""),
23-
private val storage: SessionStorage = DefaultSessionStorage(context),
22+
private val sessionStorage: SessionStorage = DefaultSessionStorage(context),
2423
private val savePeriodically: Boolean = false,
2524
private val saveIntervalInSeconds: Long = 300,
2625
private val scheduler: ScheduledExecutorService = Executors.newSingleThreadScheduledExecutor()
2726
) {
28-
private val sessions = WeakHashMap<Session, EngineSession>()
27+
private val sessions = mutableMapOf<Session, EngineSession>()
28+
2929
val sessionManager: SessionManager = SessionManager(initialSession)
3030

3131
val selectedSession
3232
get() = sessionManager.selectedSession
3333

3434
/**
35-
* Starts this provider and schedules periodic saves.
35+
* Restores persisted session state and schedules periodic saves.
3636
*/
3737
fun start(engine: Engine) {
38-
storage.restore(engine).forEach {
39-
sessionManager.add(it.session)
40-
sessions.put(it.session, it.engineSession)
38+
val (restoredSessions, restoredSelectedSession) = sessionStorage.restore(engine)
39+
sessions.putAll(restoredSessions)
40+
sessions.keys.forEach {
41+
sessionManager.add(it, it.id == restoredSelectedSession)
4142
}
42-
storage.getSelected()?.let { sessionManager.select(it) }
4343

4444
if (savePeriodically) {
4545
scheduler.scheduleAtFixedRate(
46-
{ storage.persist(sessionManager.selectedSession) },
46+
{ sessionStorage.persist(sessions, sessionManager.selectedSession?.id) },
4747
saveIntervalInSeconds,
4848
saveIntervalInSeconds,
4949
TimeUnit.SECONDS)
5050
}
5151
}
5252

5353
/**
54-
* Returns the engine session corresponding to the current or given browser session. A new
55-
* engine session will be created if none exists for the given browser session.
54+
* Returns the engine session corresponding to the given (or currently selected)
55+
* browser session. A new engine session will be created if none exists for the
56+
* given browser session.
5657
*/
5758
@Synchronized
5859
fun getOrCreateEngineSession(engine: Engine, session: Session = selectedSession): EngineSession {
5960
return sessions.getOrPut(session, {
6061
val engineSession = engine.createSession()
61-
storage.add(SessionProxy(session, engineSession))
62+
SessionProxy(session, engineSession)
6263

6364
engineSession.loadUrl(session.url)
6465
engineSession
@@ -69,6 +70,7 @@ class SessionProvider(
6970
* Stops this provider and periodic saves.
7071
*/
7172
fun stop() {
73+
sessions.clear()
7274
scheduler.shutdown()
7375
}
7476
}

android-components/components/feature/session/src/main/java/mozilla/components/feature/session/SessionProxy.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@ import mozilla.components.concept.engine.EngineSession
88
import mozilla.components.browser.session.Session
99

1010
/**
11-
* Represents a combination of a browser and engine session and acts as a proxy that
12-
* will subscribe to an EngineSession and update the Session object whenever new
13-
* data is available.
11+
* Proxy class that will subscribe to an engine session and update the browser session object
12+
* whenever new data is available.
1413
*/
1514
class SessionProxy(
1615
val session: Session,

android-components/components/feature/session/src/main/java/mozilla/components/feature/session/SessionStorage.kt

Lines changed: 9 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -6,59 +6,27 @@ package mozilla.components.feature.session
66

77
import mozilla.components.browser.session.Session
88
import mozilla.components.concept.engine.Engine
9+
import mozilla.components.concept.engine.EngineSession
910

1011
/**
1112
* Storage component for browser and engine sessions.
1213
*/
1314
interface SessionStorage {
1415

1516
/**
16-
* Persists the current storage state (all active sessions).
17+
* Persists the state of the provided sessions.
1718
*
18-
* @param selectedSession the currently selected session, optional.
19+
* @param sessions a map from browser session to engine session
20+
* @param selectedSession an optional ID of the currently selected session.
1921
* @return true if the state was persisted, otherwise false.
2022
*/
21-
fun persist(selectedSession: Session? = null): Boolean
23+
fun persist(sessions: Map<Session, EngineSession>, selectedSession: String = ""): Boolean
2224

2325
/**
24-
* Restores the storage state by reading from the latest persisted version.
26+
* Restores the session storage state by reading from the latest persisted version.
2527
*
26-
* @param engine the engine instance to use. Required to create new engine
27-
* sessions.
28-
* @return list of all restored sessions
28+
* @param engine the engine instance to use when creating new engine sessions.
29+
* @return map of all restored sessions, and the currently selected session id.
2930
*/
30-
fun restore(engine: Engine): List<SessionProxy>
31-
32-
/**
33-
* Adds the provided session. The state change is not persisted until
34-
* [persist] is called.
35-
*
36-
* @param session the session to add.
37-
* @return unique ID of the session.
38-
*/
39-
fun add(session: SessionProxy): String
40-
41-
/**
42-
* Removes the session with the provided ID. The state change is not persisted until
43-
* [persist] is called.
44-
*
45-
* @param id the ID of the session to remove.
46-
* @return true if the session was found and removed, otherwise false.
47-
*/
48-
fun remove(id: String): Boolean
49-
50-
/**
51-
* Returns the session for the provided ID.
52-
*
53-
* @param id the ID of the session.
54-
* @return the session if found, otherwise null.
55-
*/
56-
fun get(id: String): SessionProxy?
57-
58-
/**
59-
* Returns the selected session.
60-
*
61-
* @return the selected session, null if no session is selected.
62-
*/
63-
fun getSelected(): Session?
31+
fun restore(engine: Engine): Pair<Map<Session, EngineSession>, String>
6432
}

0 commit comments

Comments
 (0)