Skip to content
This repository has been archived by the owner on Nov 1, 2022. It is now read-only.

Commit

Permalink
Merge #8197
Browse files Browse the repository at this point in the history
8197: Closes issue #7762: Adds support for persisting/restoring downloads. r=csadilek a=Amejia481

Fenix pr mozilla-mobile/fenix#14043


Co-authored-by: Arturo Mejia <arturomejiamarmol@gmail.com>
  • Loading branch information
MozLando and Amejia481 committed Aug 27, 2020
2 parents 91f7bcb + b948ae0 commit 4d7378d
Show file tree
Hide file tree
Showing 35 changed files with 1,273 additions and 94 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ sealed class ContentAction : BrowserAction() {
/**
* Removes the [DownloadState] of the [ContentState] with the given [sessionId].
*/
data class ConsumeDownloadAction(val sessionId: String, val downloadId: Long) : ContentAction()
data class ConsumeDownloadAction(val sessionId: String, val downloadId: String) : ContentAction()

/**
* Updates the [HitResult] of the [ContentState] with the given [sessionId].
Expand Down Expand Up @@ -700,7 +700,7 @@ sealed class DownloadAction : BrowserAction() {
/**
* Updates the [BrowserState] to remove the download with the provided [downloadId].
*/
data class RemoveDownloadAction(val downloadId: Long) : DownloadAction()
data class RemoveDownloadAction(val downloadId: String) : DownloadAction()

/**
* Updates the [BrowserState] to remove all downloads.
Expand All @@ -711,6 +711,16 @@ sealed class DownloadAction : BrowserAction() {
* Updates the provided [download] on the [BrowserState].
*/
data class UpdateDownloadAction(val download: DownloadState) : DownloadAction()

/**
* Restores the [BrowserState.downloads] state from the storage.
*/
object RestoreDownloadsStateAction : DownloadAction()

/**
* Restores the given [download] from the storage.
*/
data class RestoreDownloadStateAction(val download: DownloadState) : DownloadAction()
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ internal object DownloadStateReducer {
is DownloadAction.RemoveAllDownloadsAction -> {
state.copy(downloads = emptyMap())
}
is DownloadAction.RestoreDownloadsStateAction -> state
is DownloadAction.RestoreDownloadStateAction -> updateDownloads(state, action.download)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@ data class BrowserState(
val containers: Map<String, ContainerState> = emptyMap(),
val extensions: Map<String, WebExtensionState> = emptyMap(),
val media: MediaState = MediaState(),
val downloads: Map<Long, DownloadState> = emptyMap(),
val downloads: Map<String, DownloadState> = emptyMap(),
val search: SearchState = SearchState()
) : State
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ package mozilla.components.browser.state.state.content
import android.os.Environment
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
import kotlin.random.Random
import java.util.UUID

/**
* Value type that represents a download request.
Expand All @@ -24,7 +24,7 @@ import kotlin.random.Random
* @property referrerUrl The site that linked to this download.
* @property skipConfirmation Whether or not the confirmation dialog should be shown before the download begins.
* @property id The unique identifier of this download.
* @property sessionId Identifier of the session that spawned the download.
* @property createdTime A timestamp when the download was created.
* @
*/
@Suppress("Deprecation")
Expand All @@ -40,40 +40,42 @@ data class DownloadState(
val destinationDirectory: String = Environment.DIRECTORY_DOWNLOADS,
val referrerUrl: String? = null,
val skipConfirmation: Boolean = false,
val id: Long = Random.nextLong(),
val sessionId: String? = null
val id: String = UUID.randomUUID().toString(),
val sessionId: String? = null,
val createdTime: Long = System.currentTimeMillis()
) : Parcelable {
val filePath: String get() =
Environment.getExternalStoragePublicDirectory(destinationDirectory).path + "/" + fileName

/**
* Status that represents every state that a download can be in.
*/
enum class Status {
@Suppress("MagicNumber")
enum class Status(val id: Int) {
/**
* Indicates that the download is in the first state after creation but not yet [DOWNLOADING].
*/
INITIATED,
INITIATED(1),
/**
* Indicates that an [INITIATED] download is now actively being downloaded.
*/
DOWNLOADING,
DOWNLOADING(2),
/**
* Indicates that the download that has been [DOWNLOADING] has been paused.
*/
PAUSED,
PAUSED(3),
/**
* Indicates that the download that has been [DOWNLOADING] has been cancelled.
*/
CANCELLED,
CANCELLED(4),
/**
* Indicates that the download that has been [DOWNLOADING] has moved to failed because
* something unexpected has happened.
*/
FAILED,
FAILED(5),
/**
* Indicates that the [DOWNLOADING] download has been completed.
*/
COMPLETED
COMPLETED(6)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ class ContentActionTest {
@Test
fun `ConsumeDownloadAction removes download`() {
val download = DownloadState(
id = 1337L,
id = "1337",
url = "https://www.mozilla.org", sessionId = tab.id
)

Expand All @@ -360,7 +360,7 @@ class ContentActionTest {
assertEquals(download, tab.content.download)

store.dispatch(
ContentAction.ConsumeDownloadAction(tab.id, downloadId = 1337)
ContentAction.ConsumeDownloadAction(tab.id, downloadId = "1337")
).joinBlocking()

assertNull(tab.content.download)
Expand All @@ -369,7 +369,7 @@ class ContentActionTest {
@Test
fun `ConsumeDownloadAction does not remove download with different id`() {
val download = DownloadState(
id = 1337L,
id = "1337",
url = "https://www.mozilla.org", sessionId = tab.id
)

Expand All @@ -380,7 +380,7 @@ class ContentActionTest {
assertEquals(download, tab.content.download)

store.dispatch(
ContentAction.ConsumeDownloadAction(tab.id, downloadId = 4223)
ContentAction.ConsumeDownloadAction(tab.id, downloadId = "4223")
).joinBlocking()

assertNotNull(tab.content.download)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import mozilla.components.support.test.ext.joinBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Assert.assertSame
import org.junit.Test

class DownloadActionTest {
Expand All @@ -30,6 +31,30 @@ class DownloadActionTest {
assertEquals(2, store.state.downloads.size)
}

@Test
fun `RestoreDownloadStateAction adds download`() {
val store = BrowserStore(BrowserState())

val download1 = DownloadState("https://mozilla.org/download1", destinationDirectory = "")
store.dispatch(DownloadAction.RestoreDownloadStateAction(download1)).joinBlocking()
assertEquals(download1, store.state.downloads[download1.id])
assertEquals(1, store.state.downloads.size)

val download2 = DownloadState("https://mozilla.org/download2", destinationDirectory = "")
store.dispatch(DownloadAction.RestoreDownloadStateAction(download2)).joinBlocking()
assertEquals(download2, store.state.downloads[download2.id])
assertEquals(2, store.state.downloads.size)
}

@Test
fun `RestoreDownloadsStateAction does nothing`() {
val store = BrowserStore(BrowserState())

val state = store.state
store.dispatch(DownloadAction.RestoreDownloadsStateAction).joinBlocking()
assertSame(store.state, state)
}

@Test
fun `RemoveDownloadAction removes download`() {
val store = BrowserStore(BrowserState())
Expand Down
27 changes: 27 additions & 0 deletions components/feature/downloads/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,21 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'

android {
compileSdkVersion config.compileSdkVersion

defaultConfig {
minSdkVersion config.minSdkVersion
targetSdkVersion config.targetSdkVersion
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

kapt {
arguments {
arg("room.schemaLocation", "$projectDir/schemas".toString())
}
}
}

buildTypes {
Expand All @@ -20,6 +28,10 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}

sourceSets {
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
}
}

tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
Expand All @@ -43,6 +55,11 @@ dependencies {
implementation Dependencies.kotlin_stdlib
implementation Dependencies.androidx_recyclerview
implementation Dependencies.androidx_constraintlayout
implementation Dependencies.androidx_room_runtime
implementation Dependencies.androidx_paging
implementation Dependencies.androidx_lifecycle_livedata

kapt Dependencies.androidx_room_compiler

testImplementation Dependencies.androidx_test_core
testImplementation Dependencies.androidx_test_junit
Expand All @@ -52,6 +69,16 @@ dependencies {
testImplementation project(':concept-engine')
testImplementation project(':support-test')
testImplementation project(':support-test-libstate')

androidTestImplementation project(':support-android-test')

androidTestImplementation Dependencies.androidx_room_testing
androidTestImplementation Dependencies.androidx_arch_core_testing
androidTestImplementation Dependencies.androidx_test_core
androidTestImplementation Dependencies.androidx_test_runner
androidTestImplementation Dependencies.androidx_test_rules
androidTestImplementation Dependencies.testing_coroutines

}

apply from: '../../../publish.gradle'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "342d0e5d0a0fcde72b88ac4585caf842",
"entities": [
{
"tableName": "downloads",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `url` TEXT NOT NULL, `file_name` TEXT, `content_type` TEXT, `content_length` INTEGER, `status` INTEGER NOT NULL, `destination_directory` TEXT NOT NULL, `created_at` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "fileName",
"columnName": "file_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "contentType",
"columnName": "content_type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "contentLength",
"columnName": "content_length",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "status",
"columnName": "status",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "destinationDirectory",
"columnName": "destination_directory",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "createdAt",
"columnName": "created_at",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '342d0e5d0a0fcde72b88ac4585caf842')"
]
}
}
Loading

1 comment on commit 4d7378d

@firefoxci-taskcluster
Copy link

Choose a reason for hiding this comment

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

Uh oh! Looks like an error! Details

Failed to get your artifact.

Please sign in to comment.