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

Copy files to cache instead of using directly. Fixes #178 #219

Merged
merged 2 commits into from
Oct 16, 2020
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
193 changes: 45 additions & 148 deletions app/src/main/java/ro/code4/monitorizarevot/helper/FileUtils.kt
Original file line number Diff line number Diff line change
@@ -1,165 +1,62 @@
package ro.code4.monitorizarevot.helper

import android.content.ContentUris
import android.content.ContentResolver
import android.content.Context
import android.database.Cursor
import android.database.DatabaseUtils
import android.net.Uri
import android.provider.DocumentsContract
import android.provider.MediaStore
import android.util.Log
import android.provider.OpenableColumns
import android.webkit.MimeTypeMap
import java.io.File
import java.io.IOException


/**
* File utilities, thanks to https://github.com/epforgpl/monitorizare-vot-android/
* File utilities
*/
object FileUtils {
/** TAG for log messages. */
internal const val TAG = "FileUtils"
private const val DEBUG = false // Set to true to enable logging

/**
* Get the value of the data column for this Uri. This is useful for
* MediaStore Uris, and other file-based ContentProviders.
*
* @param context The context.
* @param uri The Uri to query.
* @param selection (Optional) Filter used in the query.
* @param selectionArgs (Optional) Selection arguments used in the query.
* @return The value of the _data column, which is typically a file path.
* @author paulburke
*/
fun getDataColumn(
context: Context, uri: Uri?, selection: String?,
selectionArgs: Array<String>?
): String? {

var cursor: Cursor? = null
val column = "_data"
val projection = arrayOf(column)
uri?.let {
try {
cursor =
context.contentResolver.query(it, projection, selection, selectionArgs, null)
cursor?.let { curs ->
@Suppress("ConstantConditionIf")
if (curs.moveToFirst()) {
if (DEBUG)
DatabaseUtils.dumpCursor(curs)

val columnIndex = curs.getColumnIndexOrThrow(column)
return curs.getString(columnIndex)
}
private const val UPLOADS_DIR_NAME = "uploads"

@Throws(IOException::class)
internal fun copyFileToCache(context: Context, uri: Uri): File =
with(context) {
val directory = File(cacheDir, UPLOADS_DIR_NAME)
directory.mkdirs()
File(
directory,
getFileName(uri) ?: generateTempFileName(uri)
).also { f ->
contentResolver.openInputStream(uri)?.use {
f.createNewFile()
it.copyTo(f.outputStream())
}
} finally {
cursor?.close()
}
}
return null
}

/**
* Get a file path from a Uri. This will get the the path for Storage Access
* Framework Documents, as well as the _data field for the MediaStore and
* other file-based ContentProviders.<br></br>
* <br></br>
* Callers should check whether the path is local before assuming it
* represents a local file.
*
*/
fun getPath(context: Context, uri: Uri): String? {

@Suppress("ConstantConditionIf")
if (DEBUG)
Log.d(
"$TAG File -",
"Authority: " + uri.authority +
", Fragment: " + uri.fragment +
", Port: " + uri.port +
", Query: " + uri.query +
", Scheme: " + uri.scheme +
", Host: " + uri.host +
", Segments: " + uri.pathSegments.toString()
)

// DocumentProvider
if (isExternalStorageDocument(uri)) {
val docId = DocumentsContract.getDocumentId(uri)
val split =
docId.split((":").toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
val type = split[0]

if ("primary".equals(type, ignoreCase = true)) {
return "${context.getExternalFilesDir(null)}/${split[1]}"
}

// TODO handle non-primary volumes
} else if (isDownloadsDocument(uri)) {

val id = DocumentsContract.getDocumentId(uri)
val contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id)
)

return getDataColumn(context, contentUri, null, null)
} else if (isMediaDocument(uri)) {
val docId = DocumentsContract.getDocumentId(uri)
val split =
docId.split((":").toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
val type = split[0]

var contentUri: Uri? = null
@Suppress("ConstantConditionIf")
when (type) {
"image" -> contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
"video" -> contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
"audio" -> contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
}

val selection = "_id=?"
val selectionArgs = arrayOf(split[1])

return getDataColumn(context, contentUri, selection, selectionArgs)
}// MediaProvider
// DownloadsProvider
// File
// MediaStore (and general)

return null
}

/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
* @author paulburke
*/
fun isDownloadsDocument(uri: Uri): Boolean {
return "com.android.providers.downloads.documents" == uri.authority
}
private fun Context.getFileName(uri: Uri): String? =
when (uri.scheme) {
ContentResolver.SCHEME_FILE -> uri.path?.let { File(it).name }
ContentResolver.SCHEME_CONTENT -> getCursorContent(uri)
else -> null
}

/**
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
* @author paulburke
*/
fun isExternalStorageDocument(uri: Uri): Boolean {
return "com.android.externalstorage.documents" == uri.authority
}
private fun Context.getCursorContent(uri: Uri): String? =
runCatching {
contentResolver.query(uri, null, null, null, null)
?.let { cursor ->
cursor.run {
if (moveToFirst())
getString(getColumnIndex(OpenableColumns.DISPLAY_NAME))
else null
}.also { cursor.close() }
}
}.getOrNull()

/**
* @param uri The Uri to check.
* @return Whether the Uri authority is Google Photos.
*/
fun isGooglePhotosUri(uri: Uri): Boolean {
return "com.google.android.apps.photos.content" == uri.authority
}
private fun Context.generateTempFileName(uri: Uri): String =
"mon_vot_${System.currentTimeMillis()}.${getMimeType(contentResolver, uri)}"

/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
* @author paulburke
*/
fun isMediaDocument(uri: Uri): Boolean {
return "com.android.providers.media.documents" == uri.authority
}
private fun getMimeType(contentResolver: ContentResolver, uri: Uri): String? =
if (uri.scheme == ContentResolver.SCHEME_CONTENT) {
MimeTypeMap.getSingleton().getExtensionFromMimeType(contentResolver.getType(uri))
} else {
MimeTypeMap.getFileExtensionFromUrl(uri.toString())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -295,32 +295,34 @@ class Repository : KoinComponent {
}

fun saveNote(note: Note): Observable<ResponseBody> =
Single.fromCallable { db.noteDao().save(note) }.toObservable().flatMap {
note.id = it[0].toInt()
Single.fromCallable {
db.noteDao().save(note).first()
}.flatMapObservable {
note.id = it.toInt()
postNote(note)
}

private fun postNote(note: Note): Observable<ResponseBody> {
var body: MultipartBody.Part? = null
var questionId = 0
note.uriPath?.let {
val file = File(it)
val requestFile = file.asRequestBody("multipart/form-data".toMediaTypeOrNull())
body = MultipartBody.Part.createFormData("file", file.name, requestFile)

}
note.questionId?.let {
questionId = it
val noteFile = note.uriPath?.let { File(it) }
val body: MultipartBody.Part? = noteFile?.let {
MultipartBody.Part.createFormData(
"file",
it.name,
it.asRequestBody("multipart/form-data".toMediaTypeOrNull())
)
}
val questionId = note.questionId ?: 0

return apiInterface.postNote(
body, note.countyCode.createMultipart("CountyCode"),
body,
note.countyCode.createMultipart("CountyCode"),
note.pollingStationNumber.toString().createMultipart("PollingStationNumber"),
questionId.toString().createMultipart("QuestionId"),
note.description.createMultipart("Text")
).doOnNext {
note.synced = true
db.noteDao().updateNote(note)
noteFile?.delete()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,10 @@ class NoteViewModel : BaseFormViewModel() {
note.countyCode = countyCode
note.description = text
note.uriPath = noteFile?.absolutePath
repository.saveNote(note).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe(
repository.saveNote(note)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{},
{
Log.d(TAG, it.toString())
Expand All @@ -85,14 +87,15 @@ class NoteViewModel : BaseFormViewModel() {

fun getMediaFromGallery(uri: Uri?) {
uri?.let {
val filePath = FileUtils.getPath(app, it)
if (filePath != null) {
val file = File(filePath)
fileNameLiveData.postValue(file.name)
noteFile = file
} else {
messageIdToastLiveData.postValue(app.getString(ro.code4.monitorizarevot.R.string.error_permission_external_storage))
}
runCatching {
FileUtils.copyFileToCache(app, it)
}.getOrNull()?.let {
fileNameLiveData.postValue(it.name)
noteFile?.delete()
noteFile = it
} ?: messageIdToastLiveData.postValue(
app.getString(R.string.error_permission_external_storage)
)
}
}

Expand Down