Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/student/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ dependencies {
testImplementation Libs.JUNIT
testImplementation Libs.ROBOLECTRIC
testImplementation Libs.ANDROIDX_TEST_JUNIT
testImplementation "io.mockk:mockk:1.9.3"
testImplementation Libs.MOCKK
androidTestImplementation Libs.ANDROIDX_TEST_JUNIT
testImplementation Libs.KOTLIN_COROUTINES_TEST

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import com.instructure.pandautils.activities.BaseViewMediaActivity
import com.instructure.pandautils.loaders.OpenMediaAsyncTaskLoader
import com.instructure.pandautils.utils.Const
import com.instructure.pandautils.utils.LoaderUtils
import com.instructure.pandautils.utils.RouteUtils
import com.instructure.pandautils.utils.nonNullArgs
import com.instructure.student.R
import com.instructure.student.activity.InternalWebViewActivity
Expand Down Expand Up @@ -256,12 +257,11 @@ object RouteMatcher : BaseRouteMatcher() {
handleFullscreenRoute(context, route)
} else {
val isGroupRoute = "groups" == route.uri?.pathSegments?.get(0)
if (route.queryParamsHash.containsKey(RouterParams.PREVIEW)) {
// This is a link for a file preview, so we need to get the file id from the preview query param
handleSpecificFile(context as FragmentActivity, route.queryParamsHash[RouterParams.PREVIEW] ?: "", isGroupRoute)
} else {
handleSpecificFile(context as FragmentActivity, route.paramsHash[RouterParams.FILE_ID] ?: "", isGroupRoute)
}
handleSpecificFile(
context as FragmentActivity,
(if (route.queryParamsHash.containsKey(RouterParams.PREVIEW)) route.queryParamsHash[RouterParams.PREVIEW] else route.paramsHash[RouterParams.FILE_ID]) ?: "",
route,
isGroupRoute)
}
} else if (route.routeContext == RouteContext.MEDIA) {
handleMediaRoute(context, route)
Expand Down Expand Up @@ -354,15 +354,26 @@ object RouteMatcher : BaseRouteMatcher() {
}
}

private fun openMedia(activity: FragmentActivity?, mime: String, url: String, filename: String) {
if (activity != null) {
private fun openMedia(activity: FragmentActivity?, mime: String, url: String, filename: String, route: Route, fileId: String?) {
if (activity == null) {
return
}

// If we're trying to open an HTML file, don't download it. It could be referencing other files
// through a relative URL which we won't be able to access. Instead, just showing the file in
// a webview will load the file the user is trying to view and will resolve all relative paths
if (filename.toLowerCase().endsWith(".htm") || filename.toLowerCase().endsWith(".html")) {
RouteUtils.retrieveFileUrl(route, fileId) { fileUrl, context, needsAuth ->
InternalWebviewFragment.loadInternalWebView(activity, InternalWebviewFragment.makeRoute(context, fileUrl, needsAuth, true))
}
} else {
openMediaCallbacks = null
openMediaBundle = OpenMediaAsyncTaskLoader.createBundle(mime, url, filename)
LoaderUtils.restartLoaderWithBundle<LoaderManager.LoaderCallbacks<OpenMediaAsyncTaskLoader.LoadedMedia>>(LoaderManager.getInstance(activity), openMediaBundle, getLoaderCallbacks(activity), R.id.openMediaLoaderID)
}
}

private fun handleSpecificFile(activity: FragmentActivity, fileID: String?, isGroupFile: Boolean) {
private fun handleSpecificFile(activity: FragmentActivity, fileID: String?, route: Route, isGroupFile: Boolean) {

val fileFolderStatusCallback = object : StatusCallback<FileFolder>() {
override fun onResponse(response: Response<FileFolder>, linkHeaders: LinkHeaders, type: ApiType) {
Expand All @@ -372,7 +383,7 @@ object RouteMatcher : BaseRouteMatcher() {
Toast.makeText(activity, String.format(activity.getString(R.string.fileLocked), if (fileFolder.displayName == null) activity.getString(R.string.file) else fileFolder.displayName), Toast.LENGTH_LONG).show()
} else {
// This is either a group file (which have no permissions), or a file that is accessible by the user
openMedia(activity, fileFolder.contentType!!, fileFolder.url!!, fileFolder.displayName!!)
openMedia(activity, fileFolder.contentType!!, fileFolder.url!!, fileFolder.displayName!!, route, fileID)
}
} ?: Toast.makeText(activity, activity.getString(R.string.errorOccurred), Toast.LENGTH_LONG).show()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import com.instructure.pandautils.activities.BaseViewMediaActivity
import com.instructure.pandautils.loaders.OpenMediaAsyncTaskLoader
import com.instructure.pandautils.utils.Const
import com.instructure.pandautils.utils.LoaderUtils
import com.instructure.pandautils.utils.RouteUtils
import com.instructure.pandautils.utils.nonNullArgs
import com.instructure.teacher.PSPDFKit.AnnotationComments.AnnotationCommentListFragment
import com.instructure.teacher.R
Expand Down Expand Up @@ -170,13 +171,10 @@ object RouteMatcher : BaseRouteMatcher() {
openMedia(context as FragmentActivity, route.uri.toString())
}
} else {
if (route.queryParamsHash.containsKey(RouterParams.PREVIEW)) {
// This is a link for a file preview, so we need to get the file id from the preview query param
handleSpecificFile(context as FragmentActivity, route.queryParamsHash[RouterParams.PREVIEW]
?: "")
} else {
handleSpecificFile(context as FragmentActivity, route.paramsHash[RouterParams.FILE_ID] ?: "")
}
handleSpecificFile(
context as FragmentActivity,
(if (route.queryParamsHash.containsKey(RouterParams.PREVIEW)) route.queryParamsHash[RouterParams.PREVIEW] else route.paramsHash[RouterParams.FILE_ID]) ?: "",
route)
}

} else if (route.routeContext === RouteContext.MEDIA) {
Expand Down Expand Up @@ -514,24 +512,36 @@ object RouteMatcher : BaseRouteMatcher() {
}
}

private fun openMedia(activity: FragmentActivity?, mime: String, url: String, filename: String) {
if (activity != null) {
private fun openMedia(activity: FragmentActivity?, mime: String, url: String, filename: String, route: Route, fileId: String?) {
if (activity == null) {
return
}

// If we're trying to open an HTML file, don't download it. It could be referencing other files
// through a relative URL which we won't be able to access. Instead, just showing the file in
// a webview will load the file the user is trying to view and will resolve all relative paths
if (filename.toLowerCase().endsWith(".htm") || filename.toLowerCase().endsWith(".html")) {
RouteUtils.retrieveFileUrl(route, fileId) { fileUrl, context, needsAuth ->
val bundle = InternalWebViewFragment.makeBundle(url = fileUrl, title = filename, shouldAuthenticate = needsAuth)
RouteMatcher.route(activity, Route(FullscreenInternalWebViewFragment::class.java, context, bundle))
}
} else {
openMediaCallbacks = null
openMediaBundle = OpenMediaAsyncTaskLoader.createBundle(mime, url, filename)
LoaderUtils.restartLoaderWithBundle<LoaderManager.LoaderCallbacks<OpenMediaAsyncTaskLoader.LoadedMedia>>(
LoaderManager.getInstance(activity), openMediaBundle, getLoaderCallbacks(activity), R.id.openMediaLoaderID)
}
}

private fun handleSpecificFile(activity: FragmentActivity, fileID: String?) {
private fun handleSpecificFile(activity: FragmentActivity, fileID: String?, route: Route) {
val fileFolderStatusCallback = object : StatusCallback<FileFolder>() {
override fun onResponse(response: retrofit2.Response<FileFolder>, linkHeaders: com.instructure.canvasapi2.utils.LinkHeaders, type: ApiType) {
super.onResponse(response, linkHeaders, type)
val fileFolder = response.body()
if (fileFolder!!.isLocked || fileFolder.isLockedForUser) {
Toast.makeText(activity, String.format(activity.getString(R.string.fileLocked), if (fileFolder.displayName == null) activity.getString(R.string.file) else fileFolder.displayName), Toast.LENGTH_LONG).show()
} else {
openMedia(activity, fileFolder.contentType!!, fileFolder.url!!, fileFolder.displayName!!)
openMedia(activity, fileFolder.contentType!!, fileFolder.url!!, fileFolder.displayName!!, route, fileID)
}
}
}
Expand Down
1 change: 1 addition & 0 deletions buildSrc/src/main/java/GlobalDependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ object Libs {
const val JUNIT = "junit:junit:${Versions.JUNIT}"
const val ROBOLECTRIC = "org.robolectric:robolectric:${Versions.ROBOLECTRIC}"
const val ANDROIDX_TEST_JUNIT = "androidx.test.ext:junit:1.1.0"
const val MOCKK = "io.mockk:mockk:1.9.3"

/* Other */
const val CRASHLYTICS = "com.crashlytics.sdk.android:crashlytics:${Versions.CRASHLYTICS}"
Expand Down
2 changes: 1 addition & 1 deletion libs/canvas-api-2/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ dependencies {

/* Test Dependencies */
testImplementation Libs.JUNIT
testImplementation "io.mockk:mockk:1.9.3"
testImplementation Libs.MOCKK
testImplementation "org.mockito:mockito-inline:2.25.0"
testImplementation "au.com.dius:pact-jvm-consumer-junit_2.11:3.5.14"
testImplementation group: 'org.slf4j', name: 'slf4j-nop', version: '1.7.26'
Expand Down
1 change: 1 addition & 0 deletions libs/pandautils/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ dependencies {

/* Test Dependencies */
testImplementation Libs.JUNIT
testImplementation Libs.MOCKK

/* Media handling */
api("com.github.bumptech.glide:glide:$glideVersion") {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright (C) 2019 - present Instructure, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.instructure.pandautils.utils

import com.instructure.canvasapi2.models.CanvasContext
import com.instructure.canvasapi2.utils.ApiPrefs
import com.instructure.interactions.router.Route
import com.instructure.interactions.router.RouterParams

object RouteUtils {
fun retrieveFileUrl(route: Route, fileId: String?, block: (url: String, canvasContext: CanvasContext, needsAuth: Boolean) -> Unit) {
var needsAuth = true
var fileUrl = ApiPrefs.fullDomain
var context = CanvasContext.currentUserContext(ApiPrefs.user!!)
route.paramsHash[RouterParams.COURSE_ID]?.let {
context = CanvasContext.getGenericContext(CanvasContext.Type.COURSE, it.toLong())
fileUrl += "/courses/$it"
}
fileUrl += "/files/$fileId/preview"
route.queryParamsHash[RouterParams.VERIFIER]?.let {
needsAuth = false
fileUrl += "?verifier=$it"
}

block.invoke(fileUrl, context, needsAuth)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* Copyright (C) 2017 - present Instructure, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package com.instructure.pandautils.unit

import com.instructure.canvasapi2.models.CanvasContext
import com.instructure.canvasapi2.models.User
import com.instructure.canvasapi2.utils.ApiPrefs
import com.instructure.interactions.router.Route
import com.instructure.interactions.router.RouterParams
import com.instructure.pandautils.utils.RouteUtils
import io.mockk.*
import org.junit.Assert
import org.junit.Before
import org.junit.Test

class RouteUtilsTest : Assert() {

lateinit var route: Route
lateinit var user: User

@Before
fun setup() {
user = User()
route = Route()

mockkStatic(ApiPrefs::class)
every { ApiPrefs.user } returns user
every { ApiPrefs.fullDomain } returns "https://domain.instructure.com"
}

@Test
@Throws(Exception::class)
fun `retrieveFileUrl returns a url with a file id`() {
val fileId = "6"

RouteUtils.retrieveFileUrl(route, fileId) { url, _, _ ->
assertEquals("https://domain.instructure.com/files/$fileId/preview", url)
}
}

@Test
@Throws(Exception::class)
fun `retrieveFileUrl returns a url with a course and a file id`() {
val fileId = "6"
val courseId = "12"
route.paramsHash = hashMapOf(Pair(RouterParams.COURSE_ID, courseId))

RouteUtils.retrieveFileUrl(route, fileId) { url, _, _ ->
assertEquals("https://domain.instructure.com/courses/$courseId/files/$fileId/preview", url)
}
}

@Test
@Throws(Exception::class)
fun `retrieveFileUrl returns a url with a verifier`() {
val fileId = "6"
val verifier = "thisisaverifier"
route.queryParamsHash = hashMapOf(Pair(RouterParams.VERIFIER, verifier))

RouteUtils.retrieveFileUrl(route, fileId) { url, _, _ ->
assertEquals("https://domain.instructure.com/files/$fileId/preview?verifier=$verifier", url)
}
}

@Test
@Throws(Exception::class)
fun `retrieveFileUrl returns that auth is NOT needed with a verifier`() {
val fileId = "6"
val verifier = "thisisaverifier"
route.queryParamsHash = hashMapOf(Pair(RouterParams.VERIFIER, verifier))

RouteUtils.retrieveFileUrl(route, fileId) { _, _, needsAuth ->
assertFalse(needsAuth)
}
}

@Test
@Throws(Exception::class)
fun `retrieveFileUrl returns that auth is needed without a verifier`() {
val fileId = "6"

RouteUtils.retrieveFileUrl(route, fileId) { _, _, needsAuth ->
assertTrue(needsAuth)
}
}

@Test
@Throws(Exception::class)
fun `retrieveFileUrl returns a course context when a course id is provided`() {
val fileId = "6"
val courseId = "12"
route.paramsHash = hashMapOf(Pair(RouterParams.COURSE_ID, courseId))

RouteUtils.retrieveFileUrl(route, fileId) { _, context, _ ->
assertEquals(CanvasContext.getGenericContext(CanvasContext.Type.COURSE, 12), context)
}
}

@Test
@Throws(Exception::class)
fun `retrieveFileUrl returns a user context when no course id is provided`() {
val fileId = "6"

RouteUtils.retrieveFileUrl(route, fileId) { _, context, _ ->
assertEquals(CanvasContext.currentUserContext(user), context)
}
}

}