#283 fixed restoring to missing Download subdir

This commit is contained in:
Thibault Deckers 2022-09-28 21:08:34 +02:00
parent 25ae1a6c1e
commit 38b9f84af0
4 changed files with 67 additions and 47 deletions

View file

@ -15,6 +15,10 @@ All notable changes to this project will be documented in this file.
- Slideshow: option for no transition
- Widget: tap action setting
### Fixed
- restoring to missing Download subdir
## <a id="v1.7.0"></a>[v1.7.0] - 2022-09-19
### Added

View file

@ -7,7 +7,6 @@ import android.content.*
import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import android.util.Log
import com.commonsware.cwac.document.DocumentFileCompat
@ -391,8 +390,13 @@ class MediaStoreImageProvider : ImageProvider() {
effectiveTargetDir = targetDir
targetDirDocFile = StorageUtils.createDirectoryDocIfAbsent(activity, targetDir)
if (!File(targetDir).exists()) {
callback.onFailure(Exception("failed to create directory at path=$targetDir"))
return
val downloadDirPath = StorageUtils.getDownloadDirPath(activity, targetDir)
val isDownloadSubdir = downloadDirPath != null && targetDir.startsWith(downloadDirPath)
// download subdirectories can be created later by Media Store insertion
if (!isDownloadSubdir) {
callback.onFailure(Exception("failed to create directory at path=$targetDir"))
return
}
}
}
@ -535,54 +539,57 @@ class MediaStoreImageProvider : ImageProvider() {
targetNameWithoutExtension: String,
write: (OutputStream) -> Unit,
): String {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && isDownloadDir(activity, targetDir)) {
val targetFileName = "$targetNameWithoutExtension${extensionFor(mimeType)}"
val values = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, targetFileName)
put(MediaStore.MediaColumns.IS_PENDING, 1)
}
val resolver = activity.contentResolver
val uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val downloadDirPath = StorageUtils.getDownloadDirPath(activity, targetDir)
val isDownloadSubdir = downloadDirPath != null && targetDir.startsWith(downloadDirPath)
if (isDownloadSubdir) {
val volumePath = StorageUtils.getVolumePath(activity, targetDir)
val relativePath = targetDir.substring(volumePath?.length ?: 0)
uri?.let {
resolver.openOutputStream(uri)?.use(write)
values.clear()
values.put(MediaStore.MediaColumns.IS_PENDING, 0)
resolver.update(uri, values, null, null)
} ?: throw Exception("MediaStore failed for some reason")
File(targetDir, targetFileName).path
} else {
targetDirDocFile ?: throw Exception("failed to get tree doc for directory at path=$targetDir")
// the file created from a `TreeDocumentFile` is also a `TreeDocumentFile`
// but in order to open an output stream to it, we need to use a `SingleDocumentFile`
// through a document URI, not a tree URI
// note that `DocumentFile.getParentFile()` returns null if we did not pick a tree first
val targetTreeFile = targetDirDocFile.createFile(mimeType, targetNameWithoutExtension)
val targetDocFile = DocumentFileCompat.fromSingleUri(activity, targetTreeFile.uri)
try {
targetDocFile.openOutputStream().use(write)
} catch (e: Exception) {
// remove empty file
if (targetDocFile.exists()) {
targetDocFile.delete()
val targetFileName = "$targetNameWithoutExtension${extensionFor(mimeType)}"
val values = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, targetFileName)
put(MediaStore.MediaColumns.RELATIVE_PATH, relativePath)
put(MediaStore.MediaColumns.IS_PENDING, 1)
}
throw e
val resolver = activity.contentResolver
val uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values)
uri?.let {
resolver.openOutputStream(uri)?.use(write)
values.clear()
values.put(MediaStore.MediaColumns.IS_PENDING, 0)
resolver.update(uri, values, null, null)
} ?: throw Exception("MediaStore failed for some reason")
return File(targetDir, targetFileName).path
}
// the source file name and the created document file name can be different when:
// - a file with the same name already exists, some implementations give a suffix like ` (1)`, some *do not*
// - the original extension does not match the extension added by the underlying provider
val fileName = targetDocFile.name
targetDir + fileName
}
}
private fun isDownloadDir(context: Context, dirPath: String): Boolean {
val relativeDir = removeTrailingSeparator(PathSegments(context, dirPath).relativeDir ?: "")
return relativeDir == Environment.DIRECTORY_DOWNLOADS
targetDirDocFile ?: throw Exception("failed to get tree doc for directory at path=$targetDir")
// the file created from a `TreeDocumentFile` is also a `TreeDocumentFile`
// but in order to open an output stream to it, we need to use a `SingleDocumentFile`
// through a document URI, not a tree URI
// note that `DocumentFile.getParentFile()` returns null if we did not pick a tree first
val targetTreeFile = targetDirDocFile.createFile(mimeType, targetNameWithoutExtension)
val targetDocFile = DocumentFileCompat.fromSingleUri(activity, targetTreeFile.uri)
try {
targetDocFile.openOutputStream().use(write)
} catch (e: Exception) {
// remove empty file
if (targetDocFile.exists()) {
targetDocFile.delete()
}
throw e
}
// the source file name and the created document file name can be different when:
// - a file with the same name already exists, some implementations give a suffix like ` (1)`, some *do not*
// - the original extension does not match the extension added by the underlying provider
val fileName = targetDocFile.name
return targetDir + fileName
}
override suspend fun renameMultiple(

View file

@ -105,7 +105,11 @@ object PermissionManager {
val primaryDir = dirSegments.firstOrNull()
if (getRestrictedPrimaryDirectories().contains(primaryDir) && dirSegments.size > 1) {
// request secondary directory (if any) for restricted primary directory
dirSet.add(dirSegments.take(2).joinToString(File.separator))
val dir = dirSegments.take(2).joinToString(File.separator)
// only register directories that exist on storage, so they can be selected for access grant
if (File(volumePath, dir).exists()) {
dirSet.add(dir)
}
} else {
primaryDir?.let { dirSet.add(it) }
}

View file

@ -9,6 +9,7 @@ import android.content.pm.PackageManager
import android.media.MediaMetadataRetriever
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.os.storage.StorageManager
import android.provider.DocumentsContract
import android.provider.MediaStore
@ -93,6 +94,10 @@ object StorageUtils {
return getVolumePaths(context).firstOrNull { anyPath.startsWith(it) }
}
fun getDownloadDirPath(context: Context, anyPath: String): String? {
return getVolumePath(context, anyPath)?.let { volumePath -> ensureTrailingSeparator(File(volumePath, Environment.DIRECTORY_DOWNLOADS).path) }
}
private fun getPathStepIterator(context: Context, anyPath: String, root: String?): Iterator<String?>? {
val rootLength = (root ?: getVolumePath(context, anyPath))?.length ?: return null