source: disable analysis for widget, screen saver; disabling analysis also disables entry discovery
This commit is contained in:
parent
618b63bfc0
commit
211f803afe
17 changed files with 64 additions and 86 deletions
|
@ -296,14 +296,11 @@ open class MainActivity : FlutterFragmentActivity() {
|
||||||
open fun extractIntentData(intent: Intent?): FieldMap {
|
open fun extractIntentData(intent: Intent?): FieldMap {
|
||||||
when (val action = intent?.action) {
|
when (val action = intent?.action) {
|
||||||
Intent.ACTION_MAIN -> {
|
Intent.ACTION_MAIN -> {
|
||||||
val fields = HashMap<String, Any?>()
|
return hashMapOf(
|
||||||
if (intent.getBooleanExtra(EXTRA_KEY_SAFE_MODE, false)) {
|
INTENT_DATA_KEY_PAGE to intent.getStringExtra(EXTRA_KEY_PAGE),
|
||||||
fields[INTENT_DATA_KEY_SAFE_MODE] = true
|
INTENT_DATA_KEY_FILTERS to extractFiltersFromIntent(intent),
|
||||||
}
|
INTENT_DATA_KEY_EXPLORER_PATH to intent.getStringExtra(EXTRA_KEY_EXPLORER_PATH),
|
||||||
fields[INTENT_DATA_KEY_PAGE] = intent.getStringExtra(EXTRA_KEY_PAGE)
|
)
|
||||||
fields[INTENT_DATA_KEY_FILTERS] = extractFiltersFromIntent(intent)
|
|
||||||
fields[INTENT_DATA_KEY_EXPLORER_PATH] = intent.getStringExtra(EXTRA_KEY_EXPLORER_PATH)
|
|
||||||
return fields
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Intent.ACTION_VIEW,
|
Intent.ACTION_VIEW,
|
||||||
|
@ -557,7 +554,6 @@ open class MainActivity : FlutterFragmentActivity() {
|
||||||
const val INTENT_DATA_KEY_MIME_TYPE = "mimeType"
|
const val INTENT_DATA_KEY_MIME_TYPE = "mimeType"
|
||||||
const val INTENT_DATA_KEY_PAGE = "page"
|
const val INTENT_DATA_KEY_PAGE = "page"
|
||||||
const val INTENT_DATA_KEY_QUERY = "query"
|
const val INTENT_DATA_KEY_QUERY = "query"
|
||||||
const val INTENT_DATA_KEY_SAFE_MODE = "safeMode"
|
|
||||||
const val INTENT_DATA_KEY_SECURE_URIS = "secureUris"
|
const val INTENT_DATA_KEY_SECURE_URIS = "secureUris"
|
||||||
const val INTENT_DATA_KEY_URI = "uri"
|
const val INTENT_DATA_KEY_URI = "uri"
|
||||||
const val INTENT_DATA_KEY_WIDGET_ID = "widgetId"
|
const val INTENT_DATA_KEY_WIDGET_ID = "widgetId"
|
||||||
|
@ -566,7 +562,6 @@ open class MainActivity : FlutterFragmentActivity() {
|
||||||
const val EXTRA_KEY_EXPLORER_PATH = "explorerPath"
|
const val EXTRA_KEY_EXPLORER_PATH = "explorerPath"
|
||||||
const val EXTRA_KEY_FILTERS_ARRAY = "filters"
|
const val EXTRA_KEY_FILTERS_ARRAY = "filters"
|
||||||
const val EXTRA_KEY_FILTERS_STRING = "filtersString"
|
const val EXTRA_KEY_FILTERS_STRING = "filtersString"
|
||||||
const val EXTRA_KEY_SAFE_MODE = "safeMode"
|
|
||||||
const val EXTRA_KEY_WIDGET_ID = "widgetId"
|
const val EXTRA_KEY_WIDGET_ID = "widgetId"
|
||||||
|
|
||||||
// dart page routes
|
// dart page routes
|
||||||
|
|
|
@ -21,15 +21,11 @@ class MediaStoreStreamHandler(private val context: Context, arguments: Any?) : E
|
||||||
|
|
||||||
private var knownEntries: Map<Long?, Int?>? = null
|
private var knownEntries: Map<Long?, Int?>? = null
|
||||||
private var directory: String? = null
|
private var directory: String? = null
|
||||||
private var safe: Boolean = false
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (arguments is Map<*, *>) {
|
if (arguments is Map<*, *>) {
|
||||||
knownEntries = (arguments["knownEntries"] as? Map<*, *>?)?.map { (it.key as Number?)?.toLong() to it.value as Int? }?.toMap()
|
knownEntries = (arguments["knownEntries"] as? Map<*, *>?)?.map { (it.key as Number?)?.toLong() to it.value as Int? }?.toMap()
|
||||||
directory = arguments["directory"] as String?
|
directory = arguments["directory"] as String?
|
||||||
// do not use kotlin.collections `getOrDefault` as it crashes on API <24
|
|
||||||
// and there is no warning from Android Studio
|
|
||||||
safe = arguments["safe"] as Boolean? ?: false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +59,7 @@ class MediaStoreStreamHandler(private val context: Context, arguments: Any?) : E
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchAll() {
|
private fun fetchAll() {
|
||||||
MediaStoreImageProvider().fetchAll(context, knownEntries ?: emptyMap(), directory, safe) { success(it) }
|
MediaStoreImageProvider().fetchAll(context, knownEntries ?: emptyMap(), directory) { success(it) }
|
||||||
endOfStream()
|
endOfStream()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ import android.content.Context
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.media.MediaMetadataRetriever
|
import android.media.MediaMetadataRetriever
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.exifinterface.media.ExifInterfaceFork as ExifInterface
|
|
||||||
import com.drew.metadata.avi.AviDirectory
|
import com.drew.metadata.avi.AviDirectory
|
||||||
import com.drew.metadata.exif.ExifIFD0Directory
|
import com.drew.metadata.exif.ExifIFD0Directory
|
||||||
import com.drew.metadata.jpeg.JpegDirectory
|
import com.drew.metadata.jpeg.JpegDirectory
|
||||||
|
@ -29,6 +28,7 @@ import deckers.thibault.aves.utils.StorageUtils
|
||||||
import deckers.thibault.aves.utils.UriUtils.tryParseId
|
import deckers.thibault.aves.utils.UriUtils.tryParseId
|
||||||
import org.beyka.tiffbitmapfactory.TiffBitmapFactory
|
import org.beyka.tiffbitmapfactory.TiffBitmapFactory
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import androidx.exifinterface.media.ExifInterfaceFork as ExifInterface
|
||||||
|
|
||||||
class SourceEntry {
|
class SourceEntry {
|
||||||
private val origin: Int
|
private val origin: Int
|
||||||
|
@ -116,8 +116,8 @@ class SourceEntry {
|
||||||
// metadata retrieval
|
// metadata retrieval
|
||||||
// expects entry with: uri, mimeType
|
// expects entry with: uri, mimeType
|
||||||
// finds: width, height, orientation/rotation, date, title, duration
|
// finds: width, height, orientation/rotation, date, title, duration
|
||||||
fun fillPreCatalogMetadata(context: Context, safe: Boolean): SourceEntry {
|
fun fillPreCatalogMetadata(context: Context): SourceEntry {
|
||||||
if (isSvg || safe) return this
|
if (isSvg) return this
|
||||||
if (isVideo) {
|
if (isVideo) {
|
||||||
fillVideoByMediaMetadataRetriever(context)
|
fillVideoByMediaMetadataRetriever(context)
|
||||||
if (isSized && hasDuration) return this
|
if (isSized && hasDuration) return this
|
||||||
|
|
|
@ -53,7 +53,7 @@ internal class FileImageProvider : ImageProvider() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
entry.fillPreCatalogMetadata(context, safe = false)
|
entry.fillPreCatalogMetadata(context)
|
||||||
|
|
||||||
if (allowUnsized || entry.isSized || entry.isSvg || entry.isVideo) {
|
if (allowUnsized || entry.isSized || entry.isSvg || entry.isVideo) {
|
||||||
callback.onSuccess(entry.toMap())
|
callback.onSuccess(entry.toMap())
|
||||||
|
|
|
@ -51,10 +51,9 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
context: Context,
|
context: Context,
|
||||||
knownEntries: Map<Long?, Int?>,
|
knownEntries: Map<Long?, Int?>,
|
||||||
directory: String?,
|
directory: String?,
|
||||||
safe: Boolean,
|
|
||||||
handleNewEntry: NewEntryHandler,
|
handleNewEntry: NewEntryHandler,
|
||||||
) {
|
) {
|
||||||
Log.d(LOG_TAG, "fetching all media store items for ${knownEntries.size} known entries, directory=$directory safe=$safe")
|
Log.d(LOG_TAG, "fetching all media store items for ${knownEntries.size} known entries, directory=$directory")
|
||||||
val isModified = fun(contentId: Long, dateModifiedSecs: Int): Boolean {
|
val isModified = fun(contentId: Long, dateModifiedSecs: Int): Boolean {
|
||||||
val knownDate = knownEntries[contentId]
|
val knownDate = knownEntries[contentId]
|
||||||
return knownDate == null || knownDate < dateModifiedSecs
|
return knownDate == null || knownDate < dateModifiedSecs
|
||||||
|
@ -84,8 +83,8 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
} else {
|
} else {
|
||||||
handleNew = handleNewEntry
|
handleNew = handleNewEntry
|
||||||
}
|
}
|
||||||
fetchFrom(context, isModified, handleNew, IMAGE_CONTENT_URI, IMAGE_PROJECTION, selection, selectionArgs, safe = safe)
|
fetchFrom(context, isModified, handleNew, IMAGE_CONTENT_URI, IMAGE_PROJECTION, selection, selectionArgs)
|
||||||
fetchFrom(context, isModified, handleNew, VIDEO_CONTENT_URI, VIDEO_PROJECTION, selection, selectionArgs, safe = safe)
|
fetchFrom(context, isModified, handleNew, VIDEO_CONTENT_URI, VIDEO_PROJECTION, selection, selectionArgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// the provided URI can point to the wrong media collection,
|
// the provided URI can point to the wrong media collection,
|
||||||
|
@ -208,7 +207,6 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
selection: String? = null,
|
selection: String? = null,
|
||||||
selectionArgs: Array<String>? = null,
|
selectionArgs: Array<String>? = null,
|
||||||
fileMimeType: String? = null,
|
fileMimeType: String? = null,
|
||||||
safe: Boolean = false,
|
|
||||||
): Boolean {
|
): Boolean {
|
||||||
var found = false
|
var found = false
|
||||||
val orderBy = "${MediaStore.MediaColumns.DATE_MODIFIED} DESC"
|
val orderBy = "${MediaStore.MediaColumns.DATE_MODIFIED} DESC"
|
||||||
|
@ -302,7 +300,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
// missing some attributes such as width, height, orientation.
|
// missing some attributes such as width, height, orientation.
|
||||||
// Also, the reported size of raw images is inconsistent across devices
|
// Also, the reported size of raw images is inconsistent across devices
|
||||||
// and Android versions (sometimes the raw size, sometimes the decoded size).
|
// and Android versions (sometimes the raw size, sometimes the decoded size).
|
||||||
val entry = SourceEntry(entryMap).fillPreCatalogMetadata(context, safe)
|
val entry = SourceEntry(entryMap).fillPreCatalogMetadata(context)
|
||||||
entryMap = entry.toMap()
|
entryMap = entry.toMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,7 @@ open class UnknownContentProvider : ImageProvider() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val entry = SourceEntry(fields).fillPreCatalogMetadata(context, safe = false)
|
val entry = SourceEntry(fields).fillPreCatalogMetadata(context)
|
||||||
if (allowUnsized || entry.isSized || entry.isSvg || entry.isVideo) {
|
if (allowUnsized || entry.isSized || entry.isSvg || entry.isVideo) {
|
||||||
callback.onSuccess(entry.toMap())
|
callback.onSuccess(entry.toMap())
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -21,7 +21,6 @@ class IntentDataKeys {
|
||||||
static const mimeType = 'mimeType';
|
static const mimeType = 'mimeType';
|
||||||
static const page = 'page';
|
static const page = 'page';
|
||||||
static const query = 'query';
|
static const query = 'query';
|
||||||
static const safeMode = 'safeMode';
|
|
||||||
static const secureUris = 'secureUris';
|
static const secureUris = 'secureUris';
|
||||||
static const uri = 'uri';
|
static const uri = 'uri';
|
||||||
static const widgetId = 'widgetId';
|
static const widgetId = 'widgetId';
|
||||||
|
|
|
@ -31,7 +31,7 @@ import 'package:collection/collection.dart';
|
||||||
import 'package:event_bus/event_bus.dart';
|
import 'package:event_bus/event_bus.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
enum SourceInitializationState { none, directory, full }
|
enum SourceScope { none, album, full }
|
||||||
|
|
||||||
mixin SourceBase {
|
mixin SourceBase {
|
||||||
EventBus get eventBus;
|
EventBus get eventBus;
|
||||||
|
@ -93,7 +93,7 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
|
||||||
_rawEntries.forEach((v) => v.dispose());
|
_rawEntries.forEach((v) => v.dispose());
|
||||||
}
|
}
|
||||||
|
|
||||||
set safeMode(bool enabled);
|
set canAnalyze(bool enabled);
|
||||||
|
|
||||||
final EventBus _eventBus = EventBus();
|
final EventBus _eventBus = EventBus();
|
||||||
|
|
||||||
|
@ -427,13 +427,12 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
|
||||||
eventBus.fire(EntryMovedEvent(MoveType.move, movedEntries));
|
eventBus.fire(EntryMovedEvent(MoveType.move, movedEntries));
|
||||||
}
|
}
|
||||||
|
|
||||||
SourceInitializationState get initState => SourceInitializationState.none;
|
SourceScope get scope => SourceScope.none;
|
||||||
|
|
||||||
Future<void> init({
|
Future<void> init({
|
||||||
AnalysisController? analysisController,
|
AnalysisController? analysisController,
|
||||||
String? directory,
|
AlbumFilter? albumFilter,
|
||||||
bool loadTopEntriesFirst = false,
|
bool loadTopEntriesFirst = false,
|
||||||
bool canAnalyze = true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<Set<String>> refreshUris(Set<String> changedUris, {AnalysisController? analysisController});
|
Future<Set<String>> refreshUris(Set<String> changedUris, {AnalysisController? analysisController});
|
||||||
|
@ -518,13 +517,13 @@ abstract class CollectionSource with SourceBase, AlbumMixin, CountryMixin, Place
|
||||||
|
|
||||||
// monitoring
|
// monitoring
|
||||||
|
|
||||||
bool _monitoring = true;
|
bool _canRefresh = true;
|
||||||
|
|
||||||
void pauseMonitoring() => _monitoring = false;
|
void pauseMonitoring() => _canRefresh = false;
|
||||||
|
|
||||||
void resumeMonitoring() => _monitoring = true;
|
void resumeMonitoring() => _canRefresh = true;
|
||||||
|
|
||||||
bool get isMonitoring => _monitoring;
|
bool get canRefresh => _canRefresh;
|
||||||
|
|
||||||
// filter summary
|
// filter summary
|
||||||
|
|
||||||
|
|
|
@ -21,36 +21,34 @@ class MediaStoreSource extends CollectionSource {
|
||||||
final Debouncer _changeDebouncer = Debouncer(delay: ADurations.mediaContentChangeDebounceDelay);
|
final Debouncer _changeDebouncer = Debouncer(delay: ADurations.mediaContentChangeDebounceDelay);
|
||||||
final Set<String> _changedUris = {};
|
final Set<String> _changedUris = {};
|
||||||
int? _lastGeneration;
|
int? _lastGeneration;
|
||||||
SourceInitializationState _initState = SourceInitializationState.none;
|
SourceScope _scope = SourceScope.none;
|
||||||
bool _safeMode = false;
|
bool _canAnalyze = true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
set safeMode(bool enabled) => _safeMode = enabled;
|
set canAnalyze(bool enabled) => _canAnalyze = enabled;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
SourceInitializationState get initState => _initState;
|
SourceScope get scope => _scope;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> init({
|
Future<void> init({
|
||||||
AnalysisController? analysisController,
|
AnalysisController? analysisController,
|
||||||
String? directory,
|
AlbumFilter? albumFilter,
|
||||||
bool loadTopEntriesFirst = false,
|
bool loadTopEntriesFirst = false,
|
||||||
bool canAnalyze = true,
|
|
||||||
}) async {
|
}) async {
|
||||||
await reportService.log('$runtimeType init directory=$directory');
|
await reportService.log('$runtimeType init album=${albumFilter?.album}');
|
||||||
if (_initState == SourceInitializationState.none) {
|
if (_scope == SourceScope.none) {
|
||||||
await _loadEssentials();
|
await _loadEssentials();
|
||||||
}
|
}
|
||||||
if (_initState != SourceInitializationState.full) {
|
if (_scope != SourceScope.full) {
|
||||||
_initState = directory != null ? SourceInitializationState.directory : SourceInitializationState.full;
|
_scope = albumFilter != null ? SourceScope.album : SourceScope.full;
|
||||||
}
|
}
|
||||||
addDirectories(albums: settings.pinnedFilters.whereType<AlbumFilter>().map((v) => v.album).toSet());
|
addDirectories(albums: settings.pinnedFilters.whereType<AlbumFilter>().map((v) => v.album).toSet());
|
||||||
await updateGeneration();
|
await updateGeneration();
|
||||||
unawaited(_loadEntries(
|
unawaited(_loadEntries(
|
||||||
analysisController: analysisController,
|
analysisController: analysisController,
|
||||||
directory: directory,
|
directory: albumFilter?.album,
|
||||||
loadTopEntriesFirst: loadTopEntriesFirst,
|
loadTopEntriesFirst: loadTopEntriesFirst,
|
||||||
canAnalyze: canAnalyze && !_safeMode,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,7 +78,6 @@ class MediaStoreSource extends CollectionSource {
|
||||||
AnalysisController? analysisController,
|
AnalysisController? analysisController,
|
||||||
String? directory,
|
String? directory,
|
||||||
required bool loadTopEntriesFirst,
|
required bool loadTopEntriesFirst,
|
||||||
required bool canAnalyze,
|
|
||||||
}) async {
|
}) async {
|
||||||
unawaited(reportService.log('$runtimeType load start'));
|
unawaited(reportService.log('$runtimeType load start'));
|
||||||
final stopwatch = Stopwatch()..start();
|
final stopwatch = Stopwatch()..start();
|
||||||
|
@ -158,6 +155,12 @@ class MediaStoreSource extends CollectionSource {
|
||||||
knownDateByContentId[contentId] = 0;
|
knownDateByContentId[contentId] = 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!_canAnalyze) {
|
||||||
|
// it can discover new entries only if it can analyze them
|
||||||
|
state = SourceState.ready;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// items to add to the collection
|
// items to add to the collection
|
||||||
final newEntries = <AvesEntry>{};
|
final newEntries = <AvesEntry>{};
|
||||||
|
|
||||||
|
@ -169,7 +172,7 @@ class MediaStoreSource extends CollectionSource {
|
||||||
|
|
||||||
// fetch new & modified entries
|
// fetch new & modified entries
|
||||||
debugPrint('$runtimeType load ${stopwatch.elapsed} fetch new entries');
|
debugPrint('$runtimeType load ${stopwatch.elapsed} fetch new entries');
|
||||||
mediaStoreService.getEntries(_safeMode, knownDateByContentId, directory: directory).listen(
|
mediaStoreService.getEntries(knownDateByContentId, directory: directory).listen(
|
||||||
(entry) {
|
(entry) {
|
||||||
// when discovering modified entry with known content ID,
|
// when discovering modified entry with known content ID,
|
||||||
// reuse known entry ID to overwrite it while preserving favourites, etc.
|
// reuse known entry ID to overwrite it while preserving favourites, etc.
|
||||||
|
@ -210,11 +213,7 @@ class MediaStoreSource extends CollectionSource {
|
||||||
if (analysisIds != null) {
|
if (analysisIds != null) {
|
||||||
analysisEntries = visibleEntries.where((entry) => analysisIds.contains(entry.id)).toSet();
|
analysisEntries = visibleEntries.where((entry) => analysisIds.contains(entry.id)).toSet();
|
||||||
}
|
}
|
||||||
if (canAnalyze) {
|
await analyze(analysisController, entries: analysisEntries);
|
||||||
await analyze(analysisController, entries: analysisEntries);
|
|
||||||
} else {
|
|
||||||
state = SourceState.ready;
|
|
||||||
}
|
|
||||||
|
|
||||||
// the home page may not reflect the current derived filters
|
// the home page may not reflect the current derived filters
|
||||||
// as the initial addition of entries is silent,
|
// as the initial addition of entries is silent,
|
||||||
|
@ -234,7 +233,7 @@ class MediaStoreSource extends CollectionSource {
|
||||||
// sometimes yields an entry with its temporary path: `/data/sec/camera/!@#$%^..._temp.jpg`
|
// sometimes yields an entry with its temporary path: `/data/sec/camera/!@#$%^..._temp.jpg`
|
||||||
@override
|
@override
|
||||||
Future<Set<String>> refreshUris(Set<String> changedUris, {AnalysisController? analysisController}) async {
|
Future<Set<String>> refreshUris(Set<String> changedUris, {AnalysisController? analysisController}) async {
|
||||||
if (_initState == SourceInitializationState.none || !isMonitoring || !isReady) return changedUris;
|
if (_scope == SourceScope.none || !canRefresh || !isReady) return changedUris;
|
||||||
|
|
||||||
state = SourceState.loading;
|
state = SourceState.loading;
|
||||||
|
|
||||||
|
@ -272,7 +271,8 @@ class MediaStoreSource extends CollectionSource {
|
||||||
if (volume != null) {
|
if (volume != null) {
|
||||||
if (existingEntry != null) {
|
if (existingEntry != null) {
|
||||||
entriesToRefresh.add(existingEntry);
|
entriesToRefresh.add(existingEntry);
|
||||||
} else {
|
} else if (_canAnalyze) {
|
||||||
|
// it can discover new entries only if it can analyze them
|
||||||
sourceEntry.id = localMediaDb.nextId;
|
sourceEntry.id = localMediaDb.nextId;
|
||||||
newEntries.add(sourceEntry);
|
newEntries.add(sourceEntry);
|
||||||
}
|
}
|
||||||
|
@ -329,10 +329,6 @@ class MediaStoreSource extends CollectionSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
void onStoreChanged(String? uri) {
|
void onStoreChanged(String? uri) {
|
||||||
// dismiss changes if the source is only loaded to view a specific directory
|
|
||||||
// to let the main instance handle the change in the database
|
|
||||||
if (_initState == SourceInitializationState.directory) return;
|
|
||||||
|
|
||||||
if (uri != null) _changedUris.add(uri);
|
if (uri != null) _changedUris.add(uri);
|
||||||
if (_changedUris.isNotEmpty) {
|
if (_changedUris.isNotEmpty) {
|
||||||
_changeDebouncer(() async {
|
_changeDebouncer(() async {
|
||||||
|
|
|
@ -15,7 +15,7 @@ abstract class MediaStoreService {
|
||||||
Future<int?> getGeneration();
|
Future<int?> getGeneration();
|
||||||
|
|
||||||
// knownEntries: map of contentId -> dateModifiedSecs
|
// knownEntries: map of contentId -> dateModifiedSecs
|
||||||
Stream<AvesEntry> getEntries(bool safe, Map<int?, int?> knownEntries, {String? directory});
|
Stream<AvesEntry> getEntries(Map<int?, int?> knownEntries, {String? directory});
|
||||||
|
|
||||||
// returns media URI
|
// returns media URI
|
||||||
Future<Uri?> scanFile(String path, String mimeType);
|
Future<Uri?> scanFile(String path, String mimeType);
|
||||||
|
@ -77,13 +77,12 @@ class PlatformMediaStoreService implements MediaStoreService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<AvesEntry> getEntries(bool safe, Map<int?, int?> knownEntries, {String? directory}) {
|
Stream<AvesEntry> getEntries(Map<int?, int?> knownEntries, {String? directory}) {
|
||||||
try {
|
try {
|
||||||
return _stream
|
return _stream
|
||||||
.receiveBroadcastStream(<String, dynamic>{
|
.receiveBroadcastStream(<String, dynamic>{
|
||||||
'knownEntries': knownEntries,
|
'knownEntries': knownEntries,
|
||||||
'directory': directory,
|
'directory': directory,
|
||||||
'safe': safe,
|
|
||||||
})
|
})
|
||||||
.where((event) => event is Map)
|
.where((event) => event is Map)
|
||||||
.map((event) => AvesEntry.fromMap(event as Map));
|
.map((event) => AvesEntry.fromMap(event as Map));
|
||||||
|
|
|
@ -96,7 +96,8 @@ Future<AvesEntry?> _getWidgetEntry(int widgetId, bool reuseEntry) async {
|
||||||
readyCompleter.complete();
|
readyCompleter.complete();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await source.init(canAnalyze: false);
|
source.canAnalyze = false;
|
||||||
|
await source.init();
|
||||||
await readyCompleter.future;
|
await readyCompleter.future;
|
||||||
|
|
||||||
final entries = CollectionLens(source: source, filters: filters).sortedEntries;
|
final entries = CollectionLens(source: source, filters: filters).sortedEntries;
|
||||||
|
|
|
@ -683,7 +683,7 @@ class _AvesAppState extends State<AvesApp> with WidgetsBindingObserver {
|
||||||
|
|
||||||
Future<void> _onAnalysisCompletion() async {
|
Future<void> _onAnalysisCompletion() async {
|
||||||
debugPrint('Analysis completed');
|
debugPrint('Analysis completed');
|
||||||
if (_mediaStoreSource.initState != SourceInitializationState.none) {
|
if (_mediaStoreSource.scope != SourceScope.none) {
|
||||||
await _mediaStoreSource.loadCatalogMetadata();
|
await _mediaStoreSource.loadCatalogMetadata();
|
||||||
await _mediaStoreSource.loadAddresses();
|
await _mediaStoreSource.loadAddresses();
|
||||||
_mediaStoreSource.updateDerivedFilters();
|
_mediaStoreSource.updateDerivedFilters();
|
||||||
|
|
|
@ -35,9 +35,10 @@ Future<String?> pickAlbum({
|
||||||
required MoveType? moveType,
|
required MoveType? moveType,
|
||||||
}) async {
|
}) async {
|
||||||
final source = context.read<CollectionSource>();
|
final source = context.read<CollectionSource>();
|
||||||
if (source.initState != SourceInitializationState.full) {
|
if (source.scope != SourceScope.full) {
|
||||||
await reportService.log('Complete source initialization to pick album');
|
await reportService.log('Complete source initialization to pick album');
|
||||||
// source may not be fully initialized in view mode
|
// source may not be fully initialized in view mode
|
||||||
|
source.canAnalyze = true;
|
||||||
await source.init();
|
await source.init();
|
||||||
}
|
}
|
||||||
final filter = await Navigator.maybeOf(context)?.push(
|
final filter = await Navigator.maybeOf(context)?.push(
|
||||||
|
|
|
@ -98,7 +98,6 @@ class _HomePageState extends State<HomePage> {
|
||||||
var appMode = AppMode.main;
|
var appMode = AppMode.main;
|
||||||
var error = false;
|
var error = false;
|
||||||
final intentData = widget.intentData ?? await IntentService.getIntentData();
|
final intentData = widget.intentData ?? await IntentService.getIntentData();
|
||||||
final safeMode = (intentData[IntentDataKeys.safeMode] as bool?) ?? false;
|
|
||||||
final intentAction = intentData[IntentDataKeys.action] as String?;
|
final intentAction = intentData[IntentDataKeys.action] as String?;
|
||||||
_initialFilters = null;
|
_initialFilters = null;
|
||||||
_initialExplorerPath = null;
|
_initialExplorerPath = null;
|
||||||
|
@ -223,19 +222,16 @@ class _HomePageState extends State<HomePage> {
|
||||||
unawaited(GlobalSearch.registerCallback());
|
unawaited(GlobalSearch.registerCallback());
|
||||||
unawaited(AnalysisService.registerCallback());
|
unawaited(AnalysisService.registerCallback());
|
||||||
final source = context.read<CollectionSource>();
|
final source = context.read<CollectionSource>();
|
||||||
source.safeMode = safeMode;
|
if (source.scope != SourceScope.full) {
|
||||||
if (source.initState != SourceInitializationState.full) {
|
await reportService.log('Initialize source (init state=${source.scope.name}) to start app with mode=$appMode');
|
||||||
await reportService.log('Initialize source (init state=${source.initState.name}) to start app with mode=$appMode');
|
final loadTopEntriesFirst = settings.homePage == HomePageSetting.collection && settings.homeCustomCollection.isEmpty;
|
||||||
await source.init(
|
await source.init(loadTopEntriesFirst: loadTopEntriesFirst);
|
||||||
loadTopEntriesFirst: settings.homePage == HomePageSetting.collection && settings.homeCustomCollection.isEmpty,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
case AppMode.screenSaver:
|
case AppMode.screenSaver:
|
||||||
final source = context.read<CollectionSource>();
|
|
||||||
await reportService.log('Initialize source to start screen saver');
|
await reportService.log('Initialize source to start screen saver');
|
||||||
await source.init(
|
final source = context.read<CollectionSource>();
|
||||||
canAnalyze: false,
|
source.canAnalyze = false;
|
||||||
);
|
await source.init();
|
||||||
case AppMode.view:
|
case AppMode.view:
|
||||||
if (_isViewerSourceable(_viewerEntry) && _secureUris == null) {
|
if (_isViewerSourceable(_viewerEntry) && _secureUris == null) {
|
||||||
final directory = _viewerEntry?.directory;
|
final directory = _viewerEntry?.directory;
|
||||||
|
@ -243,10 +239,8 @@ class _HomePageState extends State<HomePage> {
|
||||||
unawaited(AnalysisService.registerCallback());
|
unawaited(AnalysisService.registerCallback());
|
||||||
await reportService.log('Initialize source to view item in directory $directory');
|
await reportService.log('Initialize source to view item in directory $directory');
|
||||||
final source = context.read<CollectionSource>();
|
final source = context.read<CollectionSource>();
|
||||||
await source.init(
|
source.canAnalyze = false;
|
||||||
directory: directory,
|
await source.init(albumFilter: AlbumFilter(directory, null));
|
||||||
canAnalyze: false,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await _initViewerEssentials();
|
await _initViewerEssentials();
|
||||||
|
@ -311,7 +305,7 @@ class _HomePageState extends State<HomePage> {
|
||||||
CollectionLens? collection;
|
CollectionLens? collection;
|
||||||
|
|
||||||
final source = context.read<CollectionSource>();
|
final source = context.read<CollectionSource>();
|
||||||
if (source.initState != SourceInitializationState.none) {
|
if (source.scope != SourceScope.none) {
|
||||||
final album = viewerEntry.directory;
|
final album = viewerEntry.directory;
|
||||||
if (album != null) {
|
if (album != null) {
|
||||||
// wait for collection to pass the `loading` state
|
// wait for collection to pass the `loading` state
|
||||||
|
|
|
@ -437,7 +437,7 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix
|
||||||
showFeedback(context, FeedbackType.warn, l10n.genericFailureFeedback);
|
showFeedback(context, FeedbackType.warn, l10n.genericFailureFeedback);
|
||||||
} else {
|
} else {
|
||||||
final source = context.read<CollectionSource>();
|
final source = context.read<CollectionSource>();
|
||||||
if (source.initState != SourceInitializationState.none) {
|
if (source.scope != SourceScope.none) {
|
||||||
await source.removeEntries({targetEntry.uri}, includeTrash: true);
|
await source.removeEntries({targetEntry.uri}, includeTrash: true);
|
||||||
}
|
}
|
||||||
EntryDeletedNotification({targetEntry}).dispatch(context);
|
EntryDeletedNotification({targetEntry}).dispatch(context);
|
||||||
|
|
|
@ -33,7 +33,7 @@ class FakeMediaStoreService extends Fake implements MediaStoreService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<AvesEntry> getEntries(bool safe, Map<int?, int?> knownEntries, {String? directory}) => Stream.fromIterable(entries);
|
Stream<AvesEntry> getEntries(Map<int?, int?> knownEntries, {String? directory}) => Stream.fromIterable(entries);
|
||||||
|
|
||||||
static var _lastId = 1;
|
static var _lastId = 1;
|
||||||
|
|
||||||
|
|
|
@ -109,7 +109,7 @@ void main() {
|
||||||
final source = MediaStoreSource();
|
final source = MediaStoreSource();
|
||||||
unawaited(source.init());
|
unawaited(source.init());
|
||||||
await Future.delayed(const Duration(milliseconds: 10));
|
await Future.delayed(const Duration(milliseconds: 10));
|
||||||
expect(source.initState, SourceInitializationState.full);
|
expect(source.scope, SourceScope.full);
|
||||||
await source.refreshUris({refreshEntry.uri});
|
await source.refreshUris({refreshEntry.uri});
|
||||||
|
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
|
|
Loading…
Reference in a new issue