#170: improved directory limited source init
This commit is contained in:
parent
2165a4e058
commit
8048dfa7d6
5 changed files with 67 additions and 25 deletions
|
@ -27,6 +27,8 @@ import deckers.thibault.aves.utils.MimeTypes.isImage
|
|||
import deckers.thibault.aves.utils.MimeTypes.isVideo
|
||||
import deckers.thibault.aves.utils.StorageUtils
|
||||
import deckers.thibault.aves.utils.StorageUtils.PathSegments
|
||||
import deckers.thibault.aves.utils.StorageUtils.ensureTrailingSeparator
|
||||
import deckers.thibault.aves.utils.StorageUtils.removeTrailingSeparator
|
||||
import deckers.thibault.aves.utils.UriUtils.tryParseId
|
||||
import java.io.File
|
||||
import java.io.OutputStream
|
||||
|
@ -47,14 +49,33 @@ class MediaStoreImageProvider : ImageProvider() {
|
|||
val knownDate = knownEntries[contentId]
|
||||
return knownDate == null || knownDate < dateModifiedSecs
|
||||
}
|
||||
val handleNew: NewEntryHandler
|
||||
var selection: String? = null
|
||||
var selectionArgs: Array<String>? = null
|
||||
if (directory != null) {
|
||||
selection = "${MediaColumns.PATH} LIKE ?"
|
||||
selectionArgs = arrayOf("${StorageUtils.ensureTrailingSeparator(directory)}%")
|
||||
val relativePathDirectory = ensureTrailingSeparator(directory)
|
||||
val relativePath = PathSegments(context, relativePathDirectory).relativeDir
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && relativePath != null) {
|
||||
selection = "${MediaStore.MediaColumns.RELATIVE_PATH} = ? AND ${MediaColumns.PATH} LIKE ?"
|
||||
selectionArgs = arrayOf(relativePath, "relativePathDirectory%")
|
||||
} else {
|
||||
selection = "${MediaColumns.PATH} LIKE ?"
|
||||
selectionArgs = arrayOf("$relativePathDirectory%")
|
||||
}
|
||||
|
||||
val parentCheckDirectory = removeTrailingSeparator(directory)
|
||||
handleNew = { entry ->
|
||||
// skip entries in subfolders
|
||||
val path = entry["path"] as String?
|
||||
if (path != null && File(path).parent == parentCheckDirectory) {
|
||||
handleNewEntry(entry)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
handleNew = handleNewEntry
|
||||
}
|
||||
fetchFrom(context, isModified, handleNewEntry, IMAGE_CONTENT_URI, IMAGE_PROJECTION, selection = selection, selectionArgs = selectionArgs)
|
||||
fetchFrom(context, isModified, handleNewEntry, VIDEO_CONTENT_URI, VIDEO_PROJECTION, selection = selection, selectionArgs = selectionArgs)
|
||||
fetchFrom(context, isModified, handleNew, IMAGE_CONTENT_URI, IMAGE_PROJECTION, selection, selectionArgs)
|
||||
fetchFrom(context, isModified, handleNew, VIDEO_CONTENT_URI, VIDEO_PROJECTION, selection, selectionArgs)
|
||||
}
|
||||
|
||||
// the provided URI can point to the wrong media collection,
|
||||
|
@ -407,7 +428,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
|||
if (toBin) {
|
||||
val trashDir = StorageUtils.trashDirFor(activity, sourcePath)
|
||||
if (trashDir != null) {
|
||||
effectiveTargetDir = StorageUtils.ensureTrailingSeparator(trashDir.path)
|
||||
effectiveTargetDir = ensureTrailingSeparator(trashDir.path)
|
||||
targetDirDocFile = DocumentFileCompat.fromFile(trashDir)
|
||||
}
|
||||
}
|
||||
|
@ -452,7 +473,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
|||
toBin: Boolean,
|
||||
): FieldMap {
|
||||
val sourcePath = sourceFile.path
|
||||
val sourceDir = sourceFile.parent?.let { StorageUtils.ensureTrailingSeparator(it) }
|
||||
val sourceDir = sourceFile.parent?.let { ensureTrailingSeparator(it) }
|
||||
if (sourceDir == targetDir && !(copy && nameConflictStrategy == NameConflictStrategy.RENAME)) {
|
||||
// nothing to do unless it's a renamed copy
|
||||
return skippedFieldMap
|
||||
|
@ -550,10 +571,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
|||
}
|
||||
|
||||
private fun isDownloadDir(context: Context, dirPath: String): Boolean {
|
||||
var relativeDir = PathSegments(context, dirPath).relativeDir ?: ""
|
||||
if (relativeDir.endsWith(File.separator)) {
|
||||
relativeDir = relativeDir.substring(0, relativeDir.length - 1)
|
||||
}
|
||||
val relativeDir = removeTrailingSeparator(PathSegments(context, dirPath).relativeDir ?: "")
|
||||
return relativeDir == Environment.DIRECTORY_DOWNLOADS
|
||||
}
|
||||
|
||||
|
|
|
@ -294,10 +294,7 @@ object StorageUtils {
|
|||
fun convertDirPathToTreeUri(context: Context, dirPath: String): Uri? {
|
||||
val uuid = getVolumeUuidForTreeUri(context, dirPath)
|
||||
if (uuid != null) {
|
||||
var relativeDir = PathSegments(context, dirPath).relativeDir ?: ""
|
||||
if (relativeDir.endsWith(File.separator)) {
|
||||
relativeDir = relativeDir.substring(0, relativeDir.length - 1)
|
||||
}
|
||||
val relativeDir = removeTrailingSeparator(PathSegments(context, dirPath).relativeDir ?: "")
|
||||
return DocumentsContract.buildTreeDocumentUri("com.android.externalstorage.documents", "$uuid:$relativeDir")
|
||||
}
|
||||
Log.e(LOG_TAG, "failed to convert dirPath=$dirPath to tree URI")
|
||||
|
@ -579,6 +576,10 @@ object StorageUtils {
|
|||
return if (dirPath.endsWith(File.separator)) dirPath else dirPath + File.separator
|
||||
}
|
||||
|
||||
fun removeTrailingSeparator(dirPath: String): String {
|
||||
return if (dirPath.endsWith(File.separator)) dirPath.substring(0, dirPath.length - 1) else dirPath
|
||||
}
|
||||
|
||||
// `fullPath` should match "volumePath + relativeDir + fileName"
|
||||
class PathSegments(context: Context, fullPath: String) {
|
||||
var volumePath: String? = null // `volumePath` with trailing "/"
|
||||
|
|
|
@ -160,13 +160,28 @@ class SqfliteMetadataDb implements MetadataDb {
|
|||
|
||||
@override
|
||||
Future<Set<AvesEntry>> loadEntries({String? directory}) async {
|
||||
String? where;
|
||||
List<Object?>? whereArgs;
|
||||
if (directory != null) {
|
||||
where = 'path LIKE ?';
|
||||
whereArgs = ['$directory%'];
|
||||
final separator = pContext.separator;
|
||||
if (!directory.endsWith(separator)) {
|
||||
directory = '$directory$separator';
|
||||
}
|
||||
|
||||
const where = 'path LIKE ?';
|
||||
final whereArgs = ['$directory%'];
|
||||
final rows = await _db.query(entryTable, where: where, whereArgs: whereArgs);
|
||||
|
||||
final dirLength = directory.length;
|
||||
return rows
|
||||
.whereNot((row) {
|
||||
// skip entries in subfolders
|
||||
final path = row['path'] as String?;
|
||||
return path == null || path.substring(dirLength).contains(separator);
|
||||
})
|
||||
.map(AvesEntry.fromMap)
|
||||
.toSet();
|
||||
}
|
||||
final rows = await _db.query(entryTable, where: where, whereArgs: whereArgs);
|
||||
|
||||
final rows = await _db.query(entryTable);
|
||||
return rows.map(AvesEntry.fromMap).toSet();
|
||||
}
|
||||
|
||||
|
|
|
@ -88,10 +88,11 @@ class MediaStoreSource extends CollectionSource {
|
|||
final knownContentIds = knownDateByContentId.keys.toList();
|
||||
final removedContentIds = (await mediaStoreService.checkObsoleteContentIds(knownContentIds)).toSet();
|
||||
if (topEntries.isNotEmpty) {
|
||||
final obsoleteTopEntries = topEntries.where((entry) => removedContentIds.contains(entry.contentId));
|
||||
await removeEntries(obsoleteTopEntries.map((entry) => entry.uri).toSet(), includeTrash: false);
|
||||
final removedTopEntries = topEntries.where((entry) => removedContentIds.contains(entry.contentId));
|
||||
await removeEntries(removedTopEntries.map((entry) => entry.uri).toSet(), includeTrash: false);
|
||||
}
|
||||
knownEntries.removeWhere((entry) => removedContentIds.contains(entry.contentId));
|
||||
final removedEntries = knownEntries.where((entry) => removedContentIds.contains(entry.contentId)).toSet();
|
||||
knownEntries.removeAll(removedEntries);
|
||||
|
||||
// show known entries
|
||||
debugPrint('$runtimeType refresh ${stopwatch.elapsed} add known entries');
|
||||
|
@ -109,8 +110,10 @@ class MediaStoreSource extends CollectionSource {
|
|||
}
|
||||
|
||||
// clean up obsolete entries
|
||||
debugPrint('$runtimeType refresh ${stopwatch.elapsed} remove obsolete entries');
|
||||
await metadataDb.removeIds(removedContentIds);
|
||||
if (removedEntries.isNotEmpty) {
|
||||
debugPrint('$runtimeType refresh ${stopwatch.elapsed} remove obsolete entries');
|
||||
await metadataDb.removeIds(removedEntries.map((entry) => entry.id));
|
||||
}
|
||||
|
||||
if (directory != null) {
|
||||
// trash
|
||||
|
@ -173,6 +176,7 @@ class MediaStoreSource extends CollectionSource {
|
|||
updateDirectories();
|
||||
}
|
||||
|
||||
debugPrint('$runtimeType refresh ${stopwatch.elapsed} analyze');
|
||||
Set<AvesEntry>? analysisEntries;
|
||||
final analysisIds = analysisController?.entryIds;
|
||||
if (analysisIds != null) {
|
||||
|
@ -180,7 +184,7 @@ class MediaStoreSource extends CollectionSource {
|
|||
}
|
||||
await analyze(analysisController, entries: analysisEntries);
|
||||
|
||||
debugPrint('$runtimeType refresh ${stopwatch.elapsed} done for ${knownEntries.length} known, ${allNewEntries.length} new, ${removedContentIds.length} obsolete');
|
||||
debugPrint('$runtimeType refresh ${stopwatch.elapsed} done for ${knownEntries.length} known, ${allNewEntries.length} new, ${removedEntries.length} removed');
|
||||
},
|
||||
onError: (error) => debugPrint('$runtimeType stream error=$error'),
|
||||
);
|
||||
|
|
|
@ -143,6 +143,10 @@ class _AppDebugPageState extends State<AppDebugPage> {
|
|||
onPressed: () => source.init(directory: '${androidFileUtils.dcimPath}/Camera'),
|
||||
child: const Text('Source refresh (camera)'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => source.init(directory: androidFileUtils.picturesPath),
|
||||
child: const Text('Source refresh (pictures)'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => AnalysisService.startService(force: false),
|
||||
child: const Text('Start analysis service'),
|
||||
|
|
Loading…
Reference in a new issue