#283 fixed restoring to missing Download subdir
This commit is contained in:
parent
25ae1a6c1e
commit
38b9f84af0
4 changed files with 67 additions and 47 deletions
|
@ -15,6 +15,10 @@ All notable changes to this project will be documented in this file.
|
||||||
- Slideshow: option for no transition
|
- Slideshow: option for no transition
|
||||||
- Widget: tap action setting
|
- Widget: tap action setting
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- restoring to missing Download subdir
|
||||||
|
|
||||||
## <a id="v1.7.0"></a>[v1.7.0] - 2022-09-19
|
## <a id="v1.7.0"></a>[v1.7.0] - 2022-09-19
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -7,7 +7,6 @@ import android.content.*
|
||||||
import android.media.MediaScannerConnection
|
import android.media.MediaScannerConnection
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Environment
|
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.commonsware.cwac.document.DocumentFileCompat
|
import com.commonsware.cwac.document.DocumentFileCompat
|
||||||
|
@ -391,8 +390,13 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
effectiveTargetDir = targetDir
|
effectiveTargetDir = targetDir
|
||||||
targetDirDocFile = StorageUtils.createDirectoryDocIfAbsent(activity, targetDir)
|
targetDirDocFile = StorageUtils.createDirectoryDocIfAbsent(activity, targetDir)
|
||||||
if (!File(targetDir).exists()) {
|
if (!File(targetDir).exists()) {
|
||||||
callback.onFailure(Exception("failed to create directory at path=$targetDir"))
|
val downloadDirPath = StorageUtils.getDownloadDirPath(activity, targetDir)
|
||||||
return
|
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,
|
targetNameWithoutExtension: String,
|
||||||
write: (OutputStream) -> Unit,
|
write: (OutputStream) -> Unit,
|
||||||
): String {
|
): String {
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && isDownloadDir(activity, targetDir)) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
val targetFileName = "$targetNameWithoutExtension${extensionFor(mimeType)}"
|
val downloadDirPath = StorageUtils.getDownloadDirPath(activity, targetDir)
|
||||||
val values = ContentValues().apply {
|
val isDownloadSubdir = downloadDirPath != null && targetDir.startsWith(downloadDirPath)
|
||||||
put(MediaStore.MediaColumns.DISPLAY_NAME, targetFileName)
|
if (isDownloadSubdir) {
|
||||||
put(MediaStore.MediaColumns.IS_PENDING, 1)
|
val volumePath = StorageUtils.getVolumePath(activity, targetDir)
|
||||||
}
|
val relativePath = targetDir.substring(volumePath?.length ?: 0)
|
||||||
val resolver = activity.contentResolver
|
|
||||||
val uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values)
|
|
||||||
|
|
||||||
uri?.let {
|
val targetFileName = "$targetNameWithoutExtension${extensionFor(mimeType)}"
|
||||||
resolver.openOutputStream(uri)?.use(write)
|
val values = ContentValues().apply {
|
||||||
values.clear()
|
put(MediaStore.MediaColumns.DISPLAY_NAME, targetFileName)
|
||||||
values.put(MediaStore.MediaColumns.IS_PENDING, 0)
|
put(MediaStore.MediaColumns.RELATIVE_PATH, relativePath)
|
||||||
resolver.update(uri, values, null, null)
|
put(MediaStore.MediaColumns.IS_PENDING, 1)
|
||||||
} ?: 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()
|
|
||||||
}
|
}
|
||||||
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 {
|
targetDirDocFile ?: throw Exception("failed to get tree doc for directory at path=$targetDir")
|
||||||
val relativeDir = removeTrailingSeparator(PathSegments(context, dirPath).relativeDir ?: "")
|
|
||||||
return relativeDir == Environment.DIRECTORY_DOWNLOADS
|
// 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(
|
override suspend fun renameMultiple(
|
||||||
|
|
|
@ -105,7 +105,11 @@ object PermissionManager {
|
||||||
val primaryDir = dirSegments.firstOrNull()
|
val primaryDir = dirSegments.firstOrNull()
|
||||||
if (getRestrictedPrimaryDirectories().contains(primaryDir) && dirSegments.size > 1) {
|
if (getRestrictedPrimaryDirectories().contains(primaryDir) && dirSegments.size > 1) {
|
||||||
// request secondary directory (if any) for restricted primary directory
|
// 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 {
|
} else {
|
||||||
primaryDir?.let { dirSet.add(it) }
|
primaryDir?.let { dirSet.add(it) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import android.content.pm.PackageManager
|
||||||
import android.media.MediaMetadataRetriever
|
import android.media.MediaMetadataRetriever
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.os.Environment
|
||||||
import android.os.storage.StorageManager
|
import android.os.storage.StorageManager
|
||||||
import android.provider.DocumentsContract
|
import android.provider.DocumentsContract
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
|
@ -93,6 +94,10 @@ object StorageUtils {
|
||||||
return getVolumePaths(context).firstOrNull { anyPath.startsWith(it) }
|
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?>? {
|
private fun getPathStepIterator(context: Context, anyPath: String, root: String?): Iterator<String?>? {
|
||||||
val rootLength = (root ?: getVolumePath(context, anyPath))?.length ?: return null
|
val rootLength = (root ?: getVolumePath(context, anyPath))?.length ?: return null
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue